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

« back to all changes in this revision

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

  • Committer: William Grant
  • Date: 2009-02-27 05:07:09 UTC
  • Revision ID: grantw@unimelb.edu.au-20090227050709-k16kvhyl50nzjbwm
Subject URLs now contain the short name (eg. info1) rather than the code
(eg. 600151).

Show diffs side-by-side

added added

removed removed

Lines of Context:
23
23
# A sample / testing application for IVLE.
24
24
 
25
25
import os
26
 
import os.path
27
26
import urllib
28
 
import urlparse
29
27
import cgi
30
28
 
31
 
from storm.locals import Desc, Store
32
 
import genshi
33
 
from genshi.filters import HTMLFormFiller
34
 
from genshi.template import Context, TemplateLoader
35
 
import formencode
 
29
from storm.locals import Desc
36
30
 
37
31
from ivle.webapp.base.xhtml import XHTMLView
38
32
from ivle.webapp.base.plugins import ViewPlugin, MediaPlugin
39
 
from ivle.webapp import ApplicationRoot
40
 
 
41
 
from ivle.database import Subject, Semester, Offering, Enrolment, User,\
42
 
                          ProjectSet, Project, ProjectSubmission
 
33
from ivle.webapp.errors import NotFound
 
34
from ivle.database import Subject, Semester
43
35
from ivle import util
44
 
import ivle.date
45
36
 
46
 
from ivle.webapp.admin.projectservice import ProjectSetRESTView
47
 
from ivle.webapp.admin.offeringservice import OfferingRESTView
48
 
from ivle.webapp.admin.publishing import (root_to_subject,
49
 
            subject_to_offering, offering_to_projectset, offering_to_project,
50
 
            subject_url, offering_url, projectset_url, project_url)
51
 
from ivle.webapp.admin.breadcrumbs import (SubjectBreadcrumb,
52
 
            OfferingBreadcrumb, UserBreadcrumb, ProjectBreadcrumb)
53
 
from ivle.webapp.groups import GroupsView
54
 
from ivle.webapp.tutorial import Plugin as TutorialPlugin
55
37
 
56
38
class SubjectsView(XHTMLView):
57
39
    '''The view of the list of subjects.'''
58
 
    template = 'templates/subjects.html'
 
40
    template = 'subjects.html'
59
41
    tab = 'subjects'
60
42
 
61
43
    def authorize(self, req):
62
 
        return req.user is not None
 
44
        if req.user is None:
 
45
            return False
 
46
        return req.user.enrolments.count() > 0
63
47
 
64
48
    def populate(self, req, ctx):
65
 
        ctx['user'] = req.user
66
49
        ctx['semesters'] = []
