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

« back to all changes in this revision

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

Swap around some elements to make the OfferingProjectsView XHTML more valid.

Show diffs side-by-side

added added

removed removed

Lines of Context:
26
26
import urllib
27
27
import cgi
28
28
 
29
 
from common import util
30
 
import common.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
 
              common.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))
 
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
 
 
35
from ivle.webapp.base.xhtml import XHTMLView
 
36
from ivle.webapp.base.plugins import ViewPlugin, MediaPlugin
 
37
from ivle.webapp.errors import NotFound
 
38
 
 
39
from ivle.database import Subject, Semester, Offering, Enrolment, User,\
 
40
                          ProjectSet, Project, ProjectSubmission
 
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
 
47
 
 
48
 
 
49
class SubjectsView(XHTMLView):
 
50
    '''The view of the list of subjects.'''
 
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
 
 
224
        ctx['project'] = self.context
 
225
        ctx['assesseds'] = self.context.assesseds
 
226
 
 
227
        ctx['submissions'] = []
 
228
        for assessed in self.context.assesseds:
 
229
            if assessed.submissions.count() > 0:
 
230
                ctx['submissions'].append(
 
231
                        assessed.submissions.order_by(ProjectSubmission.date_submitted)[-1])
 
232
 
 
233
 
 
234
class Plugin(ViewPlugin, MediaPlugin):
 
235
    urls = [
 
236
        ('subjects/', SubjectsView),
 
237
        ('subjects/:subject/:year/:semester/+enrolments/+new', EnrolView),
 
238
        ('subjects/:subject/:year/:semester/+projects', OfferingProjectsView),
 
239
        ('subjects/:subject/:year/:semester/+projects/:project', ProjectView),
 
240
        #API Views
 
241
        ('api/subjects/:subject/:year/:semester/+projectsets/+new',
 
242
            OfferingRESTView),
 
243
        ('api/subjects/:subject/:year/:semester/+projectsets/:projectset/+projects/+new',
 
244
            ProjectSetRESTView),
 
245
        ('api/subjects/:subject/:year/:semester/+projects/:project', 
 
246
            ProjectRESTView),
 
247
 
 
248
    ]
 
249
 
 
250
    tabs = [
 
251
        ('subjects', 'Subjects',
 
252
         'View subject content and complete worksheets',
 
253
         'subjects.png', 'subjects', 5)
 
254
    ]
 
255
 
 
256
    media = 'subject-media'