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

« back to all changes in this revision

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

  • Committer: Matt Giuca
  • Date: 2010-02-11 05:54:45 UTC
  • Revision ID: matt.giuca@gmail.com-20100211055445-151qrs4xczzl5rns
Docs: Completed Tour of IVLE (finished Admin section). Apologies for the mess on the previous commit -- committed an unfinished document.

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
 
31
from storm.locals import Desc, Store
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.errors import NotFound
 
39
from ivle.webapp import ApplicationRoot
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,\
47
 
                                             ProjectRESTView
 
46
from ivle.webapp.admin.projectservice import ProjectSetRESTView
48
47
from ivle.webapp.admin.offeringservice import OfferingRESTView
49
 
 
 
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
50
55
 
51
56
class SubjectsView(XHTMLView):
52
57
    '''The view of the list of subjects.'''
61
66
        ctx['semesters'] = []
62
67
        for semester in req.store.find(Semester).order_by(Desc(Semester.year),
63
68
                                                     Desc(Semester.semester)):
64
 
            enrolments = semester.enrolments.find(user=req.user)
65
 
            if enrolments.count():
66
 
                ctx['semesters'].append((semester, enrolments))
 
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
 
 
79
def format_submission_principal(user, principal):
 
80
    """Render a list of users to fit in the offering project listing.
 
81
 
 
82
    Given a user and a list of submitters, returns 'solo' if the
 
83
    only submitter is the user, or a string of the form
 
84
    'with A, B and C' if there are any other submitters.
 
85
 
 
86
    If submitters is None, we assume that the list of members could
 
87
    not be determined, so we just return 'group'.
 
88
    """
 
89
    if principal is None:
 
90
        return 'group'
 
91
 
 
92
    if principal is user:
 
93
        return 'solo'
 
94
 
 
95
    display_names = sorted(
 
96
        member.display_name for member in principal.members
 
97
        if member is not user)
 
98
 
 
99
    if len(display_names) == 0:
 
100
        return 'solo (%s)' % principal.name
 
101
    elif len(display_names) == 1:
 
102
        return 'with %s (%s)' % (display_names[0], principal.name)
 
103
    elif len(display_names) > 5:
 
104
        return 'with %d others (%s)' % (len(display_names), principal.name)
 
105
    else:
 
106
        return 'with %s and %s (%s)' % (', '.join(display_names[:-1]),
 
107
                                        display_names[-1], principal.name)
 
108
 
 
109
 
 
110
class OfferingView(XHTMLView):
 
111
    """The home page of an offering."""
 
112
    template = 'templates/offering.html'
 
113
    tab = 'subjects'
 
114
    permission = 'view'
 
115
 
 
116
    def populate(self, req, ctx):
 
117
        # Need the worksheet result styles.
 
118
        self.plugin_styles[TutorialPlugin] = ['tutorial.css']
 
119
        ctx['context'] = self.context
 
120
        ctx['req'] = req
 
121
        ctx['permissions'] = self.context.get_permissions(req.user)
 
122
        ctx['format_submission_principal'] = format_submission_principal
 
123
        ctx['format_datetime'] = ivle.date.make_date_nice
 
124
        ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
 
125
        ctx['OfferingEdit'] = OfferingEdit
 
126
 
 
127
        # As we go, calculate the total score for this subject
 
128
        # (Assessable worksheets only, mandatory problems only)
 
129
 
 
130
        ctx['worksheets'], problems_total, problems_done = (
 
131
            ivle.worksheet.utils.create_list_of_fake_worksheets_and_stats(
 
132
                req.store, req.user, self.context))
 
133
 
 
134
        ctx['exercises_total'] = problems_total
 
135
        ctx['exercises_done'] = problems_done
 
136
        if problems_total > 0:
 
137
            if problems_done >= problems_total:
 
138
                ctx['worksheets_complete_class'] = "complete"
 
139
            elif problems_done > 0:
 
140
                ctx['worksheets_complete_class'] = "semicomplete"
 
141
            else:
 
142
                ctx['worksheets_complete_class'] = "incomplete"
 
