~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
1294.2.52 by William Grant
Port subject-related views to object traversal.
41
from ivle.webapp import ApplicationRoot
1165.3.9 by Nick Chadwick
merge from trunk
42
1165.3.2 by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to
43
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
44
                          ProjectSet, Project, ProjectSubmission
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
45
from ivle import util
1165.3.14 by William Grant
Improve ProjectView's template substantially.
46
import ivle.date
621 by mattgiuca
Added 2 new apps: home and subjects. Both fairly incomplete, just a basic
47
1375.1.2 by William Grant
Remove ProjectRESTView, which did nothing and was unused.
48
from ivle.webapp.admin.projectservice import ProjectSetRESTView
1165.3.2 by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to
49
from ivle.webapp.admin.offeringservice import OfferingRESTView
1294.3.2 by William Grant
Router->Publisher
50
from ivle.webapp.admin.publishing import (root_to_subject,
1294.2.70 by William Grant
Split out ivle.webapp.admin's routes into annotated functions in ivle.webapp.traversal.
51
            subject_to_offering, offering_to_projectset, offering_to_project,
52
            subject_url, offering_url, projectset_url, project_url)
1294.2.96 by William Grant
Add a UserBreadcrumb.
53
from ivle.webapp.admin.breadcrumbs import (SubjectBreadcrumb,
1294.2.98 by William Grant
Add a ProjectBreadcrumb.
54
            OfferingBreadcrumb, UserBreadcrumb, ProjectBreadcrumb)
1533 by William Grant
Add a subject listing with new/edit icons.
55
from ivle.webapp.core import Plugin as CorePlugin
1358 by William Grant
Use the publishing framework to generate URLs to projectsets.
56
from ivle.webapp.groups import GroupsView
1533 by William Grant
Add a subject listing with new/edit icons.
57
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.
58
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
59
1099.1.20 by William Grant
ivle.webapp.admin.subject: Port www/apps/subjects to new framework.
60
class SubjectsView(XHTMLView):
61
    '''The view of the list of subjects.'''
1165.3.2 by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to
62
    template = 'templates/subjects.html'
1116 by William Grant
Move the old tutorial views into the 'subjects' tab, so they get the right
63
    tab = 'subjects'
1099.1.20 by William Grant
ivle.webapp.admin.subject: Port www/apps/subjects to new framework.
64
1099.1.110 by William Grant
Implement an authorization system in the new framework. This breaks the REST
65
    def authorize(self, req):
1138 by William Grant
SubjectsView now tells users if they have no enrolments.
66
        return req.user is not None
1099.1.110 by William Grant
Implement an authorization system in the new framework. This breaks the REST
67
1099.1.20 by William Grant
ivle.webapp.admin.subject: Port www/apps/subjects to new framework.
68
    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.
69
        ctx['req'] = req
1139 by William Grant
Show group administration links on SubjectsView where privileges allow it.
70
        ctx['user'] = req.user
1125 by William Grant
Rework ivle.webapp.admin.subjects#SubjectsView to split offerings nicely by
71
        ctx['semesters'] = []
1533 by William Grant
Add a subject listing with new/edit icons.
72
        ctx['mediapath'] = media_url(req, CorePlugin, 'images/')
73
        ctx['SubjectEdit'] = SubjectEdit
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
1533 by William Grant
Add a subject listing with new/edit icons.
86
        # Admins get a separate list of subjects so they can add/edit.
87
        if req.user.admin:
88
            ctx['subjects'] = req.store.find(Subject).order_by(Subject.name)
89
1532 by William Grant
Add subject creation/editing UI. Not linked just yet.
90
91
class SubjectShortNameUniquenessValidator(formencode.FancyValidator):
92
    """A FormEncode validator that checks that a subject name is unused.
93
94
    The subject referenced by state.existing_subject is permitted
95
    to hold that name. If any other object holds it, the input is rejected.
96
    """
97
    def __init__(self, matching=None):
98
        self.matching = matching
99
100
    def _to_python(self, value, state):
101
        if (state.store.find(
102
                Subject, short_name=value).one() not in
103
                (None, state.existing_subject)):
