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

621 by mattgiuca
Added 2 new apps: home and subjects. Both fairly incomplete, just a basic
1
# IVLE
2
# Copyright (C) 2007-2008 The University of Melbourne
3
#
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17
18
# App: subjects
19
# Author: Matt Giuca
20
# Date: 29/2/2008
21
22
# This is an IVLE application.
23
# A sample / testing application for IVLE.
24
627 by mattgiuca
subjects: Now presents a top-level subjects menu (same as tutorials).
25
import os
1165.3.61 by William Grant
Provide a Subversion command to grab each submission.
26
import os.path
627 by mattgiuca
subjects: Now presents a top-level subjects menu (same as tutorials).
27
import urllib
1165.3.61 by William Grant
Provide a Subversion command to grab each submission.
28
import urlparse
627 by mattgiuca
subjects: Now presents a top-level subjects menu (same as tutorials).
29
import cgi
30
1294.2.52 by William Grant
Port subject-related views to object traversal.
31
from storm.locals import Desc, Store
1165.3.2 by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to
32
import genshi
1149 by William Grant
Allow tutors and lecturers to enrol people in their offerings.
33
from genshi.filters import HTMLFormFiller
1165.3.2 by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to
34
from genshi.template import Context, TemplateLoader
1149 by William Grant
Allow tutors and lecturers to enrol people in their offerings.
35
import formencode
1532 by William Grant
Add subject creation/editing UI. Not linked just yet.
36
import formencode.validators
1125 by William Grant
Rework ivle.webapp.admin.subjects#SubjectsView to split offerings nicely by
37
1539 by William Grant
Move BaseFormView into ivle.webapp.base.forms.
38
from ivle.webapp.base.forms import BaseFormView
39
from ivle.webapp.base.plugins import ViewPlugin, MediaPlugin
1099.1.34 by William Grant
Split up ivle.webapp.base.views into ivle.webapp.base.{rest,xhtml}, as it was
40
from ivle.webapp.base.xhtml import XHTMLView
1603 by William Grant
Add UI to clone worksheets between offerings -- replacing ivle-cloneworksheets.
41
from ivle.webapp.errors import BadRequest
1294.2.52 by William Grant
Port subject-related views to object traversal.
42
from ivle.webapp import ApplicationRoot
1165.3.9 by Nick Chadwick
merge from trunk
43
1165.3.2 by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to
44
from ivle.database import Subject, Semester, Offering, Enrolment, User,\
1165.3.8 by Nick Chadwick
Added a total submissions and total assesseds to the project view
45
                          ProjectSet, Project, ProjectSubmission
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
46
from ivle import util
1165.3.14 by William Grant
Improve ProjectView's template substantially.
47
import ivle.date
621 by mattgiuca
Added 2 new apps: home and subjects. Both fairly incomplete, just a basic
48
1375.1.2 by William Grant
Remove ProjectRESTView, which did nothing and was unused.
49
from ivle.webapp.admin.projectservice import ProjectSetRESTView
1165.3.2 by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to
50
from ivle.webapp.admin.offeringservice import OfferingRESTView
1592 by William Grant
Add routes for Semester. We'll need them for the admin UI.
51
from ivle.webapp.admin.publishing import (root_to_subject, root_to_semester,
1294.2.70 by William Grant
Split out ivle.webapp.admin's routes into annotated functions in ivle.webapp.traversal.
52
            subject_to_offering, offering_to_projectset, offering_to_project,
1592 by William Grant
Add routes for Semester. We'll need them for the admin UI.
53
            subject_url, semester_url, offering_url, projectset_url,
54
            project_url)
1294.2.96 by William Grant
Add a UserBreadcrumb.
55
from ivle.webapp.admin.breadcrumbs import (SubjectBreadcrumb,
1294.2.98 by William Grant
Add a ProjectBreadcrumb.
56
            OfferingBreadcrumb, UserBreadcrumb, ProjectBreadcrumb)
1533 by William Grant
Add a subject listing with new/edit icons.
57
from ivle.webapp.core import Plugin as CorePlugin
1358 by William Grant
Use the publishing framework to generate URLs to projectsets.
58
from ivle.webapp.groups import GroupsView
1533 by William Grant
Add a subject listing with new/edit icons.
59
from ivle.webapp.media import media_url
1442.1.31 by William Grant
Show the worksheet listing with marks and schtuff on the offering index.
60
from ivle.webapp.tutorial import Plugin as TutorialPlugin
1165.3.2 by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to
61
1099.1.20 by William Grant
ivle.webapp.admin.subject: Port www/apps/subjects to new framework.
62
class SubjectsView(XHTMLView):
63
    '''The view of the list of subjects.'''
1165.3.2 by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to
64
    template = 'templates/subjects.html'
1116 by William Grant
Move the old tutorial views into the 'subjects' tab, so they get the right
65
    tab = 'subjects'
