~azzar1/unity/add-show-desktop-key

« back to all changes in this revision

Viewing changes to ivle/webapp/admin/subject.py

  • Committer: William Grant
  • Date: 2009-02-25 23:04:11 UTC
  • Revision ID: grantw@unimelb.edu.au-20090225230411-lbdyl32ir0m3d59b
Make all of the services executable.

Show diffs side-by-side

added added

removed removed

Lines of Context:
23
23
# A sample / testing application for IVLE.
24
24
 
25
25
import os
26
 
import os.path
27
26
import urllib
28
 
import urlparse
29
27
import cgi
30
28
 
31
 
from storm.locals import Desc, Store
32
 
import genshi
33
 
from genshi.filters import HTMLFormFiller
34
 
from genshi.template import Context, TemplateLoader
35
 
import formencode
36
 
 
37
29
from ivle.webapp.base.xhtml import XHTMLView
38
30
from ivle.webapp.base.plugins import ViewPlugin, MediaPlugin
39
 
from ivle.webapp import ApplicationRoot
40
 
 
41
 
from ivle.database import Subject, Semester, Offering, Enrolment, User,\
42
 
                          ProjectSet, Project, ProjectSubmission
 
31
from ivle.webapp.errors import NotFound
 
32
from ivle.database import Subject
43
33
from ivle import util
44
 
import ivle.date
45
34
 
46
 
from ivle.webapp.admin.projectservice import ProjectSetRESTView
47
 
from ivle.webapp.admin.offeringservice import OfferingRESTView
48
 
from ivle.webapp.admin.publishing import (root_to_subject,
49
 
            subject_to_offering, offering_to_projectset, offering_to_project,
50
 
            subject_url, offering_url, projectset_url, project_url)
51
 
from ivle.webapp.admin.breadcrumbs import (SubjectBreadcrumb,
52
 
            OfferingBreadcrumb, UserBreadcrumb, ProjectBreadcrumb)
53
 
from ivle.webapp.groups import GroupsView
54
 
from ivle.webapp.tutorial import Plugin as TutorialPlugin
55
35
 
56
36
class SubjectsView(XHTMLView):
57
37
    '''The view of the list of subjects.'''
58
 
    template = 'templates/subjects.html'
 
38
    template = 'subjects.html'
59
39
    tab = 'subjects'
60
40
 
61
41
    def authorize(self, req):
62
42
        return req.user is not None
63
43
 
64
44
    def populate(self, req, ctx):
65
 
        ctx['user'] = req.user
66
 
        ctx['semesters'] = []
67
 
        for semester in req.store.find(Semester).order_by(Desc(Semester.year),
68
 
                                                     Desc(Semester.semester)):
69
 
            if req.user.admin:
70
 
                # For admins, show all subjects in the system
71
 
                offerings = list(semester.offerings.find())
72
 
            else:
73
 
                offerings = [enrolment.offering for enrolment in
74
 
                                    semester.enrolments.find(user=req.user)]
75
 
            if len(offerings):
76
 
                ctx['semesters'].append((semester, offerings))
77
 
 
78
 
 
79
 
def format_submission_principal(user, principal):
80
 
    """Render a list of users to fit in the offering project listing.
81
 
 
82
 
    Given a user and a list of submitters, returns 'solo' if the
83
 
    only submitter is the user, or a string of the form
84
 
    'with A, B and C' if there are any other submitters.
85
 
 
86
 
    If submitters is None, we assume that the list of members could
87
 
    not be determined, so we just return 'group'.
88
 
    """
89
 
    if principal is None:
90
 
        return 'group'
91
 
 
92
 
    if principal is user:
93
 
        return 'solo'
94
 
 
95
 
    display_names = sorted(
96
 
        member.display_name for member in principal.members
97
 
        if member is not user)
98
 
 
99
 
    if len(display_names) == 0:
100
 
        return 'solo (%s)' % principal.name
101
 
    elif len(display_names) == 1:
102
 
        return 'with %s (%s)' % (display_names[0], principal.name)
103
 
    elif len(display_names) > 5:
104
 
        return 'with %d others (%s)' % (len(display_names), principal.name)
105
 
    else:
106
 
        return 'with %s and %s (%s)' % (', '.join(display_names[:-1]),
107
 
                                        display_names[-1], principal.name)
108
 
 
109
 
 
110
 
class OfferingView(XHTMLView):
111
 
    """The home page of an offering."""
112
 
    template = 'templates/offering.html'
113
 
    tab = 'subjects'
114
 
    permission = 'view'
115
 
 
116
 
    def populate(self, req, ctx):
117
 
        # Need the worksheet result styles.
118
 
        self.plugin_styles[TutorialPlugin] = ['tutorial.css']
119
 
        ctx['context'] = self.context
120
 
        ctx['req'] = req
121
 
        ctx['permissions'] = self.context.get_permissions(req.user)
122
 
        ctx['format_submission_principal'] = format_submission_principal
