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

« back to all changes in this revision

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

  • Committer: William Grant
  • Date: 2009-05-26 02:02:56 UTC
  • Revision ID: grantw@unimelb.edu.au-20090526020256-py3q01f6l7f6nwbu
Add some doctests to ivle.date.

Show diffs side-by-side

added added

removed removed

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