143
            # Calculate the final percentage and mark for the subject
 
144
            (ctx['exercises_pct'], ctx['worksheet_mark'],
 
145
             ctx['worksheet_max_mark']) = (
 
146
                ivle.worksheet.utils.calculate_mark(
 
147
                    problems_done, problems_total))
 
148
 
 
149
 
 
150
class OfferingSchema(formencode.Schema):
 
151
    description = formencode.validators.UnicodeString(
 
152
        if_missing=None, not_empty=False)
 
153
    url = formencode.validators.URL(if_missing=None, not_empty=False)
 
154
 
 
155
 
 
156
class OfferingEdit(XHTMLView):
 
157
    """A form to edit an offering's details."""
 
158
    template = 'templates/offering-edit.html'
 
159
    permission = 'edit'
 
160
 
 
161
    def filter(self, stream, ctx):
 
162
        return stream | HTMLFormFiller(data=ctx['data'])
 
163
 
 
164
    def populate(self, req, ctx):
 
165
        if req.method == 'POST':
 
166
            data = dict(req.get_fieldstorage())
 
167
            try:
 
168
                validator = OfferingSchema()
 
169
                data = validator.to_python(data, state=req)
 
170
 
 
171
                self.context.url = unicode(data['url']) if data['url'] else None
 
172
                self.context.description = data['description']
 
173
                req.store.commit()
 
174
                req.throw_redirect(req.publisher.generate(self.context))
 
175
            except formencode.Invalid, e:
 
176
                errors = e.unpack_errors()
 
177
        else:
 
178
            data = {
 
179
                'url': self.context.url,
 
180
                'description': self.context.description,
 
181
            }
 
182
            errors = {}
 
183
 
 
184
        ctx['data'] = data or {}
 
185
        ctx['context'] = self.context
 
186
        ctx['errors'] = errors
67
187
 
68
188
 
69
189
class UserValidator(formencode.FancyValidator):
90
210
        return value
91
211
 
92
212
 
 
213
class RoleEnrolmentValidator(formencode.FancyValidator):
 
214
    """A FormEncode validator that checks permission to enrol users with a
 
215
    particular role.
 
216
 
 
217
    The state must have an 'offering' attribute.
 
218
    """
 
219
    def _to_python(self, value, state):
 
220
        if ("enrol_" + value) not in state.offering.get_permissions(state.user):
 
221
            raise formencode.Invalid('Not allowed to assign users that role',
 
222
                                     value, state)
 
223
        return value
 
224
 
 
225
 
93
226
class EnrolSchema(formencode.Schema):
94
227
    user = formencode.All(NoEnrolmentValidator(), UserValidator())
95
 
 
 
228
    role = formencode.All(formencode.validators.OneOf(
 
229
                                ["lecturer", "tutor", "student"]),
 
230
                          RoleEnrolmentValidator(),
 
231
                          formencode.validators.UnicodeString())
 
232
 
 
233
 
 
234
class EnrolmentsView(XHTMLView):
 
235
    """A page which displays all users enrolled in an offering."""
 
236
    template = 'templates/enrolments.html'
 
237
    permission = 'edit'
 
238
 
 
239
    def populate(self, req, ctx):
 
240
        ctx['offering'] = self.context
96
241
 
97
242
class EnrolView(XHTMLView):
98
243
    """A form to enrol a user in an offering."""
99
244
    template = 'templates/enrol.html'
100
245
    tab = 'subjects'
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()
 
246
    permission = 'enrol'
114
247
 
115
248
    def filter(self, stream, ctx):
116
249
        return stream | HTMLFormFiller(data=ctx['data'])
122
255
                validator = EnrolSchema()
123
256
                req.offering = self.context # XXX: Getting into state.
124
257
                data = validator.to_python(data, state=req)
125
 
                self.context.enrol(data['user'])
 
258
                self.context.enrol(data['user'], data['role'])
126
259
                req.store.commit()
127
260
                req.throw_redirect(req.uri)
128
261
            except formencode.Invalid, e:
133
266
 
