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

« back to all changes in this revision

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

  • Committer: William Grant
  • Date: 2010-02-11 09:09:53 UTC
  • Revision ID: grantw@unimelb.edu.au-20100211090953-592dk5jruwdg1qrq
Declare appropriate tabs on the rest of the views.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
 
 
25
import os
 
26
import os.path
 
27
import urllib
 
28
import urlparse
 
29
import cgi
 
30
 
 
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
 
36
 
 
37
from ivle.webapp.base.xhtml import XHTMLView
 
38
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
 
43
from ivle import util
 
44
import ivle.date
 
45
 
 
46
from ivle.webapp.admin.projectservice import ProjectSetRESTView
 
47
from ivle.webapp.admin.offeringservice import OfferingRESTView
 
48
from ivle.webapp.admin.publishing import (root_to_subject,
 
49
            subject_to_offering, offering_to_projectset, offering_to_project,
 
50
            subject_url, offering_url, projectset_url, project_url)
 
51
from ivle.webapp.admin.breadcrumbs import (SubjectBreadcrumb,
 
52
            OfferingBreadcrumb, UserBreadcrumb, ProjectBreadcrumb)
 
53
from ivle.webapp.groups import GroupsView
 
54
from ivle.webapp.tutorial import Plugin as TutorialPlugin
 
55
 
 
56
class SubjectsView(XHTMLView):
 
57
    '''The view of the list of subjects.'''
 
58
    template = 'templates/subjects.html'
 
59
    tab = 'subjects'
 
60
 
 
61
    def authorize(self, req):
 
62
        return req.user is not None
 
63
 
 
64
    def populate(self, req, ctx):
 
65
        ctx['user'] = req.user
 
66
        ctx['semesters'] = []
 