1099.1.20 by William Grant
ivle.webapp.admin.subject: Port www/apps/subjects to new framework.
66
1099.1.110 by William Grant
Implement an authorization system in the new framework. This breaks the REST
67
    def authorize(self, req):
1138 by William Grant
SubjectsView now tells users if they have no enrolments.
68
        return req.user is not None
1099.1.110 by William Grant
Implement an authorization system in the new framework. This breaks the REST
69
1099.1.20 by William Grant
ivle.webapp.admin.subject: Port www/apps/subjects to new framework.
70
    def populate(self, req, ctx):
1525 by Matt Giuca
ivle/webapp/admin/templates/subjects.html: Use req.publisher.generate rather than rolling its own offering_url function.
71
        ctx['req'] = req
1139 by William Grant
Show group administration links on SubjectsView where privileges allow it.
72
        ctx['user'] = req.user
1125 by William Grant
Rework ivle.webapp.admin.subjects#SubjectsView to split offerings nicely by
73
        ctx['semesters'] = []
1533 by William Grant
Add a subject listing with new/edit icons.
74
1125 by William Grant
Rework ivle.webapp.admin.subjects#SubjectsView to split offerings nicely by
75
        for semester in req.store.find(Semester).order_by(Desc(Semester.year),
76
                                                     Desc(Semester.semester)):
1372 by Matt Giuca
admin/subject: Admin users now see all offerings in the system, not just the ones they are enrolled in.
77
            if req.user.admin:
78
                # For admins, show all subjects in the system
79
                offerings = list(semester.offerings.find())
80
            else:
81
                offerings = [enrolment.offering for enrolment in
1371 by Matt Giuca
admin/subject: Now sends a list of offerings the user is enrolled in to the Genshi template, rather than a list of enrolment objects (of which only the offering is observed). This allows us to send non-enrolment offerings to the template.
82
                                    semester.enrolments.find(user=req.user)]
83
            if len(offerings):
84
                ctx['semesters'].append((semester, offerings))
1099.1.20 by William Grant
ivle.webapp.admin.subject: Port www/apps/subjects to new framework.
85
1596 by William Grant
Split subject/semester management out onto a separate page, and link to SemesterEdit.
86
87
class SubjectsManage(XHTMLView):
88
    '''Subject management view.'''
89
    template = 'templates/subjects-manage.html'
90
    tab = 'subjects'
91
92
    def authorize(self, req):
93
        return req.user is not None and req.user.admin
94
95
    def populate(self, req, ctx):
96
        ctx['req'] = req
97
        ctx['mediapath'] = media_url(req, CorePlugin, 'images/')
98
        ctx['SubjectEdit'] = SubjectEdit
99
        ctx['SemesterEdit'] = SemesterEdit
100
101
        ctx['subjects'] = req.store.find(Subject).order_by(Subject.name)
102
        ctx['semesters'] = req.store.find(Semester).order_by(
103
            Semester.year, Semester.semester)
1533 by William Grant
Add a subject listing with new/edit icons.
104
1532 by William Grant
Add subject creation/editing UI. Not linked just yet.
105
106
class SubjectShortNameUniquenessValidator(formencode.FancyValidator):
107
    """A FormEncode validator that checks that a subject name is unused.
108
109
    The subject referenced by state.existing_subject is permitted
110
    to hold that name. If any other object holds it, the input is rejected.
111
    """
112
    def __init__(self, matching=None):
113
        self.matching = matching
114
115
    def _to_python(self, value, state):
116
        if (state.store.find(
117
                Subject, short_name=value).one() not in
118
                (None, state.existing_subject)):
119
            raise formencode.Invalid(
120
                'Short name already taken', value, state)
121
        return value
122
123
124
class SubjectSchema(formencode.Schema):
125
    short_name = formencode.All(
126
        SubjectShortNameUniquenessValidator(),
127
        formencode.validators.UnicodeString(not_empty=True))
128
    name = formencode.validators.UnicodeString(not_empty=True)
129
    code = formencode.validators.UnicodeString(not_empty=True)
130
131
1546 by William Grant
Derive the subject forms from BaseFormView.
132
class SubjectFormView(BaseFormView):
1532 by William Grant
Add subject creation/editing UI. Not linked just yet.
133
    """An abstract form to add or edit a subject."""
134
    tab = 'subjects'
135
136
    def authorize(self, req):
137
        return req.user is not None and req.user.admin
138
139
    def populate_state(self, state):
140
        state.existing_subject = None
141
1546 by William Grant
Derive the subject forms from BaseFormView.
142
    @property
143
    def validator(self):
144
        return SubjectSchema()
145
146
    def get_return_url(self, obj):
147
        return '/subjects'
1532 by William Grant
Add subject creation/editing UI. Not linked just yet.
148
149
150
class SubjectNew(SubjectFormView):
151
    """A form to create a subject."""
152
    template = 'templates/subject-new.html'
153
154
    def get_default_data(self, req):