123
 
        ctx['format_datetime'] = ivle.date.make_date_nice
124
 
        ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
125
 
 
126
 
        # As we go, calculate the total score for this subject
127
 
        # (Assessable worksheets only, mandatory problems only)
128
 
 
129
 
        ctx['worksheets'], problems_total, problems_done = (
130
 
            ivle.worksheet.utils.create_list_of_fake_worksheets_and_stats(
131
 
                req.store, req.user, self.context))
132
 
 
133
 
        ctx['exercises_total'] = problems_total
134
 
        ctx['exercises_done'] = problems_done
135
 
        if problems_total > 0:
136
 
            if problems_done >= problems_total:
137
 
                ctx['worksheets_complete_class'] = "complete"
138
 
            elif problems_done > 0:
139
 
                ctx['worksheets_complete_class'] = "semicomplete"
140
 
            else:
141
 
                ctx['worksheets_complete_class'] = "incomplete"
142
 
            # Calculate the final percentage and mark for the subject
143
 
            (ctx['exercises_pct'], ctx['worksheet_mark'],
144
 
             ctx['worksheet_max_mark']) = (
145
 
                ivle.worksheet.utils.calculate_mark(
146
 
                    problems_done, problems_total))
147
 
 
148
 
 
149
 
class UserValidator(formencode.FancyValidator):
150
 
    """A FormEncode validator that turns a username into a user.
151
 
 
152
 
    The state must have a 'store' attribute, which is the Storm store
153
 
    to use."""
154
 
    def _to_python(self, value, state):
155
 
        user = User.get_by_login(state.store, value)
156
 
        if user:
157
 
            return user
158
 
        else:
159
 
            raise formencode.Invalid('User does not exist', value, state)
160
 
 
161
 
 
162
 
class NoEnrolmentValidator(formencode.FancyValidator):
163
 
    """A FormEncode validator that ensures absence of an enrolment.
164
 
 
165
 
    The state must have an 'offering' attribute.
166
 
    """
167
 
    def _to_python(self, value, state):
168
 
        if state.offering.get_enrolment(value):
169
 
            raise formencode.Invalid('User already enrolled', value, state)
170
 
        return value
171
 
 
172
 
 
173
 
class RoleEnrolmentValidator(formencode.FancyValidator):
174
 
    """A FormEncode validator that checks permission to enrol users with a
175
 
    particular role.
176
 
 
177
 
    The state must have an 'offering' attribute.
178
 
    """
179
 
    def _to_python(self, value, state):
180
 
        if ("enrol_" + value) not in state.offering.get_permissions(state.user):
181
 
            raise formencode.Invalid('Not allowed to assign users that role',
182
 
                                     value, state)
183
 
        return value
184
 
 
185
 
 
186
 
class EnrolSchema(formencode.Schema):
187
 
    user = formencode.All(NoEnrolmentValidator(), UserValidator())
188
 
    role = formencode.All(formencode.validators.OneOf(
189
 
                                ["lecturer", "tutor", "student"]),
190
 
                          RoleEnrolmentValidator(),
191
 
                          formencode.validators.UnicodeString())
192
 
 
193
 
 
194
 
class EnrolmentsView(XHTMLView):
195
 
    """A page which displays all users enrolled in an offering."""
196
 
    template = 'templates/enrolments.html'
197
 
    permission = 'edit'
198
 
 
199
 
    def populate(self, req, ctx):
200
 
        ctx['offering'] = self.context
201
 
 
202
 
class EnrolView(XHTMLView):
203
 
    """A form to enrol a user in an offering."""
204
 
    template = 'templates/enrol.html'
205
 
    tab = 'subjects'
206
 
    permission = 'enrol'
207
 
 
208
 
    def filter(self, stream, ctx):
209
 
        return stream | HTMLFormFiller(data=ctx['data'])
210
 
 
211
 
    def populate(self, req, ctx):
212
 
        if req.method == 'POST':
213
 
            data = dict(req.get_fieldstorage())
214
 
            try:
215
 
                validator = EnrolSchema()
216
 
                req.offering = self.context # XXX: Getting into state.
217
 
                data = validator.to_python(data, state=req)
218
 
                self.context.enrol(data['user'], data['role'])
219
 
                req.store.commit()
220
 
                req.throw_redirect(req.uri)
221
 
            except formencode.Invalid, e:
222
 
                errors = e.unpack_errors()
223
 
        else:
224
 
            data = {}
225
 
            errors = {}
226
 
 
227
 
        ctx['data'] = data or {}
228
 
        ctx['offering'] = self.context
229
 
        ctx['roles_auth'] = self.context.get_permissions(req.user)
230
 
        ctx['errors'] = errors
231
 
 
232
 
class OfferingProjectsView(XHTMLView):
233
 
    """View the projects for an offering."""
234
 
    template = 'templates/offering_projects.html'
235
 
    permission = 'edit'
