~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-17 05:51:06 UTC
  • Revision ID: me@williamgrant.id.au-20091217055106-5xizza3nttltx1uh
Add icons to the project management view.

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
 
 
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
 
 
126
        # As we go, calculate the total score for this subject
 
127
        # (Assessable worksheets only, mandatory problems only)
 
128
 
 
129
        ctx['worksheets'], problems_total, problems_done = (
 
130
            ivle.worksheet.utils.create_list_of_fake_worksheets_and_stats(
 
131
                req.store, req.user, self.context))
 
132
 
 
133
        ctx['exercises_total'] = problems_total
 
134
        ctx['exercises_done'] = problems_done
 
135
        if problems_total > 0:
 
136
            if problems_done >= problems_total:
 
137
                ctx['worksheets_complete_class'] = "complete"
 
138
            elif problems_done > 0:
 
139
                ctx['worksheets_complete_class'] = "semicomplete"
 
140
            else:
 
141
                ctx['worksheets_complete_class'] = "incomplete"
 
142
            # Calculate the final percentage and mark for the subject
 
143
            (ctx['exercises_pct'], ctx['worksheet_mark'],
 
144
             ctx['worksheet_max_mark']) = (
 
145
                ivle.worksheet.utils.calculate_mark(
 
146
                    problems_done, problems_total))
 
147
 
 
148
 
 
149
class UserValidator(formencode.FancyValidator):
 
150
    """A FormEncode validator that turns a username into a user.
 
151
 
 
152
    The state must have a 'store' attribute, which is the Storm store
 
153
    to use."""
 
154
    def _to_python(self, value, state):
 
155
        user = User.get_by_login(state.store, value)
 
156
        if user:
 
157
            return user
 
158
        else:
 
159
            raise formencode.Invalid('User does not exist', value, state)
 
160
 
 
161
 
 
162
class NoEnrolmentValidator(formencode.FancyValidator):
 
163
    """A FormEncode validator that ensures absence of an enrolment.
 
164
 
 
165
    The state must have an 'offering' attribute.
 
166
    """
 
167
    def _to_python(self, value, state):
 
168
        if state.offering.get_enrolment(value):
 
169
            raise formencode.Invalid('User already enrolled', value, state)
 
170
        return value
 
171
 
 
172
 
 
173
class RoleEnrolmentValidator(formencode.FancyValidator):
 
174
    """A FormEncode validator that checks permission to enrol users with a
 
175
    particular role.
 
176
 
 
177
    The state must have an 'offering' attribute.
 
178
    """
 
179
    def _to_python(self, value, state):
 
180
        if ("enrol_" + value) not in state.offering.get_permissions(state.user):
 
181
            raise formencode.Invalid('Not allowed to assign users that role',
 
182
                                     value, state)
 
183
        return value
 
184
 
 
185
 
 
186
class EnrolSchema(formencode.Schema):
 
187
    user = formencode.All(NoEnrolmentValidator(), UserValidator())
 
188
    role = formencode.All(formencode.validators.OneOf(
 
189
                                ["lecturer", "tutor", "student"]),
 
190
                          RoleEnrolmentValidator(),
 
191
                          formencode.validators.UnicodeString())
 
192
 
 
193
 
 
194
class EnrolmentsView(XHTMLView):
 
195
    """A page which displays all users enrolled in an offering."""
 
196
    template = 'templates/enrolments.html'
 
197
    permission = 'edit'
 
198
 
 
199
    def populate(self, req, ctx):
 
200
        ctx['offering'] = self.context
 
201
 
 
202
class EnrolView(XHTMLView):
 
203
    """A form to enrol a user in an offering."""
 
204
    template = 'templates/enrol.html'
 
205
    tab = 'subjects'
 
206
    permission = 'enrol'
 
207
 
 
208
    def filter(self, stream, ctx):
 
209
        return stream | HTMLFormFiller(data=ctx['data'])
 
210
 
 
211
    def populate(self, req, ctx):
 
212
        if req.method == 'POST':
 
213
            data = dict(req.get_fieldstorage())
 
214
            try:
 
215
                validator = EnrolSchema()
 
216
                req.offering = self.context # XXX: Getting into state.
 
217
                data = validator.to_python(data, state=req)
 
218
                self.context.enrol(data['user'], data['role'])
 
219
                req.store.commit()
 
220
                req.throw_redirect(req.uri)
 
221
            except formencode.Invalid, e:
 
222
                errors = e.unpack_errors()
 
223
        else:
 
224
            data = {}
 
225
            errors = {}
 
226
 
 
227
        ctx['data'] = data or {}
 
228
        ctx['offering'] = self.context
 
229
        ctx['roles_auth'] = self.context.get_permissions(req.user)
 
230
        ctx['errors'] = errors
 
231
 
 
232
class OfferingProjectsView(XHTMLView):
 
233
    """View the projects for an offering."""
 
234
    template = 'templates/offering_projects.html'
 