67
50
        for semester in req.store.find(Semester).order_by(Desc(Semester.year),
68
51
                                                     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
 
    permission = 'edit'
128
 
 
129
 
    def filter(self, stream, ctx):
130
 
        return stream | HTMLFormFiller(data=ctx['data'])
131
 
 
132
 
    def populate(self, req, ctx):
133
 
        if req.method == 'POST':
134
 
            data = dict(req.get_fieldstorage())
135
 
            try:
136
 
                validator = OfferingSchema()
137
 
                data = validator.to_python(data, state=req)
138
 
 
139
 
                self.context.url = unicode(data['url']) if data['url'] else None
140
 
                self.context.description = data['description']
141
 
                req.store.commit()
142
 
                req.throw_redirect(req.publisher.generate(self.context))
143
 
            except formencode.Invalid, e:
144
 
                errors = e.unpack_errors()
145
 
        else:
146
 
            data = {
147
 
                'url': self.context.url,
148
 
                'description': self.context.description,
149
 
            }
150
 
            errors = {}
151
 
 
152
 
        ctx['data'] = data or {}
153
 
        ctx['context'] = self.context
154
 
        ctx['errors'] = errors
155
 
 
156
 
 
157
 
class UserValidator(formencode.FancyValidator):
158
 
    """A FormEncode validator that turns a username into a user.
159
 
 
160
 
    The state must have a 'store' attribute, which is the Storm store
161
 
    to use."""
162
 
    def _to_python(self, value, state):
163
 
        user = User.get_by_login(state.store, value)
164
 
        if user:
165
 
            return user
166
 
        else:
167
 
            raise formencode.Invalid('User does not exist', value, state)
168
 
 
169
 
 
170
 
class NoEnrolmentValidator(formencode.FancyValidator):
171
 
    """A FormEncode validator that ensures absence of an enrolment.
172
 
 
173
 
    The state must have an 'offering' attribute.
174
 
    """
175
 
    def _to_python(self, value, state):
176
 
        if state.offering.get_enrolment(value):
177
 
            raise formencode.Invalid('User already enrolled', value, state)
178
 
        return value
179
 
 
180
 
 
181
 
class RoleEnrolmentValidator(formencode.FancyValidator):
182
 
    """A FormEncode validator that checks permission to enrol users with a
183
 
    particular role.
184
 
 
185
 
    The state must have an 'offering' attribute.
186
 
    """
187
 
    def _to_python(self, value, state):
188
 
        if ("enrol_" + value) not in state.offering.get_permissions(state.user):
189
 
            raise formencode.Invalid('Not allowed to assign users that role',
190
 
                                     value, state)
191
 
        return value
192
 
 
193
 
 
194
 
class EnrolSchema(formencode.Schema):
195
 
    user = formencode.All(NoEnrolmentValidator(), UserValidator())
196
 
    role = formencode.All(formencode.validators.OneOf(
197
 
                                ["lecturer", "tutor", "student"]),
198
 
                          RoleEnrolmentValidator(),
199
 
                          formencode.validators.UnicodeString())
200
 
 
201
 
 
202
 
class EnrolmentsView(XHTMLView):
203
 
    """A page which displays all users enrolled in an offering."""
204
 
    template = 'templates/enrolments.html'
205
 
    permission = 'edit'
206
 
 
207
 
    def populate(self, req, ctx):
208
 
        ctx['offering'] = self.context
209
 
 
210
 
class EnrolView(XHTMLView):
211
 
    """A form to enrol a user in an offering."""
212
 
    template = 'templates/enrol.html'
213
 
    tab = 'subjects'
214
 
    permission = 'enrol'
215
 
 
216
 
    def filter(self, stream, ctx):
217
 
        return stream | HTMLFormFiller(data=ctx['data'])
218
 
 
219
 
    def populate(self, req, ctx):
220
 
        if req.method == 'POST':
221
 
            data = dict(req.get_fieldstorage())
222
 
            try:
223
 
                validator = EnrolSchema()
224
 
                req.offering = self.context # XXX: Getting into state.
225
 
                data = validator.to_python(data, state=req)
226
 
                self.context.enrol(data['user'], data['role'])
227
 
                req.store.commit()
228
 
                req.throw_redirect(req.uri)
229
 
            except formencode.Invalid, e:
230
 
                errors = e.unpack_errors()
231
 
        else:
232
 
            data = {}
233
 
            errors = {}
234
 
 
235
 
        ctx['data'] = data or {}
236
 
        ctx['offering'] = self.context
237
 
        ctx['roles_auth'] = self.context.get_permissions(req.user)
238
 
        ctx['errors'] = errors
239
 
 
240
 
class OfferingProjectsView(XHTMLView):
241
 
    """View the projects for an offering."""
242
 
    template = 'templates/offering_projects.html'
243
 
    permission = 'edit'
244
 
    tab = 'subjects'
245
 
 
246
 
    def populate(self, req, ctx):
247
 
        self.plugin_styles[Plugin] = ["project.css"]
248
 
        self.plugin_scripts[Plugin] = ["project.js"]
249
 
        ctx['req'] = req
250
 
        ctx['offering'] = self.context
251
 
        ctx['projectsets'] = []
252
 
        ctx['OfferingRESTView'] = OfferingRESTView
253
 
 
254
 
        #Open the projectset Fragment, and render it for inclusion
255
 
        #into the ProjectSets page
256
 
        #XXX: This could be a lot cleaner
257
 
        loader = genshi.template.TemplateLoader(".", auto_reload=True)
258
 
 
259
 
        set_fragment = os.path.join(os.path.dirname(__file__),
260
 
                "templates/projectset_fragment.html")
261
 
        project_fragment = os.path.join(os.path.dirname(__file__),
262
 
                "templates/project_fragment.html")
263
 
 
264
 
        for projectset in self.context.project_sets:
265
 
            settmpl = loader.load(set_fragment)
266
 
            setCtx = Context()
267
 
            setCtx['req'] = req
268
 
            setCtx['projectset'] = projectset
269
 
            setCtx['projects'] = []
270
 
            setCtx['GroupsView'] = GroupsView
271
 
            setCtx['ProjectSetRESTView'] = ProjectSetRESTView
272
 
 
273
 
            for project in projectset.projects:
274
 
                projecttmpl = loader.load(project_fragment)
275
 
                projectCtx = Context()
276
 
                projectCtx['req'] = req
277
 
                projectCtx['project'] = project
278
 
 
279
 
                setCtx['projects'].append(
280
 
                        projecttmpl.generate(projectCtx))
281
 
 
282
 
            ctx['projectsets'].append(settmpl.generate(setCtx))
283
 
 
284
 
 
285
 
class ProjectView(XHTMLView):
286
 
    """View the submissions for a ProjectSet"""
287
 
    template = "templates/project.html"
288
 
    permission = "edit"
289
 
    tab = 'subjects'
290
 
 
291
 
    def build_subversion_url(self, svnroot, submission):
292
 
        princ = submission.assessed.principal
293
 
 
294
 
        if isinstance(princ, User):
295
 
            path = 'users/%s' % princ.login
296
 
        else:
297
 
            path = 'groups/%s_%s_%s_%s' % (
298
 
                    princ.project_set.offering.subject.short_name,
299
 
                    princ.project_set.offering.semester.year,
300
 
                    princ.project_set.offering.semester.semester,
301
 
                    princ.name
302
 
                    )
303
 
        return urlparse.urljoin(
304
 
                    svnroot,
305
 
                    os.path.join(path, submission.path[1:] if
306
 
                                       submission.path.startswith(os.sep) else
307
 
                                       submission.path))
308
 
 
309
 
    def populate(self, req, ctx):
310
 
        self.plugin_styles[Plugin] = ["project.css"]
311
 
 
312
 
        ctx['req'] = req
313
 
        ctx['GroupsView'] = GroupsView
314
 
        ctx['EnrolView'] = EnrolView
315
 
        ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
316
 
        ctx['build_subversion_url'] = self.build_subversion_url
317
 
        ctx['svn_addr'] = req.config['urls']['svn_addr']
318
 
        ctx['project'] = self.context
319
 
        ctx['user'] = req.user
 
52
            enrolments = semester.enrolments.find(user=req.user)
 
53
            if enrolments.count():
 
54
                ctx['semesters'].append((semester, enrolments))
320
55
 
321
56
class Plugin(ViewPlugin, MediaPlugin):
322
 
    forward_routes = (root_to_subject, subject_to_offering,
323
 
                      offering_to_project, offering_to_projectset)
324
 
    reverse_routes = (subject_url, offering_url, projectset_url, project_url)
325
 
 
326
 
    views = [(ApplicationRoot, ('subjects', '+index'), SubjectsView),
327
 
             (Offering, '+index', OfferingView),
328
 
             (Offering, '+edit', OfferingEdit),
329
 
             (Offering, ('+enrolments', '+index'), EnrolmentsView),
330
 
             (Offering, ('+enrolments', '+new'), EnrolView),
331
 
             (Offering, ('+projects', '+index'), OfferingProjectsView),
332
 
             (Project, '+index', ProjectView),
333
 
 
334
 
             (Offering, ('+projectsets', '+new'), OfferingRESTView, 'api'),
335
 
             (ProjectSet, ('+projects', '+new'), ProjectSetRESTView, 'api'),
336
 
             ]
337
 
 
338
 
    breadcrumbs = {Subject: SubjectBreadcrumb,
339
 
                   Offering: OfferingBreadcrumb,
340
 
                   User: UserBreadcrumb,
341
 
                   Project: ProjectBreadcrumb,
342
 
                   }
 
57
    urls = [
 
58
        ('subjects/', SubjectsView),
 
59
    ]
343
60
 
344
61
    tabs = [
345
62
        ('subjects', 'Subjects',