~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: 2010-02-11 09:58:53 UTC
  • Revision ID: grantw@unimelb.edu.au-20100211095853-ip0ekc0ky2d6dxyv
'Login' is not a verb.

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