104
            raise formencode.Invalid(
105
                'Short name already taken', value, state)
106
        return value
107
108
109
class SubjectSchema(formencode.Schema):
110
    short_name = formencode.All(
111
        SubjectShortNameUniquenessValidator(),
112
        formencode.validators.UnicodeString(not_empty=True))
113
    name = formencode.validators.UnicodeString(not_empty=True)
114
    code = formencode.validators.UnicodeString(not_empty=True)
115
116
1546 by William Grant
Derive the subject forms from BaseFormView.
117
class SubjectFormView(BaseFormView):
1532 by William Grant
Add subject creation/editing UI. Not linked just yet.
118
    """An abstract form to add or edit a subject."""
119
    tab = 'subjects'
120
121
    def authorize(self, req):
122
        return req.user is not None and req.user.admin
123
124
    def populate_state(self, state):
125
        state.existing_subject = None
126
1546 by William Grant
Derive the subject forms from BaseFormView.
127
    @property
128
    def validator(self):
129
        return SubjectSchema()
130
131
    def get_return_url(self, obj):
132
        return '/subjects'
1532 by William Grant
Add subject creation/editing UI. Not linked just yet.
133
134
135
class SubjectNew(SubjectFormView):
136
    """A form to create a subject."""
137
    template = 'templates/subject-new.html'
138
139
    def get_default_data(self, req):
140
        return {}
141
1546 by William Grant
Derive the subject forms from BaseFormView.
142
    def save_object(self, req, data):
1532 by William Grant
Add subject creation/editing UI. Not linked just yet.
143
        new_subject = Subject()
144
        new_subject.short_name = data['short_name']
145
        new_subject.name = data['name']
146
        new_subject.code = data['code']
147
148
        req.store.add(new_subject)
149
        return new_subject
150
151
152
class SubjectEdit(SubjectFormView):
153
    """A form to edit a subject."""
154
    template = 'templates/subject-edit.html'
155
156
    def populate_state(self, state):
157
        state.existing_subject = self.context
158
159
    def get_default_data(self, req):
160
        return {
161
            'short_name': self.context.short_name,
162
            'name': self.context.name,
163
            'code': self.context.code,
164
            }
165
1546 by William Grant
Derive the subject forms from BaseFormView.
166
    def save_object(self, req, data):
1532 by William Grant
Add subject creation/editing UI. Not linked just yet.
167
        self.context.short_name = data['short_name']
168
        self.context.name = data['name']
169
        self.context.code = data['code']
170
171
        return self.context
172
173
1543 by William Grant
Semester creation UI.
174
class SemesterUniquenessValidator(formencode.FancyValidator):
175
    """A FormEncode validator that checks that a semester is unique.
176
177
    There cannot be more than one semester for the same year and semester.
178
    """
179
    def _to_python(self, value, state):
180
        if (state.store.find(
181
                Semester, year=value['year'], semester=value['semester']
182
                ).count() > 0):
183
            raise formencode.Invalid(
184
                'Semester already exists', value, state)
185
        return value
186
187
188
class SemesterSchema(formencode.Schema):
189
    year = formencode.validators.UnicodeString()
190
    semester = formencode.validators.UnicodeString()
191
    chained_validators = [SemesterUniquenessValidator()]
192
193
194
class SemesterNew(BaseFormView):
195
    """A form to create a semester."""
196
    template = 'templates/semester-new.html'
197
    tab = 'subjects'
198
199
    def authorize(self, req):
200
        return req.user is not None and req.user.admin
201
202
    @property
203
    def validator(self):
204
        return SemesterSchema()
205
206
    def get_default_data(self, req):
207
        return {}
208
209
    def save_object(self, req, data):
210
        new_semester = Semester()
211
        new_semester.year = data['year']
212
        new_semester.semester = data['semester']
213
214
        req.store.add(new_semester)
215
        return new_semester
216
217
    def get_return_url(self, obj):
218
        return '/subjects'
219
220
1442.1.2 by William Grant
Add basic (ie. pretty much empty) offering index.
221
class OfferingView(XHTMLView):
222
    """The home page of an offering."""
223
    template = 'templates/offering.html'