236
 
    tab = 'subjects'
237
 
 
238
 
    def populate(self, req, ctx):
239
 
        self.plugin_styles[Plugin] = ["project.css"]
240
 
        self.plugin_scripts[Plugin] = ["project.js"]
241
 
        ctx['req'] = req
242
 
        ctx['offering'] = self.context
243
 
        ctx['projectsets'] = []
244
 
        ctx['OfferingRESTView'] = OfferingRESTView
245
 
 
246
 
        #Open the projectset Fragment, and render it for inclusion
247
 
        #into the ProjectSets page
248
 
        #XXX: This could be a lot cleaner
249
 
        loader = genshi.template.TemplateLoader(".", auto_reload=True)
250
 
 
251
 
        set_fragment = os.path.join(os.path.dirname(__file__),
252
 
                "templates/projectset_fragment.html")
253
 
        project_fragment = os.path.join(os.path.dirname(__file__),
254
 
                "templates/project_fragment.html")
255
 
 
256
 
        for projectset in self.context.project_sets:
257
 
            settmpl = loader.load(set_fragment)
258
 
            setCtx = Context()
259
 
            setCtx['req'] = req
260
 
            setCtx['projectset'] = projectset
261
 
            setCtx['projects'] = []
262
 
            setCtx['GroupsView'] = GroupsView
263
 
            setCtx['ProjectSetRESTView'] = ProjectSetRESTView
264
 
 
265
 
            for project in projectset.projects:
266
 
                projecttmpl = loader.load(project_fragment)
267
 
                projectCtx = Context()
268
 
                projectCtx['req'] = req
269
 
                projectCtx['project'] = project
270
 
 
271
 
                setCtx['projects'].append(
272
 
                        projecttmpl.generate(projectCtx))
273
 
 
274
 
            ctx['projectsets'].append(settmpl.generate(setCtx))
275
 
 
276
 
 
277
 
class ProjectView(XHTMLView):
278
 
    """View the submissions for a ProjectSet"""
279
 
    template = "templates/project.html"
280
 
    permission = "edit"
281
 
    tab = 'subjects'
282
 
 
283
 
    def build_subversion_url(self, svnroot, submission):
284
 
        princ = submission.assessed.principal
285
 
 
286
 
        if isinstance(princ, User):
287
 
            path = 'users/%s' % princ.login
288
 
        else:
289
 
            path = 'groups/%s_%s_%s_%s' % (
290
 
                    princ.project_set.offering.subject.short_name,
291
 
                    princ.project_set.offering.semester.year,
292
 
                    princ.project_set.offering.semester.semester,
293
 
                    princ.name
294
 
                    )
295
 
        return urlparse.urljoin(
296
 
                    svnroot,
297
 
                    os.path.join(path, submission.path[1:] if
298
 
                                       submission.path.startswith(os.sep) else
299
 
                                       submission.path))
300
 
 
301
 
    def populate(self, req, ctx):
302
 
        self.plugin_styles[Plugin] = ["project.css"]
303
 
 
304
 
        ctx['req'] = req
305
 
        ctx['GroupsView'] = GroupsView
306
 
        ctx['EnrolView'] = EnrolView
307
 
        ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
308
 
        ctx['build_subversion_url'] = self.build_subversion_url
309
 
        ctx['svn_addr'] = req.config['urls']['svn_addr']
310
 
        ctx['project'] = self.context
311
 
        ctx['user'] = req.user
 
45
        ctx['enrolments'] = req.user.active_enrolments
312
46
 
313
47
class Plugin(ViewPlugin, MediaPlugin):
314
 
    forward_routes = (root_to_subject, subject_to_offering,
315
 
                      offering_to_project, offering_to_projectset)
316
 
    reverse_routes = (subject_url, offering_url, projectset_url, project_url)
317
 
 
318
 
    views = [(ApplicationRoot, ('subjects', '+index'), SubjectsView),
319
 
             (Offering, '+index', OfferingView),
320
 
             (Offering, ('+enrolments', '+index'), EnrolmentsView),
321
 
             (Offering, ('+enrolments', '+new'), EnrolView),
322
 
             (Offering, ('+projects', '+index'), OfferingProjectsView),
323
 
             (Project, '+index', ProjectView),
324
 
 
325
 
             (Offering, ('+projectsets', '+new'), OfferingRESTView, 'api'),
326
 
             (ProjectSet, ('+projects', '+new'), ProjectSetRESTView, 'api'),
327
 
             ]
328
 
 
329
 
    breadcrumbs = {Subject: SubjectBreadcrumb,
330
 
                   Offering: OfferingBreadcrumb,
331
 
                   User: UserBreadcrumb,
332
 
                   Project: ProjectBreadcrumb,
333
 
                   }
 
48
    urls = [
 
49
        ('subjects/', SubjectsView),
 
50
    ]
334
51
 
335
52
    tabs = [
336
53
        ('subjects', 'Subjects',