~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 08:36:15 UTC
  • Revision ID: grantw@unimelb.edu.au-20100211083615-5imzir7px5dblvwk
divify the login form, fixing up the submit/error indentation while there.

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
26
27
import urllib
 
28
import urlparse
27
29
import cgi
28
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
29
43
from ivle import util
30
 
import ivle.db
31
 
 
32
 
def handle(req):
33
 
    """Handler for the Subjects application. Links to subject home pages."""
34
 
 
35
 
    req.styles = ["media/subjects/subjects.css"]
36
 
    if req.path == "":
37
 
        handle_toplevel_menu(req)
38
 
    else:
39
 
        handle_subject_page(req, req.path)
40
 
 
41
 
def handle_toplevel_menu(req):
42
 
    # This is represented as a directory. Redirect and add a slash if it is
43
 
    # missing.
44
 
    if req.uri[-1] != '/':
45
 
        req.throw_redirect(req.uri + '/')
46
 
 
47
 
    (enrolled_subjects, unenrolled_subjects) = \
48
 
              ivle.db.DB().get_subjects_status(req.user.login)
49
 
 
50
 
    def print_subject(subject):
51
 
        if subject['url'] is None:
52
 
            req.write('  <li>%s (no home page)</li>\n'
53
 
                % cgi.escape(subject['subj_name']))
54
 
        else:
55
 
            req.write('  <li><a href="%s">%s</a></li>\n'
56
 
                % (cgi.escape(subject['url']),
57
 
                   cgi.escape(subject['subj_name'])))
58
 
 
59
 
    req.content_type = "text/html"
60
 
    req.write_html_head_foot = True
61
 
    req.write('<div id="ivle_padding">\n')
62
 
    req.write("<h2>IVLE Subject Homepages</h2>\n")
63
 
    req.write("<h2>Subjects</h2>\n<ul>\n")
64
 
    for subject in enrolled_subjects:
65
 
        print_subject(subject)
66
 
    req.write("</ul>\n")
67
 
    if len(unenrolled_subjects) > 0:
68
 
        req.write("<h3>Other Subjects</h3>\n")
69
 
        req.write("<p>You are not currently enrolled in these subjects</p>\n")
70
 
        req.write("<ul>\n")
71
 
        for subject in unenrolled_subjects:
72
 
            print_subject(subject)
73
 
        req.write("</ul>\n")
74
 
    req.write("</div>\n")
75
 
 
76
 
def handle_subject_page(req, path):
77
 
    req.content_type = "text/html"
78
 
    req.write_html_head_foot = True     # Have dispatch print head and foot
79
 
 
80
 
    # Just make the iframe pointing to media/subjects
81
 
    serve_loc = util.make_path(os.path.join('media', 'subjects', path))
82
 
    req.write('<object class="fullscreen" type="text/html" \
83
 
data="%s"></iframe>'% urllib.quote(serve_loc))
 
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
    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
 
320
 
 
321
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
                   }
 
343
 
 
344
    tabs = [
 
345
        ('subjects', 'Subjects',
 
346
         'View subject content and complete worksheets',
 
347
         'subjects.png', 'subjects', 5)
 
348
    ]
 
349
 
 
350
    media = 'subject-media'