~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-12-07 11:01:50 UTC
  • mfrom: (1341.1.3 trunk)
  • Revision ID: grantw@unimelb.edu.au-20091207110150-jct30a9yru432ddn
Fix up the project set creation UI a bit.

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
 
 
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
 
64
            enrolments = semester.enrolments.find(user=req.user)
 
65
            if enrolments.count():
 
66
                ctx['semesters'].append((semester, enrolments))
187
67
 
188
68
 
189
69
class UserValidator(formencode.FancyValidator):
210
90
        return value
211
91
 
212
92
 
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
 
 
226
93
class EnrolSchema(formencode.Schema):
227
94
    user = formencode.All(NoEnrolmentValidator(), UserValidator())
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
 
95
 
241
96
 
242
97
class EnrolView(XHTMLView):
243
98
    """A form to enrol a user in an offering."""
244
99
    template = 'templates/enrol.html'
245
100
    tab = 'subjects'
246
 
    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()
247
114
 
248
115
    def filter(self, stream, ctx):
249
116
        return stream | HTMLFormFiller(data=ctx['data'])
255
122
                validator = EnrolSchema()
256
123
                req.offering = self.context # XXX: Getting into state.
257
124
                data = validator.to_python(data, state=req)
258
 
                self.context.enrol(data['user'], data['role'])
 
125
                self.context.enrol(data['user'])
259
126
                req.store.commit()
260
127
                req.throw_redirect(req.uri)
261
128
            except formencode.Invalid, e:
266
133
 
267
134
        ctx['data'] = data or {}
268
135
        ctx['offering'] = self.context
269
 
        ctx['roles_auth'] = self.context.get_permissions(req.user)
270
136
        ctx['errors'] = errors
271
137
 
272
138
class OfferingProjectsView(XHTMLView):
274
140
    template = 'templates/offering_projects.html'
275
141
    permission = 'edit'
276
142
    tab = 'subjects'
277
 
 
 
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
    
278
169
    def populate(self, req, ctx):
279
170
        self.plugin_styles[Plugin] = ["project.css"]
280
171
        self.plugin_scripts[Plugin] = ["project.js"]
281
 
        ctx['req'] = req
282
172
        ctx['offering'] = self.context
283
173
        ctx['projectsets'] = []
284
 
        ctx['OfferingRESTView'] = OfferingRESTView
285
174
 
286
175
        #Open the projectset Fragment, and render it for inclusion
287
176
        #into the ProjectSets page
296
185
        for projectset in self.context.project_sets:
297
186
            settmpl = loader.load(set_fragment)
298
187
            setCtx = Context()
299
 
            setCtx['req'] = req
300
188
            setCtx['projectset'] = projectset
 
189
            setCtx['new_project_url'] = self.new_project_url(projectset)
301
190
            setCtx['projects'] = []
302
 
            setCtx['GroupsView'] = GroupsView
303
 
            setCtx['ProjectSetRESTView'] = ProjectSetRESTView
304
191
 
305
192
            for project in projectset.projects:
306
193
                projecttmpl = loader.load(project_fragment)
307
194
                projectCtx = Context()
308
 
                projectCtx['req'] = req
309
195
                projectCtx['project'] = project
 
196
                projectCtx['project_url'] = self.project_url(projectset, project)
310
197
 
311
198
                setCtx['projects'].append(
312
199
                        projecttmpl.generate(projectCtx))
320
207
    permission = "edit"
321
208
    tab = 'subjects'
322
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
 
323
223
    def build_subversion_url(self, svnroot, submission):
324
224
        princ = submission.assessed.principal
325
225
 
341
241
    def populate(self, req, ctx):
342
242
        self.plugin_styles[Plugin] = ["project.css"]
343
243
 
344
 
        ctx['req'] = req
345
 
        ctx['GroupsView'] = GroupsView
346
 
        ctx['EnrolView'] = EnrolView
347
244
        ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
348
245
        ctx['build_subversion_url'] = self.build_subversion_url
349
246
        ctx['svn_addr'] = req.config['urls']['svn_addr']
351
248
        ctx['user'] = req.user
352
249
 
353
250
class Plugin(ViewPlugin, MediaPlugin):
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
 
                   }
 
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
    ]
375
265
 
376
266
    tabs = [
377
267
        ('subjects', 'Subjects',