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

« back to all changes in this revision

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

Remove the setup.configure import from setup.

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
36
 
 
37
29
from ivle.webapp.base.xhtml import XHTMLView
38
30
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
 
31
from ivle.webapp.errors import NotFound
 
32
from ivle.database import Subject
43
33
from ivle import util
44
 
import ivle.date
45
34
 
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
35
 
56
36
class SubjectsView(XHTMLView):
57
37
    '''The view of the list of subjects.'''
58
 
    template = 'templates/subjects.html'
59
 
    tab = 'subjects'
 
38
    template = 'subjects.html'
 
39
    appname = 'subjects' # XXX
60
40
 
61
41
    def authorize(self, req):
62
42
        return req.user is not None
63
43
 
64
44
    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
 
 
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
187
 
 
188
 
 
189
 
class UserValidator(formencode.FancyValidator):
190
 
    """A FormEncode validator that turns a username into a user.
191
 
 
192
 
    The state must have a 'store' attribute, which is the Storm store
193
 
    to use."""
194
 
    def _to_python(self, value, state):
195
 
        user = User.get_by_login(state.store, value)
196
 
        if user:
197
 
            return user
198
 
        else:
199
 
            raise formencode.Invalid('User does not exist', value, state)
200
 
 
201
 
 
202
 
class NoEnrolmentValidator(formencode.FancyValidator):
203
 
    """A FormEncode validator that ensures absence of an enrolment.
204
 
 
205
 
    The state must have an 'offering' attribute.
206
 
    """
207
 
    def _to_python(self, value, state):
208
 
        if state.offering.get_enrolment(value):
209
 
            raise formencode.Invalid('User already enrolled', value, state)
210
 
        return value
211
 
 
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
 
 
226
 
class EnrolSchema(formencode.Schema):
227
 
    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
241
 
 
242
 
class EnrolView(XHTMLView):
243
 
    """A form to enrol a user in an offering."""
244
 
    template = 'templates/enrol.html'
245
 
    tab = 'subjects'
246
 
    permission = 'enrol'
247
 
 
248
 
    def filter(self, stream, ctx):
249
 
        return stream | HTMLFormFiller(data=ctx['data'])
250
 
 
251
 
    def populate(self, req, ctx):
252
 
        if req.method == 'POST':
253
 
            data = dict(req.get_fieldstorage())
254
 
            try:
255
 
                validator = EnrolSchema()
256
 
                req.offering = self.context # XXX: Getting into state.
257
 
                data = validator.to_python(data, state=req)
258
 
                self.context.enrol(data['user'], data['role'])
259
 
                req.store.commit()
260
 
                req.throw_redirect(req.uri)
261
 
            except formencode.Invalid, e:
262
 
                errors = e.unpack_errors()
263
 
        else:
264
 
            data = {}
265
 
            errors = {}
266
 
 
267
 
        ctx['data'] = data or {}
268
 
        ctx['offering'] = self.context
269
 
        ctx['roles_auth'] = self.context.get_permissions(req.user)
270
 
        ctx['errors'] = errors
271
 
 
272
 
class OfferingProjectsView(XHTMLView):
273
 
    """View the projects for an offering."""
274
 
    template = 'templates/offering_projects.html'
275
 
    permission = 'edit'
276
 
    tab = 'subjects'
277
 
 
278
 
    def populate(self, req, ctx):
279
 
        self.plugin_styles[Plugin] = ["project.css"]
280
 
        self.plugin_scripts[Plugin] = ["project.js"]
281
 
        ctx['req'] = req
282
 
        ctx['offering'] = self.context
283
 
        ctx['projectsets'] = []
284
 
        ctx['OfferingRESTView'] = OfferingRESTView
285
 
 
286
 
        #Open the projectset Fragment, and render it for inclusion
287
 
        #into the ProjectSets page
288
 
        #XXX: This could be a lot cleaner
289
 
        loader = genshi.template.TemplateLoader(".", auto_reload=True)
290
 
 
291
 
        set_fragment = os.path.join(os.path.dirname(__file__),
292
 
                "templates/projectset_fragment.html")