224
    tab = 'subjects'
225
    permission = 'view'
226
227
    def populate(self, req, ctx):
1442.1.31 by William Grant
Show the worksheet listing with marks and schtuff on the offering index.
228
        # Need the worksheet result styles.
229
        self.plugin_styles[TutorialPlugin] = ['tutorial.css']
1442.1.2 by William Grant
Add basic (ie. pretty much empty) offering index.
230
        ctx['context'] = self.context
231
        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.
232
        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.
233
        ctx['format_submission_principal'] = util.format_submission_principal
1442.1.10 by William Grant
Add a nice padded list of projects.
234
        ctx['format_datetime'] = ivle.date.make_date_nice
235
        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.
236
        ctx['OfferingEdit'] = OfferingEdit
1558 by William Grant
Allow tutors to manage groups.
237
        ctx['GroupsView'] = GroupsView
1442.1.2 by William Grant
Add basic (ie. pretty much empty) offering index.
238
1442.1.31 by William Grant
Show the worksheet listing with marks and schtuff on the offering index.
239
        # As we go, calculate the total score for this subject
240
        # (Assessable worksheets only, mandatory problems only)
241
242
        ctx['worksheets'], problems_total, problems_done = (
243
            ivle.worksheet.utils.create_list_of_fake_worksheets_and_stats(
244
                req.store, req.user, self.context))
245
246
        ctx['exercises_total'] = problems_total
247
        ctx['exercises_done'] = problems_done
248
        if problems_total > 0:
249
            if problems_done >= problems_total:
250
                ctx['worksheets_complete_class'] = "complete"
251
            elif problems_done > 0:
252
                ctx['worksheets_complete_class'] = "semicomplete"
253
            else:
254
                ctx['worksheets_complete_class'] = "incomplete"
255
            # Calculate the final percentage and mark for the subject
256
            (ctx['exercises_pct'], ctx['worksheet_mark'],
257
             ctx['worksheet_max_mark']) = (
258
                ivle.worksheet.utils.calculate_mark(
259
                    problems_done, problems_total))
260
1442.1.2 by William Grant
Add basic (ie. pretty much empty) offering index.
261
1537 by William Grant
Add offering creation UI, and allow admins to change the subject or semester of existing offerings.
262
class SubjectValidator(formencode.FancyValidator):
263
    """A FormEncode validator that turns a subject name into a subject.
264
265
    The state must have a 'store' attribute, which is the Storm store
266
    to use.
267
    """
268
    def _to_python(self, value, state):
269
        subject = state.store.find(Subject, short_name=value).one()
270
        if subject:
271
            return subject
272
        else:
273
            raise formencode.Invalid('Subject does not exist', value, state)
274
275
276
class SemesterValidator(formencode.FancyValidator):
277
    """A FormEncode validator that turns a string into a semester.
278
279
    The string should be of the form 'year/semester', eg. '2009/1'.
280
281
    The state must have a 'store' attribute, which is the Storm store
282
    to use.
283
    """
284
    def _to_python(self, value, state):
285
        try:
286
            year, semester = value.split('/')
287
        except ValueError:
288
            year = semester = None
289
290
        semester = state.store.find(
291
            Semester, year=year, semester=semester).one()
292
        if semester:
293
            return semester
294
        else:
295
            raise formencode.Invalid('Semester does not exist', value, state)
296
297
298
class OfferingUniquenessValidator(formencode.FancyValidator):
299
    """A FormEncode validator that checks that an offering is unique.
300
301
    There cannot be more than one offering in the same year and semester.
302
303
    The offering referenced by state.existing_offering is permitted to
304
    hold that year and semester tuple. If any other object holds it, the
305
    input is rejected.
306
    """
307
    def _to_python(self, value, state):
308
        if (state.store.find(
309
                Offering, subject=value['subject'],
310
                semester=value['semester']).one() not in
311
                (None, state.existing_offering)):
312
            raise formencode.Invalid(
313
                'Offering already exists', value, state)
314
        return value