67
        for semester in req.store.find(Semester).order_by(Desc(Semester.year),
 
68
                                                     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
 
156
 
 
157
 
 
158
class UserValidator(formencode.FancyValidator):
 
159
    """A FormEncode validator that turns a username into a user.
 
160
 
 
161
    The state must have a 'store' attribute, which is the Storm store
 
162
    to use."""
 
163
    def _to_python(self, value, state):
 
164
        user = User.get_by_login(state.store, value)
 
165
        if user:
 
166
            return user
 
167
        else:
 
168
            raise formencode.Invalid('User does not exist', value, state)
 
169
 
 
170
 
 
171
class NoEnrolmentValidator(formencode.FancyValidator):
 
172
    """A FormEncode validator that ensures absence of an enrolment.
 
173
 
 
174
    The state must have an 'offering' attribute.
 
175
    """
 
176
    def _to_python(self, value, state):
 
177
        if state.offering.get_enrolment(value):
 
178
            raise formencode.Invalid('User already enrolled', value, state)
 
179
        return value
 
180
 
 
181
 
 
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
class EnrolSchema(formencode.Schema):
 
196
    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
 
211
 
 
212
class EnrolView(XHTMLView):
 
213
    """A form to enrol a user in an offering."""
 
214
    template = 'templates/enrol.html'
 
215
    tab = 'subjects'
 
216
    permission = 'enrol'
 
217
 
 
218
    def filter(self, stream, ctx):
 
219
        return stream | HTMLFormFiller(data=ctx['data'])
 
220
 
 
221
    def populate(self, req, ctx):
 
222
        if req.method == 'POST':
 
223
            data = dict(req.get_fieldstorage())
 
224
            try:
 
225
                validator = EnrolSchema()
 
226
                req.offering = self.context # XXX: Getting into state.
 
227
                data = validator.to_python(data, state=req)
 
228
                self.context.enrol(data['user'], data['role'])
 
229
                req.store.commit()
 
230
                req.throw_redirect(req.uri)
 
231
            except formencode.Invalid, e:
 
232
                errors = e.unpack_errors()
 
233
        else:
 
234
            data = {}
 
235
            errors = {}
 
236
 
 
237
        ctx['data'] = data or {}
 
238
        ctx['offering'] = self.context
 
239
        ctx['roles_auth'] = self.context.get_permissions(req.user)
 
240
        ctx['errors'] = errors
 
241
 
 
242
class OfferingProjectsView(XHTMLView):
 
243
    """View the projects for an offering."""
 
244
    template = 'templates/offering_projects.html'
 
245
    permission = 'edit'
 
246
    tab = 'subjects'
 
247
 
 
248
    def populate(self, req, ctx):
 
249
        self.plugin_styles[Plugin] = ["project.css"]
 
250
        self.plugin_scripts[Plugin] = ["project.js"]
 
251
        ctx['req'] = req
 
252
        ctx['offering'] = self.context
 
253
        ctx['projectsets'] = []
 
254
        ctx['OfferingRESTView'] = OfferingRESTView
 
255
 
 
256
        #Open the projectset Fragment, and render it for inclusion
 
257
        #into the ProjectSets page
 
258
        #XXX: This could be a lot cleaner
 
259
        loader = genshi.template.TemplateLoader(".", auto_reload=True)
 
260
 
 
261
        set_fragment = os.path.join(os.path.dirname(__file__),
 
262
                "templates/projectset_fragment.html")
 
263
        project_fragment = os.path.join(os.path.dirname(__file__),
 
264
                "templates/project_fragment.html")
 
265
 
 
266
        for projectset in self.context.project_sets:
 
267
            settmpl = loader.load(set_fragment)
 
268
            setCtx = Context()
 
269
            setCtx['req'] = req
 
270
            setCtx['projectset'] = projectset
 
271
            setCtx['projects'] = []
 
272
            setCtx['GroupsView'] = GroupsView
 
273
            setCtx['ProjectSetRESTView'] = ProjectSetRESTView
 
274
 
 
275
            for project in projectset.projects:
 
276
                projecttmpl = loader.load(project_fragment)
 
277
                projectCtx = Context()
 
278
                projectCtx['req'] = req
 
279
                projectCtx['project'] = project
 
280
 
 
281
                setCtx['projects'].append(
 
282
                        projecttmpl.generate(projectCtx))
 
283
 
 
284
            ctx['projectsets'].append(settmpl.generate(setCtx))
 
285
 
 
286
 
 
287
class ProjectView(XHTMLView):
 
288
    """View the submissions for a ProjectSet"""
 
289
    template = "templates/project.html"
 
290
    permission = "edit"
 
291
    tab = 'subjects'
 
292
 
 
293
    def build_subversion_url(self, svnroot, submission):
 
294
        princ = submission.assessed.principal
 
295
 
 
296
        if isinstance(princ, User):
 
297
            path = 'users/%s' % princ.login
 
298
        else:
 
299
            path = 'groups/%s_%s_%s_%s' % (
 
300
                    princ.project_set.offering.subject.short_name,
 
301
                    princ.project_set.offering.semester.year,
 
302
                    princ.project_set.offering.semester.semester,
 
303
                    princ.name
 
304
                    )
 
305
        return urlparse.urljoin(
 
306
                    svnroot,
 
307
                    os.path.join(path, submission.path[1:] if
 
308
                                       submission.path.startswith(os.sep) else
 
309
                                       submission.path))
 
310
 
 
311
    def populate(self, req, ctx):
 
312
        self.plugin_styles[Plugin] = ["project.css"]
 
313
 
 
314
        ctx['req'] = req
 
315
        ctx['GroupsView'] = GroupsView
 
316
        ctx['EnrolView'] = EnrolView
 
317
        ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
 
318
        ctx['build_subversion_url'] = self.build_subversion_url
 
319
        ctx['svn_addr'] = req.config['urls']['svn_addr']
 
320
        ctx['project'] = self.context
 
321
        ctx['user'] = req.user
 
322
 
 
323
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
                   }
 
345
 
 
346
    tabs = [
 
347
        ('subjects', 'Subjects',
 
348
         'View subject content and complete worksheets',
 
349
         'subjects.png', 'subjects', 5)
 
350
    ]
 
351
 
 
352
    media = 'subject-media'