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

« back to all changes in this revision

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

Added printing of full traceback when an error is caught.

Show diffs side-by-side

added added

removed removed

Lines of Context:
26
26
import urllib
27
27
import cgi
28
28
 
 
29
from storm.locals import Desc
 
30
import genshi
 
31
from genshi.filters import HTMLFormFiller
 
32
from genshi.template import Context, TemplateLoader
 
33
import formencode
 
34
 
29
35
from ivle.webapp.base.xhtml import XHTMLView
30
 
from ivle.webapp.base.plugins import BasePlugin
 
36
from ivle.webapp.base.plugins import ViewPlugin, MediaPlugin
31
37
from ivle.webapp.errors import NotFound
32
 
from ivle.database import Subject
 
38
 
 
39
from ivle.database import Subject, Semester, Offering, Enrolment, User,\
 
40
                          ProjectSet, Project, ProjectSubmission
33
41
from ivle import util
 
42
import ivle.date
 
43
 
 
44
from ivle.webapp.admin.projectservice import ProjectSetRESTView,\
 
45
                                             ProjectRESTView
 
46
from ivle.webapp.admin.offeringservice import OfferingRESTView
34
47
 
35
48
 
36
49
class SubjectsView(XHTMLView):
37
50
    '''The view of the list of subjects.'''
38
 
    template = 'subjects.html'
39
 
    appname = 'subjects' # XXX
40
 
 
41
 
    def populate(self, req, ctx):
42
 
        req.styles = ["media/subjects/subjects.css"]
43
 
 
44
 
        enrolled_subjects = req.user.subjects
45
 
        unenrolled_subjects = [subject for subject in
46
 
                               req.store.find(Subject)
47
 
                               if subject not in enrolled_subjects]
48
 
 
49
 
        ctx['enrolled_subjects'] = []
50
 
        ctx['other_subjects'] = []
51
 
 
52
 
        req.content_type = "text/html"
53
 
        req.write_html_head_foot = True
54
 
 
55
 
        for subject in enrolled_subjects:
56
 
            new_subj = {}
57
 
            new_subj['name'] = subject.name
58
 
            new_subj['url'] = subject.url
59
 
            ctx['enrolled_subjects'].append(new_subj)
60
 
 
61
 
        if len(unenrolled_subjects) > 0:
62
 
            for subject in unenrolled_subjects:
63
 
                new_subj = {}
64
 
                new_subj['name'] = subject.name
65
 
                new_subj['url'] = subject.url
66
 
                ctx['other_subjects'].append(new_subj)
67
 
 
68
 
 
69
 
class SubjectView(XHTMLView):
70
 
    '''The view of a subject.'''
71
 
    template = 'subject.html'
72
 
    appname = 'subjects' # XXX
73
 
 
74
 
    def __init__(self, req, subject, path):
75
 
        self.subject = req.store.find(Subject, code=subject).one()
76
 
        self.path = path
77
 
 
78
 
    def populate(self, req, ctx):
79
 
        if self.subject is None:
80
 
            raise NotFound()
81
 
 
82
 
        ctx['serve_loc'] = urllib.quote(util.make_path(os.path.join('media',
83
 
                                    'subjects', self.subject.code, self.path)))
84
 
 
85
 
class Plugin(BasePlugin):
 
51
    template = 'templates/subjects.html'
 
52
    tab = 'subjects'
 
53
 
 
54
    def authorize(self, req):
 
55
        return req.user is not None
 
56
 
 
57
    def populate(self, req, ctx):
 
58
        ctx['user'] = req.user
 
59
        ctx['semesters'] = []
 
60
        for semester in req.store.find(Semester).order_by(Desc(Semester.year),
 
61
                                                     Desc(Semester.semester)):
 
62
            enrolments = semester.enrolments.find(user=req.user)
 
63
            if enrolments.count():
 
64
                ctx['semesters'].append((semester, enrolments))
 
65
 
 
66
 
 
67
class UserValidator(formencode.FancyValidator):
 