293
 
        project_fragment = os.path.join(os.path.dirname(__file__),
294
 
                "templates/project_fragment.html")
295
 
 
296
 
        for projectset in self.context.project_sets:
297
 
            settmpl = loader.load(set_fragment)
298
 
            setCtx = Context()
299
 
            setCtx['req'] = req
300
 
            setCtx['projectset'] = projectset
301
 
            setCtx['projects'] = []
302
 
            setCtx['GroupsView'] = GroupsView
303
 
            setCtx['ProjectSetRESTView'] = ProjectSetRESTView
304
 
 
305
 
            for project in projectset.projects:
306
 
                projecttmpl = loader.load(project_fragment)
307
 
                projectCtx = Context()
308
 
                projectCtx['req'] = req
309
 
                projectCtx['project'] = project
310
 
 
311
 
                setCtx['projects'].append(
312
 
                        projecttmpl.generate(projectCtx))
313
 
 
314
 
            ctx['projectsets'].append(settmpl.generate(setCtx))
315
 
 
316
 
 
317
 
class ProjectView(XHTMLView):
318
 
    """View the submissions for a ProjectSet"""
319
 
    template = "templates/project.html"
320
 
    permission = "edit"
321
 
    tab = 'subjects'
322
 
 
323
 
    def build_subversion_url(self, svnroot, submission):
324
 
        princ = submission.assessed.principal
325
 
 
326
 
        if isinstance(princ, User):
327
 
            path = 'users/%s' % princ.login
328
 
        else:
329
 
            path = 'groups/%s_%s_%s_%s' % (
330
 
                    princ.project_set.offering.subject.short_name,
331
 
                    princ.project_set.offering.semester.year,
332
 
                    princ.project_set.offering.semester.semester,
333
 
                    princ.name
334
 
                    )
335
 
        return urlparse.urljoin(
336
 
                    svnroot,
337
 
                    os.path.join(path, submission.path[1:] if
338
 
                                       submission.path.startswith(os.sep) else
339
 
                                       submission.path))
340
 
 
341
 
    def populate(self, req, ctx):
342
 
        self.plugin_styles[Plugin] = ["project.css"]
343
 
 
344
 
        ctx['req'] = req
345
 
        ctx['GroupsView'] = GroupsView
346
 
        ctx['EnrolView'] = EnrolView
347
 
        ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
348
 
        ctx['build_subversion_url'] = self.build_subversion_url
349
 
        ctx['svn_addr'] = req.config['urls']['svn_addr']
350
 
        ctx['project'] = self.context
351
 
        ctx['user'] = req.user
 
45
        enrolled_subjects = req.user.subjects
 
46
        unenrolled_subjects = [subject for subject in
 
47
                               req.store.find(Subject)
 
48
                               if subject not in enrolled_subjects]
 
49
 
 
50
        ctx['enrolled_subjects'] = []
 
51
        ctx['other_subjects'] = []
 
52
 
 
53
        req.content_type = "text/html"
 
54
        req.write_html_head_foot = True
 
55
 
 
56
        for subject in enrolled_subjects:
 
57
            new_subj = {}
 
58
            new_subj['name'] = subject.name
 
59
            new_subj['url'] = subject.url
 
60
            ctx['enrolled_subjects'].append(new_subj)
 
61
 
 
62
        if len(unenrolled_subjects) > 0:
 
63
            for subject in unenrolled_subjects:
 
64
                new_subj = {}
 
65
                new_subj['name'] = subject.name
 
66
                new_subj['url'] = subject.url
 
67
                ctx['other_subjects'].append(new_subj)
 
68
 
352
69
 
353
70
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
 
                   }
 
71
    urls = [
 
72
        ('subjects/', SubjectsView),
 
73
    ]
375
74
 
376
75
    tabs = [
377
 
        ('subjects', 'Subjects',
378
 
         'View subject content and complete worksheets',
379
 
         'subjects.png', 'subjects', 5)
 
76
        ('subjects', 'Subjects', 'Announcements and information about the '
 
77
         'subjects you are enrolled in.', 'subjects.png', 'subjects', 5)
380
78
    ]
381
79
 
382
80
    media = 'subject-media'