134
267
        ctx['data'] = data or {}
135
268
        ctx['offering'] = self.context
 
269
        ctx['roles_auth'] = self.context.get_permissions(req.user)
136
270
        ctx['errors'] = errors
137
271
 
138
272
class OfferingProjectsView(XHTMLView):
140
274
    template = 'templates/offering_projects.html'
141
275
    permission = 'edit'
142
276
    tab = 'subjects'
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
 
    
 
277
 
169
278
    def populate(self, req, ctx):
170
279
        self.plugin_styles[Plugin] = ["project.css"]
171
280
        self.plugin_scripts[Plugin] = ["project.js"]
 
281
        ctx['req'] = req
172
282
        ctx['offering'] = self.context
173
283
        ctx['projectsets'] = []
 
284
        ctx['OfferingRESTView'] = OfferingRESTView
174
285
 
175
286
        #Open the projectset Fragment, and render it for inclusion
176
287
        #into the ProjectSets page
185
296
        for projectset in self.context.project_sets:
186
297
            settmpl = loader.load(set_fragment)
187
298
            setCtx = Context()
 
299
            setCtx['req'] = req
188
300
            setCtx['projectset'] = projectset
189
 
            setCtx['new_project_url'] = self.new_project_url(projectset)
190
301
            setCtx['projects'] = []
 
302
            setCtx['GroupsView'] = GroupsView
 
303
            setCtx['ProjectSetRESTView'] = ProjectSetRESTView
191
304
 
192
305
            for project in projectset.projects:
193
306
                projecttmpl = loader.load(project_fragment)
194
307
                projectCtx = Context()
 
308
                projectCtx['req'] = req
195
309
                projectCtx['project'] = project
196
 
                projectCtx['project_url'] = self.project_url(projectset, project)
197
310
 
198
311
                setCtx['projects'].append(
199
312
                        projecttmpl.generate(projectCtx))
207
320
    permission = "edit"
208
321
    tab = 'subjects'
209
322
 
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
 
 
223
323
    def build_subversion_url(self, svnroot, submission):
224
324
        princ = submission.assessed.principal
225
325
 
241
341
    def populate(self, req, ctx):
242
342
        self.plugin_styles[Plugin] = ["project.css"]
243
343
 
 
344
        ctx['req'] = req
 
345
        ctx['GroupsView'] = GroupsView
 
346
        ctx['EnrolView'] = EnrolView
244
347
        ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
245
348
        ctx['build_subversion_url'] = self.build_subversion_url
246
349
        ctx['svn_addr'] = req.config['urls']['svn_addr']
248
351
        ctx['user'] = req.user
249
352
 
250
353
class Plugin(ViewPlugin, MediaPlugin):
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
 
    ]
 
354
    forward_routes = (root_to_subject, subject_to_offering,
 
355
                      offering_to_project, offering_to_projectset)
 
356
    reverse_routes = (subject_url, offering_url, projectset_url, project_url)
 
357
 
 
358
    views = [(ApplicationRoot, ('subjects', '+index'), SubjectsView),
 
359
             (Offering, '+index', OfferingView),
 
360
             (Offering, '+edit', OfferingEdit),
 
361
             (Offering, ('+enrolments', '+index'), EnrolmentsView),
 
362
             (Offering, ('+enrolments', '+new'), EnrolView),
 
363
             (Offering, ('+projects', '+index'), OfferingProjectsView),
 
364
             (Project, '+index', ProjectView),
 
365
 
 
366
             (Offering, ('+projectsets', '+new'), OfferingRESTView, 'api'),
 
367
             (ProjectSet, ('+projects', '+new'), ProjectSetRESTView, 'api'),
 
368
             ]
 
369
 
 
370
    breadcrumbs = {Subject: SubjectBreadcrumb,
 
371
                   Offering: OfferingBreadcrumb,
 
372
                   User: UserBreadcrumb,
 
373
                   Project: ProjectBreadcrumb,
 
374
                   }
265
375
 
266
376
    tabs = [
267
377
        ('subjects', 'Subjects',