155
        return {}
156
1546 by William Grant
Derive the subject forms from BaseFormView.
157
    def save_object(self, req, data):
1532 by William Grant
Add subject creation/editing UI. Not linked just yet.
158
        new_subject = Subject()
159
        new_subject.short_name = data['short_name']
160
        new_subject.name = data['name']
161
        new_subject.code = data['code']
162
163
        req.store.add(new_subject)
164
        return new_subject
165
166
167
class SubjectEdit(SubjectFormView):
168
    """A form to edit a subject."""
169
    template = 'templates/subject-edit.html'
170
171
    def populate_state(self, state):
172
        state.existing_subject = self.context
173
174
    def get_default_data(self, req):
175
        return {
176
            'short_name': self.context.short_name,
177
            'name': self.context.name,
178
            'code': self.context.code,
179
            }
180
1546 by William Grant
Derive the subject forms from BaseFormView.
181
    def save_object(self, req, data):
1532 by William Grant
Add subject creation/editing UI. Not linked just yet.
182
        self.context.short_name = data['short_name']
183
        self.context.name = data['name']
184
        self.context.code = data['code']
185
186
        return self.context
187
188
1543 by William Grant
Semester creation UI.
189
class SemesterUniquenessValidator(formencode.FancyValidator):
190
    """A FormEncode validator that checks that a semester is unique.
191
192
    There cannot be more than one semester for the same year and semester.
193
    """
194
    def _to_python(self, value, state):
195
        if (state.store.find(
196
                Semester, year=value['year'], semester=value['semester']
1594 by William Grant
Add semester edit UI.
197
                ).one() not in (None, state.existing_semester)):
1543 by William Grant
Semester creation UI.
198
            raise formencode.Invalid(
199
                'Semester already exists', value, state)
200
        return value
201
202
203
class SemesterSchema(formencode.Schema):
204
    year = formencode.validators.UnicodeString()
205
    semester = formencode.validators.UnicodeString()
1594 by William Grant
Add semester edit UI.
206
    state = formencode.All(
207
        formencode.validators.OneOf(["past", "current", "future"]),
208
        formencode.validators.UnicodeString())
1543 by William Grant
Semester creation UI.
209
    chained_validators = [SemesterUniquenessValidator()]
210
211
1594 by William Grant
Add semester edit UI.
212
class SemesterFormView(BaseFormView):
213
    tab = 'subjects'
214
215
    def authorize(self, req):
216
        return req.user is not None and req.user.admin
217
218
    @property
219
    def validator(self):
220
        return SemesterSchema()
221
222
    def get_return_url(self, obj):
223
        return '/subjects/+manage'
224
225
226
class SemesterNew(SemesterFormView):
1543 by William Grant
Semester creation UI.
227
    """A form to create a semester."""
228
    template = 'templates/semester-new.html'
229
    tab = 'subjects'
230
1594 by William Grant
Add semester edit UI.
231
    def populate_state(self, state):
232
        state.existing_semester = None
1543 by William Grant
Semester creation UI.
233
234
    def get_default_data(self, req):
235
        return {}
236
237
    def save_object(self, req, data):
238
        new_semester = Semester()
239
        new_semester.year = data['year']
240
        new_semester.semester = data['semester']
1594 by William Grant
Add semester edit UI.
241
        new_semester.state = data['state']
1543 by William Grant
Semester creation UI.
242
243
        req.store.add(new_semester)
244
        return new_semester
245
1594 by William Grant
Add semester edit UI.
246
247
class SemesterEdit(SemesterFormView):
248
    """A form to edit a semester."""
249
    template = 'templates/semester-edit.html'
250
251
    def populate_state(self, state):
252
        state.existing_semester = self.context
253
254
    def get_default_data(self, req):
255
        return {
256
            'year': self.context.year,
257
            'semester': self.context.semester,
258
            'state': self.context.state,
259
            }
260
261
    def save_object(self, req, data):
262
        self.context.year = data['year']
263
        self.context.semester = data['semester']
264
        self.context.state = data['state']
265
266
        return self.context
1543 by William Grant
Semester creation UI.
267
268
1442.1.2 by William Grant
Add basic (ie. pretty much empty) offering index.
269
class OfferingView(XHTMLView):
270
    """The home page of an offering."""
271
    template = 'templates/offering.html'
272
    tab = 'subjects'
273
    permission = 'view'
274
275
    def populate(self, req, ctx):
1442.1.31 by William Grant
Show the worksheet listing with marks and schtuff on the offering index.
276
        # Need the worksheet result styles.
277
        self.plugin_styles[TutorialPlugin] = ['tutorial.css']
1442.1.2 by William Grant
Add basic (ie. pretty much empty) offering index.
278
        ctx['context'] = self.context
279
        ctx['req'] = req
1544 by Matt Giuca
Added an argument 'config' to every single get_permissions method throughout the program. All calls to get_permissions pass a config. This is to allow per-site policy configurations on permissions.
280
        ctx['permissions'] = self.context.get_permissions(req.user,req.config)