68
    """A FormEncode validator that turns a username into a user.
 
69
 
 
70
    The state must have a 'store' attribute, which is the Storm store
 
71
    to use."""
 
72
    def _to_python(self, value, state):
 
73
        user = User.get_by_login(state.store, value)
 
74
        if user:
 
75
            return user
 
76
        else:
 
77
            raise formencode.Invalid('User does not exist', value, state)
 
78
 
 
79
 
 
80
class NoEnrolmentValidator(formencode.FancyValidator):
 
81
    """A FormEncode validator that ensures absence of an enrolment.
 
82
 
 
83
    The state must have an 'offering' attribute.
 
84
    """
 
85
    def _to_python(self, value, state):
 
86
        if state.offering.get_enrolment(value):
 
87
            raise formencode.Invalid('User already enrolled', value, state)
 
88
        return value
 
89
 
 
90
 
 
91
class EnrolSchema(formencode.Schema):
 
92
    user = formencode.All(NoEnrolmentValidator(), UserValidator())
 
93
 
 
94
 
 
95
class EnrolView(XHTMLView):
 
96
    """A form to enrol a user in an offering."""
 
97
    template = 'templates/enrol.html'
 
98
    tab = 'subjects'
 
99
    permission = 'edit'
 
100
 
 
101
    def __init__(self, req, subject, year, semester):
 
102
        """Find the given offering by subject, year and semester."""
 
103
        self.context = req.store.find(Offering,
 
104
            Offering.subject_id == Subject.id,
 
105
            Subject.short_name == subject,
 
106
            Offering.semester_id == Semester.id,
 
107
            Semester.year == year,
 
108
            Semester.semester == semester).one()
 
109
 
 
110
        if not self.context:
 
111
            raise NotFound()
 
112
 
 
113
    def filter(self, stream, ctx):
 
114
        return stream | HTMLFormFiller(data=ctx['data'])
 
115
 
 
116
    def populate(self, req, ctx):
 
117
        if req.method == 'POST':
 
118
            data = dict(req.get_fieldstorage())
 
119
            try:
 
120
                validator = EnrolSchema()
 
121
                req.offering = self.context # XXX: Getting into state.
 
122
                data = validator.to_python(data, state=req)
 
123
                self.context.enrol(data['user'])
 
124
                req.store.commit()
 
125
                req.throw_redirect(req.uri)
 
126
            except formencode.Invalid, e:
 
127
                errors = e.unpack_errors()
 
128
        else:
 
129
            data = {}
 
130
            errors = {}
 
131
 
 
132
        ctx['data'] = data or {}
 
133
        ctx['offering'] = self.context
 
134
        ctx['errors'] = errors
 
135
 
 
136
class OfferingProjectsView(XHTMLView):
 
137
    """View the projects for an offering."""
 
138
    template = 'templates/offering_projects.html'
 
139
    permission = 'edit'
 
140
    tab = 'subjects'
 
141
    
 
142
    def __init__(self, req, subject, year, semester):
 
143
        self.context = req.store.find(Offering,
 
144
            Offering.subject_id == Subject.id,
 
145
            Subject.short_name == subject,
 
146
            Offering.semester_id == Semester.id,
 
147
            Semester.year == year,
 
148
            Semester.semester == semester).one()
 
149
 
 
150
        if not self.context:
 
151
            raise NotFound()
 
152
 
 
153
    def project_url(self, projectset, project):
 
154
        return "/subjects/%s/%s/%s/+projects/%s" % (
 
155
                    self.context.subject.short_name,
 
156
                    self.context.semester.year,
 
157
                    self.context.semester.semester,
 
158
                    project.short_name
 
159
                    )
 
160
 
 
161
    def new_project_url(self, projectset):
 
162
        return "/api/subjects/" + self.context.subject.short_name + "/" +\
 
163
                self.context.semester.year + "/" + \
 
164
                self.context.semester.semester + "/+projectsets/" +\
 