315
316
1451.1.5 by William Grant
Add an OfferingEdit view, for setting the description and URL.
317
class OfferingSchema(formencode.Schema):
1451.1.8 by William Grant
Allow unsetting of the URL or description.
318
    description = formencode.validators.UnicodeString(
319
        if_missing=None, not_empty=False)
320
    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.
321
322
1537 by William Grant
Add offering creation UI, and allow admins to change the subject or semester of existing offerings.
323
class OfferingAdminSchema(OfferingSchema):
324
    subject = formencode.All(
325
        SubjectValidator(), formencode.validators.UnicodeString())
326
    semester = formencode.All(
327
        SemesterValidator(), formencode.validators.UnicodeString())
328
    chained_validators = [OfferingUniquenessValidator()]
329
330
331
class OfferingEdit(BaseFormView):
1451.1.5 by William Grant
Add an OfferingEdit view, for setting the description and URL.
332
    """A form to edit an offering's details."""
333
    template = 'templates/offering-edit.html'
1523 by William Grant
Declare appropriate tabs on the rest of the views.
334
    tab = 'subjects'
1451.1.5 by William Grant
Add an OfferingEdit view, for setting the description and URL.
335
    permission = 'edit'
336
1537 by William Grant
Add offering creation UI, and allow admins to change the subject or semester of existing offerings.
337
    @property
338
    def validator(self):
339
        if self.req.user.admin:
340
            return OfferingAdminSchema()
1451.1.5 by William Grant
Add an OfferingEdit view, for setting the description and URL.
341
        else:
1537 by William Grant
Add offering creation UI, and allow admins to change the subject or semester of existing offerings.
342
            return OfferingSchema()
343
344
    def populate(self, req, ctx):
345
        super(OfferingEdit, self).populate(req, ctx)
346
        ctx['subjects'] = req.store.find(Subject)
347
        ctx['semesters'] = req.store.find(Semester)
348
349
    def populate_state(self, state):
350
        state.existing_offering = self.context
351
352
    def get_default_data(self, req):
353
        return {
354
            'subject': self.context.subject.short_name,
355
            'semester': self.context.semester.year + '/' +
356
                        self.context.semester.semester,
357
            'url': self.context.url,
358
            'description': self.context.description,
1451.1.5 by William Grant
Add an OfferingEdit view, for setting the description and URL.
359
            }
1537 by William Grant
Add offering creation UI, and allow admins to change the subject or semester of existing offerings.
360
361
    def save_object(self, req, data):
362
        if req.user.admin:
363
            self.context.subject = data['subject']
364
            self.context.semester = data['semester']
365
        self.context.description = data['description']
366
        self.context.url = unicode(data['url']) if data['url'] else None
367
        return self.context
368
369
370
class OfferingNew(BaseFormView):
371
    """A form to create an offering."""
372
    template = 'templates/offering-new.html'
373
    tab = 'subjects'
374
375
    def authorize(self, req):
376
        return req.user is not None and req.user.admin
377
378
    @property
379
    def validator(self):
380
        return OfferingAdminSchema()
381
382
    def populate(self, req, ctx):
383
        super(OfferingNew, self).populate(req, ctx)
384
        ctx['subjects'] = req.store.find(Subject)
385
        ctx['semesters'] = req.store.find(Semester)
386
387
    def populate_state(self, state):
388
        state.existing_offering = None
389
390
    def get_default_data(self, req):
391
        return {}
392
393
    def save_object(self, req, data):
394
        new_offering = Offering()
395
        new_offering.subject = data['subject']
396
        new_offering.semester = data['semester']
397
        new_offering.description = data['description']
398
        new_offering.url = unicode(data['url']) if data['url'] else None
399
400
        req.store.add(new_offering)
401
        return new_offering
1451.1.5 by William Grant
Add an OfferingEdit view, for setting the description and URL.
402
403
1149 by William Grant
Allow tutors and lecturers to enrol people in their offerings.
404
class UserValidator(formencode.FancyValidator):
405
    """A FormEncode validator that turns a username into a user.
406
407
    The state must have a 'store' attribute, which is the Storm store
408
    to use."""
409
    def _to_python(self, value, state):
410
        user = User.get_by_login(state.store, value)
411
        if user:
412
            return user
413
        else:
1150 by William Grant
Refuse a +enrol if the user is already enrolled. This stops overwriting
414
            raise formencode.Invalid('User does not exist', value, state)
415
416
417
class NoEnrolmentValidator(formencode.FancyValidator):
418
    """A FormEncode validator that ensures absence of an enrolment.
419
420
    The state must have an 'offering' attribute.
421
    """
422
    def _to_python(self, value, state):
423
        if state.offering.get_enrolment(value):
424
            raise formencode.Invalid('User already enrolled', value, state)
425
        return value
1149 by William Grant
Allow tutors and lecturers to enrol people in their offerings.
426
427
1377 by Matt Giuca
database: Added finer-grained enrol permissions on offerings.
428
class RoleEnrolmentValidator(formencode.FancyValidator):
429
    """A FormEncode validator that checks permission to enrol users with a
430
    particular role.
431
432
    The state must have an 'offering' attribute.
433
    """
434
    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.
435
        if (("enrol_" + value) not in
436
                state.offering.get_permissions(state.user, state.config)):
1377 by Matt Giuca
database: Added finer-grained enrol permissions on offerings.
437
            raise formencode.Invalid('Not allowed to assign users that role',
438
                                     value, state)
439
        return value
440
441
1149 by William Grant
Allow tutors and lecturers to enrol people in their offerings.
442
class EnrolSchema(formencode.Schema):
1150 by William Grant
Refuse a +enrol if the user is already enrolled. This stops overwriting
443
    user = formencode.All(NoEnrolmentValidator(), UserValidator())
1377 by Matt Giuca
database: Added finer-grained enrol permissions on offerings.
444
    role = formencode.All(formencode.validators.OneOf(
445
                                ["lecturer", "tutor", "student"]),
446
                          RoleEnrolmentValidator(),
447
                          formencode.validators.UnicodeString())
1149 by William Grant
Allow tutors and lecturers to enrol people in their offerings.
448
449
1365 by Matt Giuca
Added a new view under Offering/+enrolments to display all staff and students in an offering.
450
class EnrolmentsView(XHTMLView):
451
    """A page which displays all users enrolled in an offering."""
452
    template = 'templates/enrolments.html'
1523 by William Grant
Declare appropriate tabs on the rest of the views.
453
    tab = 'subjects'
1365 by Matt Giuca
Added a new view under Offering/+enrolments to display all staff and students in an offering.
454
    permission = 'edit'
455
456
    def populate(self, req, ctx):
457
        ctx['offering'] = self.context