1515 by Matt Giuca
Submit view: The projects list is now identical (except for radio buttons) to the view on the subjects page. It is much clearer and contains more info. The code is somewhat different, because it's a table, not a list, so I didn't abstract it. Moved a function out of subject.py to ivle.util, as it is shared by both views.
281
        ctx['format_submission_principal'] = util.format_submission_principal
1442.1.10 by William Grant
Add a nice padded list of projects.
282
        ctx['format_datetime'] = ivle.date.make_date_nice
283
        ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
1451.1.7 by William Grant
Add a 'Change details' link on the offering index, pointing to +edit.
284
        ctx['OfferingEdit'] = OfferingEdit
1603 by William Grant
Add UI to clone worksheets between offerings -- replacing ivle-cloneworksheets.
285
        ctx['OfferingCloneWorksheets'] = OfferingCloneWorksheets
1558 by William Grant
Allow tutors to manage groups.
286
        ctx['GroupsView'] = GroupsView
1442.1.2 by William Grant
Add basic (ie. pretty much empty) offering index.
287
1442.1.31 by William Grant
Show the worksheet listing with marks and schtuff on the offering index.
288
        # As we go, calculate the total score for this subject
289
        # (Assessable worksheets only, mandatory problems only)
290
291
        ctx['worksheets'], problems_total, problems_done = (
292
            ivle.worksheet.utils.create_list_of_fake_worksheets_and_stats(
293
                req.store, req.user, self.context))
294
295
        ctx['exercises_total'] = problems_total
296
        ctx['exercises_done'] = problems_done
297
        if problems_total > 0:
298
            if problems_done >= problems_total:
299
                ctx['worksheets_complete_class'] = "complete"
300
            elif problems_done > 0:
301
                ctx['worksheets_complete_class'] = "semicomplete"
302
            else:
303
                ctx['worksheets_complete_class'] = "incomplete"
304
            # Calculate the final percentage and mark for the subject
305
            (ctx['exercises_pct'], ctx['worksheet_mark'],
306
             ctx['worksheet_max_mark']) = (
307
                ivle.worksheet.utils.calculate_mark(
308
                    problems_done, problems_total))
309
1442.1.2 by William Grant
Add basic (ie. pretty much empty) offering index.
310
1537 by William Grant
Add offering creation UI, and allow admins to change the subject or semester of existing offerings.
311
class SubjectValidator(formencode.FancyValidator):
312
    """A FormEncode validator that turns a subject name into a subject.
313
314
    The state must have a 'store' attribute, which is the Storm store
315
    to use.
316
    """
317
    def _to_python(self, value, state):
318
        subject = state.store.find(Subject, short_name=value).one()
319
        if subject:
320
            return subject
321
        else:
322
            raise formencode.Invalid('Subject does not exist', value, state)
323
324
325
class SemesterValidator(formencode.FancyValidator):
326
    """A FormEncode validator that turns a string into a semester.
327
328
    The string should be of the form 'year/semester', eg. '2009/1'.
329
330
    The state must have a 'store' attribute, which is the Storm store
331
    to use.
332
    """
333
    def _to_python(self, value, state):
334
        try:
335
            year, semester = value.split('/')
336
        except ValueError:
337
            year = semester = None
338
339
        semester = state.store.find(
340
            Semester, year=year, semester=semester).one()
341
        if semester:
342
            return semester
343
        else:
344
            raise formencode.Invalid('Semester does not exist', value, state)
345
346
347
class OfferingUniquenessValidator(formencode.FancyValidator):
348
    """A FormEncode validator that checks that an offering is unique.
349
350
    There cannot be more than one offering in the same year and semester.
351
352
    The offering referenced by state.existing_offering is permitted to
353
    hold that year and semester tuple. If any other object holds it, the
354
    input is rejected.
355
    """
356
    def _to_python(self, value, state):
357
        if (state.store.find(
358
                Offering, subject=value['subject'],
359
                semester=value['semester']).one() not in
360
                (None, state.existing_offering)):
361
            raise formencode.Invalid(
362
                'Offering already exists', value, state)
363
        return value
364
365
1451.1.5 by William Grant
Add an OfferingEdit view, for setting the description and URL.
366
class OfferingSchema(formencode.Schema):
1451.1.8 by William Grant
Allow unsetting of the URL or description.
367
    description = formencode.validators.UnicodeString(
368
        if_missing=None, not_empty=False)
369
    url = formencode.validators.URL(if_missing=None, not_empty=False)
1451.1.5 by William Grant
Add an OfferingEdit view, for setting the description and URL.
370
371
1537 by William Grant
Add offering creation UI, and allow admins to change the subject or semester of existing offerings.
372
class OfferingAdminSchema(OfferingSchema):
373
    subject = formencode.All(
374
        SubjectValidator(), formencode.validators.UnicodeString())