165
                str(projectset.id) + "/+projects/+new"
 
166
    
 
167
    def populate(self, req, ctx):
 
168
        self.plugin_styles[Plugin] = ["project.css"]
 
169
        self.plugin_scripts[Plugin] = ["project.js"]
 
170
        ctx['offering'] = self.context
 
171
        ctx['projectsets'] = []
 
172
 
 
173
        #Open the projectset Fragment, and render it for inclusion
 
174
        #into the ProjectSets page
 
175
        #XXX: This could be a lot cleaner
 
176
        loader = genshi.template.TemplateLoader(".", auto_reload=True)
 
177
 
 
178
        set_fragment = os.path.join(os.path.dirname(__file__),
 
179
                "templates/projectset_fragment.html")
 
180
        project_fragment = os.path.join(os.path.dirname(__file__),
 
181
                "templates/project_fragment.html")
 
182
 
 
183
        for projectset in self.context.project_sets:
 
184
            settmpl = loader.load(set_fragment)
 
185
            setCtx = Context()
 
186
            setCtx['projectset'] = projectset
 
187
            setCtx['new_project_url'] = self.new_project_url(projectset)
 
188
            setCtx['projects'] = []
 
189
 
 
190
            for project in projectset.projects:
 
191
                projecttmpl = loader.load(project_fragment)
 
192
                projectCtx = Context()
 
193
                projectCtx['project'] = project
 
194
                projectCtx['project_url'] = self.project_url(projectset, project)
 
195
 
 
196
                setCtx['projects'].append(
 
197
                        projecttmpl.generate(projectCtx))
 
198
 
 
199
            ctx['projectsets'].append(settmpl.generate(setCtx))
 
200
 
 
201
 
 
202
class ProjectView(XHTMLView):
 
203
    """View the submissions for a ProjectSet"""
 
204
    template = "templates/project.html"
 
205
    permission = "edit"
 
206
    tab = 'subjects'
 
207
 
 
208
    def __init__(self, req, subject, year, semester, project):
 
209
        self.context = req.store.find(Project,
 
210
                Project.short_name == project,
 
211
                Project.project_set_id == ProjectSet.id,
 
212
                ProjectSet.offering_id == Offering.id,
 
213
                Offering.semester_id == Semester.id,
 
214
                Semester.year == year,
 
215
                Semester.semester == semester,
 
216
                Offering.subject_id == Subject.id,
 
217
                Subject.short_name == subject).one()
 
218
        if self.context is None:
 
219
            raise NotFound()
 
220
 
 
221
    def populate(self, req, ctx):
 
222
        ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
 
223
        ctx['project'] = self.context
 
224
 
 
225
class Plugin(ViewPlugin, MediaPlugin):
86
226
    urls = [
87
227
        ('subjects/', SubjectsView),
88
 
        ('subjects/:subject', SubjectView, {'path': ''}),
89
 
        ('subjects/:subject/*(path)', SubjectView),
90
 
    ]
 
228
        ('subjects/:subject/:year/:semester/+enrolments/+new', EnrolView),
 
229
        ('subjects/:subject/:year/:semester/+projects', OfferingProjectsView),
 
230
        ('subjects/:subject/:year/:semester/+projects/:project', ProjectView),
 
231
        #API Views
 
232
        ('api/subjects/:subject/:year/:semester/+projectsets/+new',
 
233
            OfferingRESTView),
 
234
        ('api/subjects/:subject/:year/:semester/+projectsets/:projectset/+projects/+new',
 
235
            ProjectSetRESTView),
 
236
        ('api/subjects/:subject/:year/:semester/+projects/:project', 
 
237
            ProjectRESTView),
 
238
 
 
239
    ]
 
240
 
 
241
    tabs = [
 
242
        ('subjects', 'Subjects',
 
243
         'View subject content and complete worksheets',
 
244
         'subjects.png', 'subjects', 5)
 
245
    ]
 
246
 
 
247
    media = 'subject-media'