458
1149 by William Grant
Allow tutors and lecturers to enrol people in their offerings.
459
class EnrolView(XHTMLView):
460
    """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
461
    template = 'templates/enrol.html'
1149 by William Grant
Allow tutors and lecturers to enrol people in their offerings.
462
    tab = 'subjects'
1376 by Matt Giuca
database: More granular permissions on offerings: Added 'enrol' permission.
463
    permission = 'enrol'
1149 by William Grant
Allow tutors and lecturers to enrol people in their offerings.
464
465
    def filter(self, stream, ctx):
466
        return stream | HTMLFormFiller(data=ctx['data'])
467
468
    def populate(self, req, ctx):
469
        if req.method == 'POST':
470
            data = dict(req.get_fieldstorage())
471
            try:
472
                validator = EnrolSchema()
1150 by William Grant
Refuse a +enrol if the user is already enrolled. This stops overwriting
473
                req.offering = self.context # XXX: Getting into state.
1149 by William Grant
Allow tutors and lecturers to enrol people in their offerings.
474
                data = validator.to_python(data, state=req)
1377 by Matt Giuca
database: Added finer-grained enrol permissions on offerings.
475
                self.context.enrol(data['user'], data['role'])
1149 by William Grant
Allow tutors and lecturers to enrol people in their offerings.
476
                req.store.commit()
477
                req.throw_redirect(req.uri)
478
            except formencode.Invalid, e:
479
                errors = e.unpack_errors()
480
        else:
481
            data = {}
482
            errors = {}
483
484
        ctx['data'] = data or {}
485
        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.
486
        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.
487
        ctx['errors'] = errors
488
1165.3.19 by William Grant
Rename SubjectProjectSetView to OfferingProjectsView.
489
class OfferingProjectsView(XHTMLView):
490
    """View the projects for an offering."""
491
    template = 'templates/offering_projects.html'
1165.2.3 by Nick Chadwick
Added a new Admin view, which allows for the administration of projects
492
    permission = 'edit'
1165.3.18 by William Grant
Put the project listing and view in the Subjects tab.
493
    tab = 'subjects'
1165.3.2 by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to
494
1165.2.3 by Nick Chadwick
Added a new Admin view, which allows for the administration of projects
495
    def populate(self, req, ctx):
496
        self.plugin_styles[Plugin] = ["project.css"]
1165.3.2 by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to
497
        self.plugin_scripts[Plugin] = ["project.js"]
1361 by William Grant
Remove the last +projectsets hardcoding.
498
        ctx['req'] = req
1165.2.3 by Nick Chadwick
Added a new Admin view, which allows for the administration of projects
499
        ctx['offering'] = self.context
1165.3.2 by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to
500
        ctx['projectsets'] = []
1361 by William Grant
Remove the last +projectsets hardcoding.
501
        ctx['OfferingRESTView'] = OfferingRESTView
1165.3.2 by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to
502
503
        #Open the projectset Fragment, and render it for inclusion
504
        #into the ProjectSets page
505
        #XXX: This could be a lot cleaner
506
        loader = genshi.template.TemplateLoader(".", auto_reload=True)
507
508
        set_fragment = os.path.join(os.path.dirname(__file__),
509
                "templates/projectset_fragment.html")
510
        project_fragment = os.path.join(os.path.dirname(__file__),
511
                "templates/project_fragment.html")
512
513
        for projectset in self.context.project_sets:
514
            settmpl = loader.load(set_fragment)
515
            setCtx = Context()
1358 by William Grant
Use the publishing framework to generate URLs to projectsets.
516
            setCtx['req'] = req
1165.3.30 by William Grant
Clean out the projectset fragment context.
517
            setCtx['projectset'] = projectset
1165.3.2 by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to
518
            setCtx['projects'] = []
1358 by William Grant
Use the publishing framework to generate URLs to projectsets.
519
            setCtx['GroupsView'] = GroupsView
520
            setCtx['ProjectSetRESTView'] = ProjectSetRESTView
1165.3.2 by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to
521
522
            for project in projectset.projects:
523
                projecttmpl = loader.load(project_fragment)
524
                projectCtx = Context()
1358 by William Grant
Use the publishing framework to generate URLs to projectsets.
525
                projectCtx['req'] = req
1165.3.2 by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to
526
                projectCtx['project'] = project
527
528
                setCtx['projects'].append(
529
                        projecttmpl.generate(projectCtx))
530
531
            ctx['projectsets'].append(settmpl.generate(setCtx))
532
533
534
class ProjectView(XHTMLView):
1165.2.3 by Nick Chadwick
Added a new Admin view, which allows for the administration of projects
535
    """View the submissions for a ProjectSet"""
1165.3.2 by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to
536
    template = "templates/project.html"
1556 by William Grant
Allow tutors to view project submissions.
537
    permission = "view_project_submissions"
1165.3.18 by William Grant
Put the project listing and view in the Subjects tab.
538
    tab = 'subjects'
1165.3.2 by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to
539
1165.3.61 by William Grant
Provide a Subversion command to grab each submission.
540
    def build_subversion_url(self, svnroot, submission):
541
        princ = submission.assessed.principal
542
543
        if isinstance(princ, User):
544
            path = 'users/%s' % princ.login
545
        else:
546
            path = 'groups/%s_%s_%s_%s' % (
547
                    princ.project_set.offering.subject.short_name,
548
                    princ.project_set.offering.semester.year,
549
                    princ.project_set.offering.semester.semester,
550
                    princ.name
551
                    )
552
        return urlparse.urljoin(
553
                    svnroot,
554
                    os.path.join(path, submission.path[1:] if
555
                                       submission.path.startswith(os.sep) else
556
                                       submission.path))
557
1165.3.2 by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to
558
    def populate(self, req, ctx):
1165.3.66 by William Grant
Prettify the submissions table.
559
        self.plugin_styles[Plugin] = ["project.css"]
560
1375.1.4 by William Grant
Indicate when there is nobody assigned to a project, and link to the page to fix that.
561
        ctx['req'] = req
562
        ctx['GroupsView'] = GroupsView
563
        ctx['EnrolView'] = EnrolView
1165.3.14 by William Grant
Improve ProjectView's template substantially.
564
        ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
1165.3.61 by William Grant
Provide a Subversion command to grab each submission.
565
        ctx['build_subversion_url'] = self.build_subversion_url
566
        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
567
        ctx['project'] = self.context
1165.3.61 by William Grant
Provide a Subversion command to grab each submission.
568
        ctx['user'] = req.user
1165.3.2 by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to
569
1099.1.115 by William Grant
Add tabs to the new framework. Move the app icons into the apps themselves.
570
class Plugin(ViewPlugin, MediaPlugin):
1294.2.70 by William Grant
Split out ivle.webapp.admin's routes into annotated functions in ivle.webapp.traversal.
571
    forward_routes = (root_to_subject, subject_to_offering,
572
                      offering_to_project, offering_to_projectset)
573
    reverse_routes = (subject_url, offering_url, projectset_url, project_url)
1294.2.52 by William Grant
Port subject-related views to object traversal.
574
575
    views = [(ApplicationRoot, ('subjects', '+index'), SubjectsView),
1532 by William Grant
Add subject creation/editing UI. Not linked just yet.
576
             (ApplicationRoot, ('subjects', '+new'), SubjectNew),
1537 by William Grant
Add offering creation UI, and allow admins to change the subject or semester of existing offerings.
577
             (ApplicationRoot, ('subjects', '+new-offering'), OfferingNew),
1543 by William Grant
Semester creation UI.
578
             (ApplicationRoot, ('subjects', '+new-semester'), SemesterNew),
1532 by William Grant
Add subject creation/editing UI. Not linked just yet.
579
             (Subject, '+edit', SubjectEdit),
1442.1.2 by William Grant
Add basic (ie. pretty much empty) offering index.
580
             (Offering, '+index', OfferingView),
1451.1.5 by William Grant
Add an OfferingEdit view, for setting the description and URL.
581
             (Offering, '+edit', OfferingEdit),
1365 by Matt Giuca
Added a new view under Offering/+enrolments to display all staff and students in an offering.
582
             (Offering, ('+enrolments', '+index'), EnrolmentsView),
1294.2.52 by William Grant
Port subject-related views to object traversal.
583
             (Offering, ('+enrolments', '+new'), EnrolView),
584
             (Offering, ('+projects', '+index'), OfferingProjectsView),
585
             (Project, '+index', ProjectView),
586
587
             (Offering, ('+projectsets', '+new'), OfferingRESTView, 'api'),
588
             (ProjectSet, ('+projects', '+new'), ProjectSetRESTView, 'api'),
589
             ]
1099.1.115 by William Grant
Add tabs to the new framework. Move the app icons into the apps themselves.
590
1294.2.94 by William Grant
Add a SubjectBreadcrumb.
591
    breadcrumbs = {Subject: SubjectBreadcrumb,
592
                   Offering: OfferingBreadcrumb,
1294.2.96 by William Grant
Add a UserBreadcrumb.
593
                   User: UserBreadcrumb,
1294.2.98 by William Grant
Add a ProjectBreadcrumb.
594
                   Project: ProjectBreadcrumb,
1294.2.89 by William Grant
Add an Offering breadcrumb.
595
                   }
596
1099.1.115 by William Grant
Add tabs to the new framework. Move the app icons into the apps themselves.
597
    tabs = [
1118 by matt.giuca
Rewrote tooltips for the four tabs visible by default.
598
        ('subjects', 'Subjects',
599
         'View subject content and complete worksheets',
600
         'subjects.png', 'subjects', 5)
1099.1.115 by William Grant
Add tabs to the new framework. Move the app icons into the apps themselves.
601
    ]
602
603
    media = 'subject-media'