235
    permission = 'edit'
 
236
    tab = 'subjects'
 
237
 
 
238
    def populate(self, req, ctx):
 
239
        self.plugin_styles[Plugin] = ["project.css"]
 
240
        self.plugin_scripts[Plugin] = ["project.js"]
 
241
        ctx['req'] = req
 
242
        ctx['offering'] = self.context
 
243
        ctx['projectsets'] = []
 
244
        ctx['OfferingRESTView'] = OfferingRESTView
 
245
 
 
246
        #Open the projectset Fragment, and render it for inclusion
 
247
        #into the ProjectSets page
 
248
        #XXX: This could be a lot cleaner
 
249
        loader = genshi.template.TemplateLoader(".", auto_reload=True)
 
250
 
 
251
        set_fragment = os.path.join(os.path.dirname(__file__),
 
252
                "templates/projectset_fragment.html")
 
253
        project_fragment = os.path.join(os.path.dirname(__file__),
 
254
                "templates/project_fragment.html")
 
255
 
 
256
        for projectset in self.context.project_sets:
 
257
            settmpl = loader.load(set_fragment)
 
258
            setCtx = Context()
 
259
            setCtx['req'] = req
 
260
            setCtx['projectset'] = projectset
 
261
            setCtx['projects'] = []
 
262
            setCtx['GroupsView'] = GroupsView
 
263
            setCtx['ProjectSetRESTView'] = ProjectSetRESTView
 
264
 
 
265
            for project in projectset.projects:
 
266
                projecttmpl = loader.load(project_fragment)
 
267
                projectCtx = Context()
 
268
                projectCtx['req'] = req
 
269
                projectCtx['project'] = project
 
270
 
 
271
                setCtx['projects'].append(
 
272
                        projecttmpl.generate(projectCtx))
 
273
 
 
274
            ctx['projectsets'].append(settmpl.generate(setCtx))
 
275
 
 
276
 
 
277
class ProjectView(XHTMLView):
 
278
    """View the submissions for a ProjectSet"""
 
279
    template = "templates/project.html"
 
280
    permission = "edit"
 
281
    tab = 'subjects'
 
282
 
 
283
    def build_subversion_url(self, svnroot, submission):
 
284
        princ = submission.assessed.principal
 
285
 
 
286
        if isinstance(princ, User):
 
287
            path = 'users/%s' % princ.login
 
288
        else:
 
289
            path = 'groups/%s_%s_%s_%s' % (
 
290
                    princ.project_set.offering.subject.short_name,
 
291
                    princ.project_set.offering.semester.year,
 
292
                    princ.project_set.offering.semester.semester,
 
293
                    princ.name
 
294
                    )
 
295
        return urlparse.urljoin(
 
296
                    svnroot,
 
297
                    os.path.join(path, submission.path[1:] if
 
298
                                       submission.path.startswith(os.sep) else
 
299
                                       submission.path))
 
300
 
 
301
    def populate(self, req, ctx):
 
302
        self.plugin_styles[Plugin] = ["project.css"]
 
303
 
 
304
        ctx['req'] = req
 
305
        ctx['GroupsView'] = GroupsView
 
306
        ctx['EnrolView'] = EnrolView
 
307
        ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
 
308
        ctx['build_subversion_url'] = self.build_subversion_url
 
309
        ctx['svn_addr'] = req.config['urls']['svn_addr']
 
310
        ctx['project'] = self.context
 
311
        ctx['user'] = req.user
 
312
 
 
313
class Plugin(ViewPlugin, MediaPlugin):
 
314
    forward_routes = (root_to_subject, subject_to_offering,
 
315
                      offering_to_project, offering_to_projectset)
 
316
    reverse_routes = (subject_url, offering_url, projectset_url, project_url)
 
317
 
 
318
    views = [(ApplicationRoot, ('subjects', '+index'), SubjectsView),
 
319
             (Offering, '+index', OfferingView),
 
320
             (Offering, ('+enrolments', '+index'), EnrolmentsView),
 
321
             (Offering, ('+enrolments', '+new'), EnrolView),
 
322
             (Offering, ('+projects', '+index'), OfferingProjectsView),
 
323
             (Project, '+index', ProjectView),
 
324
 
 
325
             (Offering, ('+projectsets', '+new'), OfferingRESTView, 'api'),
 
326
             (ProjectSet, ('+projects', '+new'), ProjectSetRESTView, 'api'),
 
327
             ]
 
328
 
 
329
    breadcrumbs = {Subject: SubjectBreadcrumb,
 
330
                   Offering: OfferingBreadcrumb,
 
331
                   User: UserBreadcrumb,
 
332
                   Project: ProjectBreadcrumb,
 
333
                   }
 
334
 
 
335
    tabs = [
 
336
        ('subjects', 'Subjects',
 
337
         'View subject content and complete worksheets',
 
338
         'subjects.png', 'subjects', 5)
 
339
    ]
 
340
 
 
341
    media = 'subject-media'