375
    semester = formencode.All(
376
        SemesterValidator(), formencode.validators.UnicodeString())
377
    chained_validators = [OfferingUniquenessValidator()]
378
379
380
class OfferingEdit(BaseFormView):
1451.1.5 by William Grant
Add an OfferingEdit view, for setting the description and URL.
381
    """A form to edit an offering's details."""
382
    template = 'templates/offering-edit.html'
1523 by William Grant
Declare appropriate tabs on the rest of the views.
383
    tab = 'subjects'
1451.1.5 by William Grant
Add an OfferingEdit view, for setting the description and URL.
384
    permission = 'edit'
385
1537 by William Grant
Add offering creation UI, and allow admins to change the subject or semester of existing offerings.
386
    @property
387
    def validator(self):
388
        if self.req.user.admin:
389
            return OfferingAdminSchema()
1451.1.5 by William Grant
Add an OfferingEdit view, for setting the description and URL.
390
        else:
1537 by William Grant
Add offering creation UI, and allow admins to change the subject or semester of existing offerings.
391
            return OfferingSchema()
392
393
    def populate(self, req, ctx):
394
        super(OfferingEdit, self).populate(req, ctx)
1598 by William Grant
Sort subjects and semesters sanely in the offering forms.
395
        ctx['subjects'] = req.store.find(Subject).order_by(Subject.name)
396
        ctx['semesters'] = req.store.find(Semester).order_by(
397
            Semester.year, Semester.semester)
1537 by William Grant
Add offering creation UI, and allow admins to change the subject or semester of existing offerings.
398
399
    def populate_state(self, state):
400
        state.existing_offering = self.context
401
402
    def get_default_data(self, req):
403
        return {
404
            'subject': self.context.subject.short_name,
405
            'semester': self.context.semester.year + '/' +
406
                        self.context.semester.semester,
407
            'url': self.context.url,
408
            'description': self.context.description,
1451.1.5 by William Grant
Add an OfferingEdit view, for setting the description and URL.
409
            }
1537 by William Grant
Add offering creation UI, and allow admins to change the subject or semester of existing offerings.
410
411
    def save_object(self, req, data):
412
        if req.user.admin:
413
            self.context.subject = data['subject']
414
            self.context.semester = data['semester']
415
        self.context.description = data['description']
416
        self.context.url = unicode(data['url']) if data['url'] else None
417
        return self.context
418
419
420
class OfferingNew(BaseFormView):
421
    """A form to create an offering."""
422
    template = 'templates/offering-new.html'
423
    tab = 'subjects'
424
425
    def authorize(self, req):
426
        return req.user is not None and req.user.admin
427
428
    @property
429
    def validator(self):
430
        return OfferingAdminSchema()
431
432
    def populate(self, req, ctx):
433
        super(OfferingNew, self).populate(req, ctx)
1599 by William Grant
Sort subjects and semesters sanely in the offering new form too.
434
        ctx['subjects'] = req.store.find(Subject).order_by(Subject.name)
435
        ctx['semesters'] = req.store.find(Semester).order_by(
436
            Semester.year, Semester.semester)
1537 by William Grant
Add offering creation UI, and allow admins to change the subject or semester of existing offerings.
437
438
    def populate_state(self, state):
439
        state.existing_offering = None
440
441
    def get_default_data(self, req):
442
        return {}
443
444
    def save_object(self, req, data):
445
        new_offering = Offering()
446
        new_offering.subject = data['subject']
447
        new_offering.semester = data['semester']
448
        new_offering.description = data['description']
449
        new_offering.url = unicode(data['url']) if data['url'] else None
450
451
        req.store.add(new_offering)
452
        return new_offering
1451.1.5 by William Grant
Add an OfferingEdit view, for setting the description and URL.
453
454
1603 by William Grant
Add UI to clone worksheets between offerings -- replacing ivle-cloneworksheets.
455
class OfferingCloneWorksheetsSchema(formencode.Schema):
456
    subject = formencode.All(
457
        SubjectValidator(), formencode.validators.UnicodeString())
458
    semester = formencode.All(
459
        SemesterValidator(), formencode.validators.UnicodeString())
460
461
462
class OfferingCloneWorksheets(BaseFormView):
463
    """A form to clone worksheets from one offering to another."""
464
    template = 'templates/offering-clone-worksheets.html'
465
    tab = 'subjects'
466
467
    def authorize(self, req):
468
        return req.user is not None and req.user.admin
469
470
    @property
471
    def validator(self):
472
        return OfferingCloneWorksheetsSchema()
473
474
    def populate(self, req, ctx):
475
        super(OfferingCloneWorksheets, self).populate(req, ctx)
476
        ctx['subjects'] = req.store.find(Subject).order_by(Subject.name)
477
        ctx['semesters'] = req.store.find(Semester).order_by(
478
            Semester.year, Semester.semester)
