29
from storm.locals import Desc
31
from genshi.filters import HTMLFormFiller
32
from genshi.template import Context, TemplateLoader
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
39
from ivle.database import Subject, Semester, Offering, Enrolment, User,\
40
ProjectSet, Project, ProjectSubmission
33
41
from ivle import util
44
from ivle.webapp.admin.projectservice import ProjectSetRESTView,\
46
from ivle.webapp.admin.offeringservice import OfferingRESTView
36
49
class SubjectsView(XHTMLView):
37
50
'''The view of the list of subjects.'''
38
template = 'subjects.html'
39
appname = 'subjects' # XXX
41
def populate(self, req, ctx):
42
req.styles = ["media/subjects/subjects.css"]
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]
49
ctx['enrolled_subjects'] = []
50
ctx['other_subjects'] = []
52
req.content_type = "text/html"
53
req.write_html_head_foot = True
55
for subject in enrolled_subjects:
57
new_subj['name'] = subject.name
58
new_subj['url'] = subject.url
59
ctx['enrolled_subjects'].append(new_subj)
61
if len(unenrolled_subjects) > 0:
62
for subject in unenrolled_subjects:
64
new_subj['name'] = subject.name
65
new_subj['url'] = subject.url
66
ctx['other_subjects'].append(new_subj)
69
class SubjectView(XHTMLView):
70
'''The view of a subject.'''
71
template = 'subject.html'
72
appname = 'subjects' # XXX
74
def __init__(self, req, subject, path):
75
self.subject = req.store.find(Subject, code=subject).one()
78
def populate(self, req, ctx):
79
if self.subject is None:
82
ctx['serve_loc'] = urllib.quote(util.make_path(os.path.join('media',
83
'subjects', self.subject.code, self.path)))
85
class Plugin(BasePlugin):
51
template = 'templates/subjects.html'
54
def authorize(self, req):
55
return req.user is not None
57
def populate(self, req, ctx):
58
ctx['user'] = req.user
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))
67
class UserValidator(formencode.FancyValidator):
68
"""A FormEncode validator that turns a username into a user.
70
The state must have a 'store' attribute, which is the Storm store
72
def _to_python(self, value, state):
73
user = User.get_by_login(state.store, value)
77
raise formencode.Invalid('User does not exist', value, state)
80
class NoEnrolmentValidator(formencode.FancyValidator):
81
"""A FormEncode validator that ensures absence of an enrolment.
83
The state must have an 'offering' attribute.
85
def _to_python(self, value, state):
86
if state.offering.get_enrolment(value):
87
raise formencode.Invalid('User already enrolled', value, state)
91
class EnrolSchema(formencode.Schema):
92
user = formencode.All(NoEnrolmentValidator(), UserValidator())
95
class EnrolView(XHTMLView):
96
"""A form to enrol a user in an offering."""
97
template = 'templates/enrol.html'
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()
113
def filter(self, stream, ctx):
114
return stream | HTMLFormFiller(data=ctx['data'])
116
def populate(self, req, ctx):
117
if req.method == 'POST':
118
data = dict(req.get_fieldstorage())
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'])
125
req.throw_redirect(req.uri)
126
except formencode.Invalid, e:
127
errors = e.unpack_errors()
132
ctx['data'] = data or {}
133
ctx['offering'] = self.context
134
ctx['errors'] = errors
136
class OfferingProjectsView(XHTMLView):
137
"""View the projects for an offering."""
138
template = 'templates/offering_projects.html'
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()
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,
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"
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'] = []
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)
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")
183
for projectset in self.context.project_sets:
184
settmpl = loader.load(set_fragment)
186
setCtx['projectset'] = projectset
187
setCtx['new_project_url'] = self.new_project_url(projectset)
188
setCtx['projects'] = []
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)
196
setCtx['projects'].append(
197
projecttmpl.generate(projectCtx))
199
ctx['projectsets'].append(settmpl.generate(setCtx))
202
class ProjectView(XHTMLView):
203
"""View the submissions for a ProjectSet"""
204
template = "templates/project.html"
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:
221
def populate(self, req, ctx):
222
ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
223
ctx['project'] = self.context
225
class Plugin(ViewPlugin, MediaPlugin):
87
227
('subjects/', SubjectsView),
88
('subjects/:subject', SubjectView, {'path': ''}),
89
('subjects/:subject/*(path)', SubjectView),
228
('subjects/:subject/:year/:semester/+enrolments/+new', EnrolView),
229
('subjects/:subject/:year/:semester/+projects', OfferingProjectsView),
230
('subjects/:subject/:year/:semester/+projects/:project', ProjectView),
232
('api/subjects/:subject/:year/:semester/+projectsets/+new',
234
('api/subjects/:subject/:year/:semester/+projectsets/:projectset/+projects/+new',
236
('api/subjects/:subject/:year/:semester/+projects/:project',
242
('subjects', 'Subjects',
243
'View subject content and complete worksheets',
244
'subjects.png', 'subjects', 5)
247
media = 'subject-media'