479
480
    def get_default_data(self, req):
481
        return {}
482
483
    def save_object(self, req, data):
484
        if self.context.worksheets.count() > 0:
485
            raise BadRequest(
486
                "Cannot clone to target with existing worksheets.")
487
        offering = req.store.find(
488
            Offering, subject=data['subject'], semester=data['semester']).one()
489
        if offering is None:
490
            raise BadRequest("No such offering.")
491
        if offering.worksheets.count() == 0:
492
            raise BadRequest("Source offering has no worksheets.")
493
494
        self.context.clone_worksheets(offering)
495
        return self.context
496
497
1149 by William Grant
Allow tutors and lecturers to enrol people in their offerings.
498
class UserValidator(formencode.FancyValidator):
499
    """A FormEncode validator that turns a username into a user.
500
501
    The state must have a 'store' attribute, which is the Storm store
502
    to use."""
503
    def _to_python(self, value, state):
504
        user = User.get_by_login(state.store, value)
505
        if user:
506
            return user
507
        else:
1150 by William Grant
Refuse a +enrol if the user is already enrolled. This stops overwriting
508
            raise formencode.Invalid('User does not exist', value, state)
509
510
511
class NoEnrolmentValidator(formencode.FancyValidator):
512
    """A FormEncode validator that ensures absence of an enrolment.
513
514
    The state must have an 'offering' attribute.
515
    """
516
    def _to_python(self, value, state):
517
        if state.offering.get_enrolment(value):
518
            raise formencode.Invalid('User already enrolled', value, state)
519
        return value
1149 by William Grant
Allow tutors and lecturers to enrol people in their offerings.
520
521
1377 by Matt Giuca
database: Added finer-grained enrol permissions on offerings.
522
class RoleEnrolmentValidator(formencode.FancyValidator):
523
    """A FormEncode validator that checks permission to enrol users with a
524
    particular role.
525
526
    The state must have an 'offering' attribute.
527
    """
528
    def _to_python(self, value, state):
1544 by Matt Giuca
Added an argument 'config' to every single get_permissions method throughout the program. All calls to get_permissions pass a config. This is to allow per-site policy configurations on permissions.
529
        if (("enrol_" + value) not in
530
                state.offering.get_permissions(state.user, state.config)):
1377 by Matt Giuca
database: Added finer-grained enrol permissions on offerings.
531
            raise formencode.Invalid('Not allowed to assign users that role',
532
                                     value, state)
533
        return value
534
535
1149 by William Grant
Allow tutors and lecturers to enrol people in their offerings.
536
class EnrolSchema(formencode.Schema):
1150 by William Grant
Refuse a +enrol if the user is already enrolled. This stops overwriting
537
    user = formencode.All(NoEnrolmentValidator(), UserValidator())
1377 by Matt Giuca
database: Added finer-grained enrol permissions on offerings.
538
    role = formencode.All(formencode.validators.OneOf(
539
                                ["lecturer", "tutor", "student"]),
540
                          RoleEnrolmentValidator(),
541
                          formencode.validators.UnicodeString())
1149 by William Grant
Allow tutors and lecturers to enrol people in their offerings.
542
543
1365 by Matt Giuca
Added a new view under Offering/+enrolments to display all staff and students in an offering.
544
class EnrolmentsView(XHTMLView):
545
    """A page which displays all users enrolled in an offering."""
546
    template = 'templates/enrolments.html'
1523 by William Grant
Declare appropriate tabs on the rest of the views.
547
    tab = 'subjects'
1365 by Matt Giuca
Added a new view under Offering/+enrolments to display all staff and students in an offering.
548
    permission = 'edit'
549
550
    def populate(self, req, ctx):
551
        ctx['offering'] = self.context
552
1149 by William Grant
Allow tutors and lecturers to enrol people in their offerings.
553
class EnrolView(XHTMLView):
554
    """A form to enrol a user in an offering."""
1165.3.2 by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to
555
    template = 'templates/enrol.html'
1149 by William Grant
Allow tutors and lecturers to enrol people in their offerings.
556
    tab = 'subjects'
1376 by Matt Giuca
database: More granular permissions on offerings: Added 'enrol' permission.
557
    permission = 'enrol'
1149 by William Grant
Allow tutors and lecturers to enrol people in their offerings.
558
559
    def filter(self, stream, ctx):
560
        return stream | HTMLFormFiller(data=ctx['data'])
561
562
    def populate(self, req, ctx):
563
        if req.method == 'POST':
564
            data = dict(req.get_fieldstorage())
565
            try:
566
                validator = EnrolSchema()
1150 by William Grant
Refuse a +enrol if the user is already enrolled. This stops overwriting
567
                req.offering = self.context # XXX: Getting into state.
1149 by William Grant
Allow tutors and lecturers to enrol people in their offerings.
568
                data = validator.to_python(data, state=req)
1377 by Matt Giuca
database: Added finer-grained enrol permissions on offerings.
569
                self.context.enrol(data['user'], data['role'])
1149 by William Grant
Allow tutors and lecturers to enrol people in their offerings.
570
                req.store.commit()
571
                req.throw_redirect(req.uri)
572
            except formencode.Invalid, e:
573
                errors = e.unpack_errors()
574
        else:
575
            data = {}
576
            errors = {}
577
578
        ctx['data'] = data or {}
579
        ctx['offering'] = self.context
1544 by Matt Giuca
Added an argument 'config' to every single get_permissions method throughout the program. All calls to get_permissions pass a config. This is to allow per-site policy configurations on permissions.
580
        ctx['roles_auth'] = self.context.get_permissions(req.user, req.config)
1149 by William Grant
Allow tutors and lecturers to enrol people in their offerings.
581
        ctx['errors'] = errors
582
1165.3.19 by William Grant
Rename SubjectProjectSetView to OfferingProjectsView.
583
class OfferingProjectsView(XHTMLView):
584
    """View the projects for an offering."""
585
    template = 'templates/offering_projects.html'
1165.2.3 by Nick Chadwick
Added a new Admin view, which allows for the administration of projects
586
    permission = 'edit'
1165.3.18 by William Grant
Put the project listing and view in the Subjects tab.
587
    tab = 'subjects'
1165.3.2 by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to
588
1165.2.3 by Nick Chadwick
Added a new Admin view, which allows for the administration of projects
589
    def populate(self, req, ctx):
590
        self.plugin_styles[Plugin] = ["project.css"]
1165.3.2 by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to
591
        self.plugin_scripts[Plugin] = ["project.js"]
1361 by William Grant
Remove the last +projectsets hardcoding.
592
        ctx['req'] = req
1165.2.3 by Nick Chadwick
Added a new Admin view, which allows for the administration of projects
593
        ctx['offering'] = self.context
1165.3.2 by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to
594
        ctx['projectsets'] = []
1361 by William Grant
Remove the last +projectsets hardcoding.
595
        ctx['OfferingRESTView'] = OfferingRESTView
1165.3.2 by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to
596
597
        #Open the projectset Fragment, and render it for inclusion
598
        #into the ProjectSets page
599
        #XXX: This could be a lot cleaner
600
        loader = genshi.template.TemplateLoader(".", auto_reload=True)
601
602
        set_fragment = os.path.join(os.path.dirname(__file__),
603
                "templates/projectset_fragment.html")
604
        project_fragment = os.path.join(os.path.dirname(__file__),
605
                "templates/project_fragment.html")
606
607
        for projectset in self.context.project_sets:
608
            settmpl = loader.load(set_fragment)
609
            setCtx = Context()
1358 by William Grant
Use the publishing framework to generate URLs to projectsets.
610
            setCtx['req'] = req
1165.3.30 by William Grant
Clean out the projectset fragment context.
611
            setCtx['projectset'] = projectset
1165.3.2 by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to
612
            setCtx['projects'] = []
1358 by William Grant
Use the publishing framework to generate URLs to projectsets.
613
            setCtx['GroupsView'] = GroupsView
614
            setCtx['ProjectSetRESTView'] = ProjectSetRESTView
1165.3.2 by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to
615
616
            for project in projectset.projects:
617
                projecttmpl = loader.load(project_fragment)
618
                projectCtx = Context()
1358 by William Grant
Use the publishing framework to generate URLs to projectsets.
619
                projectCtx['req'] = req
1165.3.2 by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to
620
                projectCtx['project'] = project
621
622
                setCtx['projects'].append(
623
                        projecttmpl.generate(projectCtx))
624
625
            ctx['projectsets'].append(settmpl.generate(setCtx))
626
627
628
class ProjectView(XHTMLView):
1165.2.3 by Nick Chadwick
Added a new Admin view, which allows for the administration of projects
629
    """View the submissions for a ProjectSet"""
1165.3.2 by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to
630
    template = "templates/project.html"
1556 by William Grant
Allow tutors to view project submissions.
631
    permission = "view_project_submissions"
1165.3.18 by William Grant
Put the project listing and view in the Subjects tab.
632
    tab = 'subjects'
1165.3.2 by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to
633
1165.3.61 by William Grant
Provide a Subversion command to grab each submission.
634
    def build_subversion_url(self, svnroot, submission):
635
        princ = submission.assessed.principal
636
637
        if isinstance(princ, User):
638
            path = 'users/%s' % princ.login
639
        else:
640
            path = 'groups/%s_%s_%s_%s' % (
641
                    princ.project_set.offering.subject.short_name,
642
                    princ.project_set.offering.semester.year,
643
                    princ.project_set.offering.semester.semester,
644
                    princ.name
645
                    )
646
        return urlparse.urljoin(
647
                    svnroot,
648
                    os.path.join(path, submission.path[1:] if
649
                                       submission.path.startswith(os.sep) else
650
                                       submission.path))
651
1165.3.2 by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to
652
    def populate(self, req, ctx):
1165.3.66 by William Grant
Prettify the submissions table.
653
        self.plugin_styles[Plugin] = ["project.css"]
654
1375.1.4 by William Grant
Indicate when there is nobody assigned to a project, and link to the page to fix that.
655
        ctx['req'] = req
656
        ctx['GroupsView'] = GroupsView
657
        ctx['EnrolView'] = EnrolView
1165.3.14 by William Grant
Improve ProjectView's template substantially.
658
        ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
1165.3.61 by William Grant
Provide a Subversion command to grab each submission.
659
        ctx['build_subversion_url'] = self.build_subversion_url
660
        ctx['svn_addr'] = req.config['urls']['svn_addr']
1165.3.2 by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to
661
        ctx['project'] = self.context
1165.3.61 by William Grant
Provide a Subversion command to grab each submission.
662
        ctx['user'] = req.user
1165.3.2 by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to
663
1099.1.115 by William Grant
Add tabs to the new framework. Move the app icons into the apps themselves.
664
class Plugin(ViewPlugin, MediaPlugin):
1592 by William Grant
Add routes for Semester. We'll need them for the admin UI.
665
    forward_routes = (root_to_subject, root_to_semester, subject_to_offering,
1294.2.70 by William Grant
Split out ivle.webapp.admin's routes into annotated functions in ivle.webapp.traversal.
666
                      offering_to_project, offering_to_projectset)
1592 by William Grant
Add routes for Semester. We'll need them for the admin UI.
667
    reverse_routes = (
668
        subject_url, semester_url, offering_url, projectset_url, project_url)
1294.2.52 by William Grant
Port subject-related views to object traversal.
669
670
    views = [(ApplicationRoot, ('subjects', '+index'), SubjectsView),
1596 by William Grant
Split subject/semester management out onto a separate page, and link to SemesterEdit.
671
             (ApplicationRoot, ('subjects', '+manage'), SubjectsManage),
1532 by William Grant
Add subject creation/editing UI. Not linked just yet.
672
             (ApplicationRoot, ('subjects', '+new'), SubjectNew),
1537 by William Grant
Add offering creation UI, and allow admins to change the subject or semester of existing offerings.
673
             (ApplicationRoot, ('subjects', '+new-offering'), OfferingNew),
1594 by William Grant
Add semester edit UI.
674
             (ApplicationRoot, ('+semesters', '+new'), SemesterNew),
1532 by William Grant
Add subject creation/editing UI. Not linked just yet.
675
             (Subject, '+edit', SubjectEdit),
1594 by William Grant
Add semester edit UI.
676
             (Semester, '+edit', SemesterEdit),
1442.1.2 by William Grant
Add basic (ie. pretty much empty) offering index.
677
             (Offering, '+index', OfferingView),
1451.1.5 by William Grant
Add an OfferingEdit view, for setting the description and URL.
678
             (Offering, '+edit', OfferingEdit),
1603 by William Grant
Add UI to clone worksheets between offerings -- replacing ivle-cloneworksheets.
679
             (Offering, '+clone-worksheets', OfferingCloneWorksheets),
1365 by Matt Giuca
Added a new view under Offering/+enrolments to display all staff and students in an offering.
680
             (Offering, ('+enrolments', '+index'), EnrolmentsView),
1294.2.52 by William Grant
Port subject-related views to object traversal.
681
             (Offering, ('+enrolments', '+new'), EnrolView),
682
             (Offering, ('+projects', '+index'), OfferingProjectsView),
683
             (Project, '+index', ProjectView),
684
685
             (Offering, ('+projectsets', '+new'), OfferingRESTView, 'api'),
686
             (ProjectSet, ('+projects', '+new'), ProjectSetRESTView, 'api'),
687
             ]
1099.1.115 by William Grant
Add tabs to the new framework. Move the app icons into the apps themselves.
688
1294.2.94 by William Grant
Add a SubjectBreadcrumb.
689
    breadcrumbs = {Subject: SubjectBreadcrumb,
690
                   Offering: OfferingBreadcrumb,
1294.2.96 by William Grant
Add a UserBreadcrumb.
691
                   User: UserBreadcrumb,
1294.2.98 by William Grant
Add a ProjectBreadcrumb.
692
                   Project: ProjectBreadcrumb,
1294.2.89 by William Grant
Add an Offering breadcrumb.
693
                   }
694
1099.1.115 by William Grant
Add tabs to the new framework. Move the app icons into the apps themselves.
695
    tabs = [
1118 by matt.giuca
Rewrote tooltips for the four tabs visible by default.
696
        ('subjects', 'Subjects',
697
         'View subject content and complete worksheets',
698
         'subjects.png', 'subjects', 5)
1099.1.115 by William Grant
Add tabs to the new framework. Move the app icons into the apps themselves.
699
    ]
700
701
    media = 'subject-media'