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

1080.1.2 by matt.giuca
New module: ivle.database. Classes and utilities for Storm ORM.
1
# IVLE - Informatics Virtual Learning Environment
2
# Copyright (C) 2007-2009 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
# Author: Matt Giuca, Will Grant
19
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
20
"""Database utilities and content classes.
1080.1.2 by matt.giuca
New module: ivle.database. Classes and utilities for Storm ORM.
21
22
This module provides all of the classes which map to database tables.
23
It also provides miscellaneous utility functions for database interaction.
24
"""
25
1197 by Matt Giuca
ivle.chat, ivle.database, ivle.makeuser: Replaced use of md5 library with
26
import hashlib
1080.1.15 by me at id
Give ivle.database.User {password,account}_expired attributes, and get
27
import datetime
1080.1.13 by me at id
ivle.database.User: Add an authenticate() method, and a hash_password()
28
1080.1.4 by matt.giuca
ivle.database: Added User class.
29
from storm.locals import create_database, Store, Int, Unicode, DateTime, \
1080.1.27 by me at id
ivle.database.User: Add an 'active_enrolments' property, which returns a list
30
                         Reference, ReferenceSet, Bool, Storm, Desc
1099.1.242 by Nick Chadwick
Fixed a problem with exercise editor, which wasn't editing or adding
31
from storm.exceptions import NotOneError, IntegrityError
1080.1.2 by matt.giuca
New module: ivle.database. Classes and utilities for Storm ORM.
32
1099.1.220 by Nick Chadwick
Merged from trunk
33
from ivle.worksheet.rst import rst
1080.1.2 by matt.giuca
New module: ivle.database. Classes and utilities for Storm ORM.
34
1080.1.39 by Matt Giuca
ivle.database: Added __all__ to the top of the file.
35
__all__ = ['get_store',
36
            'User',
37
            'Subject', 'Semester', 'Offering', 'Enrolment',
38
            'ProjectSet', 'Project', 'ProjectGroup', 'ProjectGroupMembership',
1165.1.4 by William Grant
Add database classes for assessed, project_extension and project_submission.
39
            'Assessed', 'ProjectSubmission', 'ProjectExtension',
1080.1.59 by Matt Giuca
ivle.worksheet, ivle.database: Added/updated __all__.
40
            'Exercise', 'Worksheet', 'WorksheetExercise',
1080.1.61 by William Grant
ivle.database: Add an Offering.enrol(user) method, which enrols the user in
41
            'ExerciseSave', 'ExerciseAttempt',
1110 by William Grant
ivle-enrol now allows updating of existing enrolments. It also sets the role.
42
            'TestCase', 'TestSuite', 'TestSuiteVar'
1080.1.39 by Matt Giuca
ivle.database: Added __all__ to the top of the file.
43
        ]
44
1080.1.25 by me at id
ivle.database: Add Subject, Semester and Offering.
45
def _kwarg_init(self, **kwargs):
46
    for k,v in kwargs.items():
1080.1.46 by William Grant
ivle.database._kwarg_init: Check with hasattr() on the class, not the object,
47
        if k.startswith('_') or not hasattr(self.__class__, k):
1080.1.25 by me at id
ivle.database: Add Subject, Semester and Offering.
48
            raise TypeError("%s got an unexpected keyword argument '%s'"
1080.1.45 by William Grant
ivle.database._kwarg_init: Fix exception throwing.
49
                % (self.__class__.__name__, k))
1080.1.25 by me at id
ivle.database: Add Subject, Semester and Offering.
50
        setattr(self, k, v)
51
1201 by William Grant
ivle.database.get_store() now takes a configuration object.
52
def get_conn_string(config):
53
    """Create a Storm connection string to the IVLE database
54
55
    @param config: The IVLE configuration.
1080.1.2 by matt.giuca
New module: ivle.database. Classes and utilities for Storm ORM.
56
    """
1099.1.174 by William Grant
ivle.database.get_conn_string() now defaults to localhost:5432, rather than
57
58
    clusterstr = ''
1201 by William Grant
ivle.database.get_store() now takes a configuration object.
59
    if config['database']['username']:
60
        clusterstr += config['database']['username']
61
        if config['database']['password']:
62
            clusterstr += ':' + config['database']['password']
1099.1.174 by William Grant
ivle.database.get_conn_string() now defaults to localhost:5432, rather than
63
        clusterstr += '@'
64
1201 by William Grant
ivle.database.get_store() now takes a configuration object.
65
    host = config['database']['host'] or 'localhost'
66
    port = config['database']['port'] or 5432
1099.1.174 by William Grant
ivle.database.get_conn_string() now defaults to localhost:5432, rather than
67
68
    clusterstr += '%s:%d' % (host, port)
69
1201 by William Grant
ivle.database.get_store() now takes a configuration object.
70
    return "postgres://%s/%s" % (clusterstr, config['database']['name'])
71
72
def get_store(config):
73
    """Create a Storm store connected to the IVLE database.
74
75
    @param config: The IVLE configuration.
76
    """
77
    return Store(create_database(get_conn_string(config)))
1080.1.4 by matt.giuca
ivle.database: Added User class.
78
1080.1.39 by Matt Giuca
ivle.database: Added __all__ to the top of the file.
79
# USERS #
80
1080.1.26 by me at id
ivle.database: Add an Enrolment class, and reference(set)s between all of the
81
class User(Storm):
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
82
    """An IVLE user account."""
1080.1.4 by matt.giuca
ivle.database: Added User class.
83
    __storm_table__ = "login"
84
85
    id = Int(primary=True, name="loginid")
86
    login = Unicode()
87
    passhash = Unicode()
88
    state = Unicode()
1101 by William Grant
Privileges (apart from admin) are now offering-local, not global.
89
    admin = Bool()
1080.1.4 by matt.giuca
ivle.database: Added User class.
90
    unixid = Int()
91
    nick = Unicode()
92
    pass_exp = DateTime()
93
    acct_exp = DateTime()
94
    last_login = DateTime()
95
    svn_pass = Unicode()
96
    email = Unicode()
97
    fullname = Unicode()
98
    studentid = Unicode()
99
    settings = Unicode()
100
1080.1.25 by me at id
ivle.database: Add Subject, Semester and Offering.
101
    __init__ = _kwarg_init
1080.1.4 by matt.giuca
ivle.database: Added User class.
102
103
    def __repr__(self):
104
        return "<%s '%s'>" % (type(self).__name__, self.login)
1080.1.5 by matt.giuca
ivle.database.User: Add the missing methods from ivle.user.User.
105
1080.1.13 by me at id
ivle.database.User: Add an authenticate() method, and a hash_password()
106
    def authenticate(self, password):
107
        """Validate a given password against this user.
108
109
        Returns True if the given password matches the password hash for this
110
        User, False if it doesn't match, and None if there is no hash for the
111
        user.
112
        """
113
        if self.passhash is None:
114
            return None
115
        return self.hash_password(password) == self.passhash
116
1080.1.15 by me at id
Give ivle.database.User {password,account}_expired attributes, and get
117
    @property
1165.1.26 by William Grant
Add display_name properties to users and groups.
118
    def display_name(self):
119
        return self.fullname
120
121
    @property
1080.1.15 by me at id
Give ivle.database.User {password,account}_expired attributes, and get
122
    def password_expired(self):
1080.1.5 by matt.giuca
ivle.database.User: Add the missing methods from ivle.user.User.
123
        fieldval = self.pass_exp
1080.1.15 by me at id
Give ivle.database.User {password,account}_expired attributes, and get
124
        return fieldval is not None and datetime.datetime.now() > fieldval
125
126
    @property
127
    def account_expired(self):
1080.1.5 by matt.giuca
ivle.database.User: Add the missing methods from ivle.user.User.
128
        fieldval = self.acct_exp
1080.1.15 by me at id
Give ivle.database.User {password,account}_expired attributes, and get
129
        return fieldval is not None and datetime.datetime.now() > fieldval
1080.1.6 by matt.giuca
ivle.database.User: Added get_by_login method.
130
1099.1.121 by William Grant
Don't set req.user unless the login in the session specifies a valid user.
131
    @property
132
    def valid(self):
133
        return self.state == 'enabled' and not self.account_expired
134
1080.1.29 by me at id
ivle.database.User: Order 'enrolments' the same way as 'active_enrolments'.
135
    def _get_enrolments(self, justactive):
1080.1.27 by me at id
ivle.database.User: Add an 'active_enrolments' property, which returns a list
136
        return Store.of(self).find(Enrolment,
137
            Enrolment.user_id == self.id,
1080.1.29 by me at id
ivle.database.User: Order 'enrolments' the same way as 'active_enrolments'.
138
            (Enrolment.active == True) if justactive else True,
1080.1.27 by me at id
ivle.database.User: Add an 'active_enrolments' property, which returns a list
139
            Enrolment.offering_id == Offering.id,
140
            Offering.semester_id == Semester.id,
141
            Offering.subject_id == Subject.id).order_by(
142
                Desc(Semester.year),
143
                Desc(Semester.semester),
144
                Desc(Subject.code)
145
            )
146
1080.1.68 by William Grant
ivle.database.User: Add a write-only 'password' attribute. When set, it will
147
    def _set_password(self, password):
148
        if password is None:
149
            self.passhash = None
150
        else:
151
            self.passhash = unicode(User.hash_password(password))
152
    password = property(fset=_set_password)
153
1080.1.29 by me at id
ivle.database.User: Order 'enrolments' the same way as 'active_enrolments'.
154
    @property
1080.1.31 by me at id
ivle.database.User: Add 'subjects', an attribute containing currently
155
    def subjects(self):
156
        return Store.of(self).find(Subject,
157
            Enrolment.user_id == self.id,
158
            Enrolment.active == True,
159
            Offering.id == Enrolment.offering_id,
160
            Subject.id == Offering.subject_id).config(distinct=True)
161
1080.1.36 by William Grant
ivle.database: Add ProjectSet, Project, ProjectGroup, ProjectGroupMembership
162
    # TODO: Invitations should be listed too?
163
    def get_groups(self, offering=None):
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
164
        """Get groups of which this user is a member.
165
166
        @param offering: An optional offering to restrict the search to.
167
        """
1080.1.36 by William Grant
ivle.database: Add ProjectSet, Project, ProjectGroup, ProjectGroupMembership
168
        preds = [
169
            ProjectGroupMembership.user_id == self.id,
170
            ProjectGroup.id == ProjectGroupMembership.project_group_id,
171
        ]
172
        if offering:
173
            preds.extend([
174
                ProjectSet.offering_id == offering.id,
175
                ProjectGroup.project_set_id == ProjectSet.id,
176
            ])
177
        return Store.of(self).find(ProjectGroup, *preds)
178
179
    @property
180
    def groups(self):
181
        return self.get_groups()
182
1080.1.31 by me at id
ivle.database.User: Add 'subjects', an attribute containing currently
183
    @property
1080.1.29 by me at id
ivle.database.User: Order 'enrolments' the same way as 'active_enrolments'.
184
    def active_enrolments(self):
185
        '''A sanely ordered list of the user's active enrolments.'''
186
        return self._get_enrolments(True)
187
188
    @property
189
    def enrolments(self):
190
        '''A sanely ordered list of all of the user's enrolments.'''
191
        return self._get_enrolments(False) 
1080.1.27 by me at id
ivle.database.User: Add an 'active_enrolments' property, which returns a list
192
1165.1.11 by William Grant
Let callsites ask User.get_projects() to show inactive offerings too.
193
    def get_projects(self, offering=None, active_only=True):
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
194
        """Find projects that the user can submit.
1165.1.10 by William Grant
Add User.get_projects(), returning a list of submission targets.
195
1165.1.11 by William Grant
Let callsites ask User.get_projects() to show inactive offerings too.
196
        This will include projects for offerings in which the user is
1165.1.10 by William Grant
Add User.get_projects(), returning a list of submission targets.
197
        enrolled, as long as the project is not in a project set which has
198
        groups (ie. if maximum number of group members is 0).
199
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
200
        @param active_only: Whether to only search active offerings.
201
        @param offering: An optional offering to restrict the search to.
202
        """
1165.1.10 by William Grant
Add User.get_projects(), returning a list of submission targets.
203
        return Store.of(self).find(Project,
204
            Project.project_set_id == ProjectSet.id,
1165.1.46 by William Grant
Respect the new max_students_per_group semantics in Python.
205
            ProjectSet.max_students_per_group == None,
1165.1.10 by William Grant
Add User.get_projects(), returning a list of submission targets.
206
            ProjectSet.offering_id == Offering.id,
1165.1.11 by William Grant
Let callsites ask User.get_projects() to show inactive offerings too.
207
            (offering is None) or (Offering.id == offering.id),
1165.1.10 by William Grant
Add User.get_projects(), returning a list of submission targets.
208
            Semester.id == Offering.semester_id,
1165.1.11 by William Grant
Let callsites ask User.get_projects() to show inactive offerings too.
209
            (not active_only) or (Semester.state == u'current'),
1165.1.10 by William Grant
Add User.get_projects(), returning a list of submission targets.
210
            Enrolment.offering_id == Offering.id,
211
            Enrolment.user_id == self.id)
212
1080.1.13 by me at id
ivle.database.User: Add an authenticate() method, and a hash_password()
213
    @staticmethod
214
    def hash_password(password):
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
215
        """Hash a password with MD5."""
1197 by Matt Giuca
ivle.chat, ivle.database, ivle.makeuser: Replaced use of md5 library with
216
        return hashlib.md5(password).hexdigest()
1080.1.13 by me at id
ivle.database.User: Add an authenticate() method, and a hash_password()
217
1080.1.6 by matt.giuca
ivle.database.User: Added get_by_login method.
218
    @classmethod
219
    def get_by_login(cls, store, login):
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
220
        """Find a user in a store by login name."""
1080.1.7 by matt.giuca
The new ivle.database.User class is now used in Request and usrmgt, which
221
        return store.find(cls, cls.login == unicode(login)).one()
1080.1.25 by me at id
ivle.database: Add Subject, Semester and Offering.
222
1099.1.110 by William Grant
Implement an authorization system in the new framework. This breaks the REST
223
    def get_permissions(self, user):
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
224
        """Determine privileges held by a user over this object.
225
226
        If the user requesting privileges is this user or an admin,
227
        they may do everything. Otherwise they may do nothing.
228
        """
1101 by William Grant
Privileges (apart from admin) are now offering-local, not global.
229
        if user and user.admin or user is self:
1165.1.7 by William Grant
Grant submit_project on users to themselves, and on groups to their members.
230
            return set(['view', 'edit', 'submit_project'])
1099.1.110 by William Grant
Implement an authorization system in the new framework. This breaks the REST
231
        else:
232
            return set()
233
1080.1.39 by Matt Giuca
ivle.database: Added __all__ to the top of the file.
234
# SUBJECTS AND ENROLMENTS #
235
1080.1.26 by me at id
ivle.database: Add an Enrolment class, and reference(set)s between all of the
236
class Subject(Storm):
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
237
    """A subject (or course) which is run in some semesters."""
238
1080.1.25 by me at id
ivle.database: Add Subject, Semester and Offering.
239
    __storm_table__ = "subject"
240
241
    id = Int(primary=True, name="subjectid")
242
    code = Unicode(name="subj_code")
243
    name = Unicode(name="subj_name")
244
    short_name = Unicode(name="subj_short_name")
245
    url = Unicode()
246
1080.1.26 by me at id
ivle.database: Add an Enrolment class, and reference(set)s between all of the
247
    offerings = ReferenceSet(id, 'Offering.subject_id')
248
1080.1.25 by me at id
ivle.database: Add Subject, Semester and Offering.
249
    __init__ = _kwarg_init
250
251
    def __repr__(self):
252
        return "<%s '%s'>" % (type(self).__name__, self.short_name)
253
1099.1.110 by William Grant
Implement an authorization system in the new framework. This breaks the REST
254
    def get_permissions(self, user):
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
255
        """Determine privileges held by a user over this object.
256
257
        If the user requesting privileges is an admin, they may edit.
258
        Otherwise they may only read.
259
        """
1099.1.110 by William Grant
Implement an authorization system in the new framework. This breaks the REST
260
        perms = set()
261
        if user is not None:
262
            perms.add('view')
1101 by William Grant
Privileges (apart from admin) are now offering-local, not global.
263
            if user.admin:
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
264
                perms.add('edit')
1099.1.110 by William Grant
Implement an authorization system in the new framework. This breaks the REST
265
        return perms
266
1195.1.1 by Matt Giuca
ivle.database: Added Subject.active_offerings, which can be used by tools
267
    def active_offerings(self):
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
268
        """Find active offerings for this subject.
269
270
        Return a sequence of currently active offerings for this subject
1195.1.1 by Matt Giuca
ivle.database: Added Subject.active_offerings, which can be used by tools
271
        (offerings whose semester.state is "current"). There should be 0 or 1
272
        elements in this sequence, but it's possible there are more.
273
        """
1195.1.6 by Matt Giuca
ivle.database: Added Subject.offering_for_semester.
274
        return self.offerings.find(Offering.semester_id == Semester.id,
275
                                   Semester.state == u'current')
276
277
    def offering_for_semester(self, year, semester):
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
278
        """Get the offering for the given year/semester, or None.
279
280
        @param year: A string representation of the year.
281
        @param semester: A string representation of the semester.
282
        """
1195.1.6 by Matt Giuca
ivle.database: Added Subject.offering_for_semester.
283
        return self.offerings.find(Offering.semester_id == Semester.id,
284
                               Semester.year == unicode(year),
285
                               Semester.semester == unicode(semester)).one()
1195.1.1 by Matt Giuca
ivle.database: Added Subject.active_offerings, which can be used by tools
286
1080.1.26 by me at id
ivle.database: Add an Enrolment class, and reference(set)s between all of the
287
class Semester(Storm):
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
288
    """A semester in which subjects can be run."""
289
1080.1.25 by me at id
ivle.database: Add Subject, Semester and Offering.
290
    __storm_table__ = "semester"
291
292
    id = Int(primary=True, name="semesterid")
293
    year = Unicode()
294
    semester = Unicode()
1104 by William Grant
Replace Semester.active with Semester.state, allowing more useful state
295
    state = Unicode()
1080.1.25 by me at id
ivle.database: Add Subject, Semester and Offering.
296
1080.1.26 by me at id
ivle.database: Add an Enrolment class, and reference(set)s between all of the
297
    offerings = ReferenceSet(id, 'Offering.semester_id')
1124 by William Grant
Add Semester.enrolments.
298
    enrolments = ReferenceSet(id,
299
                              'Offering.semester_id',
300
                              'Offering.id',
301
                              'Enrolment.offering_id')
1080.1.26 by me at id
ivle.database: Add an Enrolment class, and reference(set)s between all of the
302
1080.1.25 by me at id
ivle.database: Add Subject, Semester and Offering.
303
    __init__ = _kwarg_init
304
305
    def __repr__(self):
306
        return "<%s %s/%s>" % (type(self).__name__, self.year, self.semester)
307
1080.1.26 by me at id
ivle.database: Add an Enrolment class, and reference(set)s between all of the
308
class Offering(Storm):
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
309
    """An offering of a subject in a particular semester."""
310
1080.1.25 by me at id
ivle.database: Add Subject, Semester and Offering.
311
    __storm_table__ = "offering"
312
313
    id = Int(primary=True, name="offeringid")
314
    subject_id = Int(name="subject")
315
    subject = Reference(subject_id, Subject.id)
316
    semester_id = Int(name="semesterid")
317
    semester = Reference(semester_id, Semester.id)
318
    groups_student_permissions = Unicode()
319
1080.1.26 by me at id
ivle.database: Add an Enrolment class, and reference(set)s between all of the
320
    enrolments = ReferenceSet(id, 'Enrolment.offering_id')
1080.1.79 by William Grant
ivle.database.Offering: Add a members ReferenceSet.
321
    members = ReferenceSet(id,
322
                           'Enrolment.offering_id',
323
                           'Enrolment.user_id',
324
                           'User.id')
1080.1.76 by William Grant
ivle.database.Offering: Add project_sets referenceset.
325
    project_sets = ReferenceSet(id, 'ProjectSet.offering_id')
1080.1.26 by me at id
ivle.database: Add an Enrolment class, and reference(set)s between all of the
326
1099.1.180 by Nick Chadwick
This commit changes the tutorial service, which now almost exclusively
327
    worksheets = ReferenceSet(id, 
328
        'Worksheet.offering_id', 
1099.1.212 by Nick Chadwick
Added a new page to display exercises. This will then be modified to
329
        order_by="seq_no"
1099.1.180 by Nick Chadwick
This commit changes the tutorial service, which now almost exclusively
330
    )
1099.1.114 by Nick Chadwick
Modified the database so that exercises are now stored in the database, rather
331
1080.1.25 by me at id
ivle.database: Add Subject, Semester and Offering.
332
    __init__ = _kwarg_init
333
334
    def __repr__(self):
335
        return "<%s %r in %r>" % (type(self).__name__, self.subject,
336
                                  self.semester)
1080.1.26 by me at id
ivle.database: Add an Enrolment class, and reference(set)s between all of the
337
1110 by William Grant
ivle-enrol now allows updating of existing enrolments. It also sets the role.
338
    def enrol(self, user, role=u'student'):
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
339
        """Enrol a user in this offering.
340
341
        Enrolments handle both the staff and student cases. The role controls
342
        the privileges granted by this enrolment.
343
        """
1110 by William Grant
ivle-enrol now allows updating of existing enrolments. It also sets the role.
344
        enrolment = Store.of(self).find(Enrolment,
1080.1.61 by William Grant
ivle.database: Add an Offering.enrol(user) method, which enrols the user in
345
                               Enrolment.user_id == user.id,
1110 by William Grant
ivle-enrol now allows updating of existing enrolments. It also sets the role.
346
                               Enrolment.offering_id == self.id).one()
347
348
        if enrolment is None:
349
            enrolment = Enrolment(user=user, offering=self)
350
            self.enrolments.add(enrolment)
351
352
        enrolment.active = True
353
        enrolment.role = role
1080.1.61 by William Grant
ivle.database: Add an Offering.enrol(user) method, which enrols the user in
354
1132 by William Grant
Add Offering.unenrol(), to unenrol a user from an offering.
355
    def unenrol(self, user):
356
        '''Unenrol a user from this offering.'''
357
        enrolment = Store.of(self).find(Enrolment,
358
                               Enrolment.user_id == user.id,
359
                               Enrolment.offering_id == self.id).one()
360
        Store.of(enrolment).remove(enrolment)
361
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
362
    def get_permissions(self, user):
363
        perms = set()
364
        if user is not None:
1131 by William Grant
Offerings now give 'view' only to user enrolled in them. 'edit' is granted
365
            enrolment = self.get_enrolment(user)
366
            if enrolment or user.admin:
367
                perms.add('view')
368
            if (enrolment and enrolment.role in (u'tutor', u'lecturer')) \
369
               or user.admin:
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
370
                perms.add('edit')
371
        return perms
372
1129 by William Grant
Move the group admin view to per-offering.
373
    def get_enrolment(self, user):
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
374
        """Find the user's enrolment in this offering."""
1129 by William Grant
Move the group admin view to per-offering.
375
        try:
376
            enrolment = self.enrolments.find(user=user).one()
377
        except NotOneError:
378
            enrolment = None
379
380
        return enrolment
381
1165.4.2 by Nick Chadwick
Added a get_assigned method to project sets, to find out which objects
382
    def get_students(self):
1165.3.39 by Nick Chadwick
Added a method onto offering to find all the users enroled in the subject
383
        enrolments = self.enrolments.find(role=u'student')
384
        return [enrolment.user for enrolment in enrolments]
1165.4.2 by Nick Chadwick
Added a get_assigned method to project sets, to find out which objects
385
1080.1.26 by me at id
ivle.database: Add an Enrolment class, and reference(set)s between all of the
386
class Enrolment(Storm):
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
387
    """An enrolment of a user in an offering.
388
389
    This represents the roles of both staff and students.
390
    """
391
1080.1.26 by me at id
ivle.database: Add an Enrolment class, and reference(set)s between all of the
392
    __storm_table__ = "enrolment"
393
    __storm_primary__ = "user_id", "offering_id"
394
395
    user_id = Int(name="loginid")
396
    user = Reference(user_id, User.id)
397
    offering_id = Int(name="offeringid")
398
    offering = Reference(offering_id, Offering.id)
1101 by William Grant
Privileges (apart from admin) are now offering-local, not global.
399
    role = Unicode()
1080.1.26 by me at id
ivle.database: Add an Enrolment class, and reference(set)s between all of the
400
    notes = Unicode()
401
    active = Bool()
402
1080.1.81 by William Grant
ivle.database.Enrolment: Add a groups attribute, containing groups of which
403
    @property
404
    def groups(self):
405
        return Store.of(self).find(ProjectGroup,
406
                ProjectSet.offering_id == self.offering.id,
407
                ProjectGroup.project_set_id == ProjectSet.id,
408
                ProjectGroupMembership.project_group_id == ProjectGroup.id,
409
                ProjectGroupMembership.user_id == self.user.id)
410
1080.1.26 by me at id
ivle.database: Add an Enrolment class, and reference(set)s between all of the
411
    __init__ = _kwarg_init
412
413
    def __repr__(self):
414
        return "<%s %r in %r>" % (type(self).__name__, self.user,
415
                                  self.offering)
1080.1.36 by William Grant
ivle.database: Add ProjectSet, Project, ProjectGroup, ProjectGroupMembership
416
1080.1.39 by Matt Giuca
ivle.database: Added __all__ to the top of the file.
417
# PROJECTS #
418
1080.1.36 by William Grant
ivle.database: Add ProjectSet, Project, ProjectGroup, ProjectGroupMembership
419
class ProjectSet(Storm):
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
420
    """A set of projects that share common groups.
421
422
    Each student project group is attached to a project set. The group is
423
    valid for all projects in the group's set.
424
    """
425
1080.1.36 by William Grant
ivle.database: Add ProjectSet, Project, ProjectGroup, ProjectGroupMembership
426
    __storm_table__ = "project_set"
427
428
    id = Int(name="projectsetid", primary=True)
429
    offering_id = Int(name="offeringid")
430
    offering = Reference(offering_id, Offering.id)
431
    max_students_per_group = Int()
432
1080.1.77 by William Grant
ivle.database.ProjectSet: Add projects and project_groups referencesets.
433
    projects = ReferenceSet(id, 'Project.project_set_id')
434
    project_groups = ReferenceSet(id, 'ProjectGroup.project_set_id')
435
1080.1.36 by William Grant
ivle.database: Add ProjectSet, Project, ProjectGroup, ProjectGroupMembership
436
    __init__ = _kwarg_init
437
438
    def __repr__(self):
439
        return "<%s %d in %r>" % (type(self).__name__, self.id,
440
                                  self.offering)
441
1165.3.2 by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to
442
    def get_permissions(self, user):
443
        return self.offering.get_permissions(user)
444
1165.4.2 by Nick Chadwick
Added a get_assigned method to project sets, to find out which objects
445
    # Get the individuals (groups or users) Assigned to this project
446
    def get_assigned(self):
447
        #If its a Solo project, return everyone in offering
448
        if self.max_students_per_group is None:
449
            return self.offering.get_students()
450
        else:
451
            return self.project_groups
452
1080.1.36 by William Grant
ivle.database: Add ProjectSet, Project, ProjectGroup, ProjectGroupMembership
453
class Project(Storm):
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
454
    """A student project for which submissions can be made."""
455
1080.1.36 by William Grant
ivle.database: Add ProjectSet, Project, ProjectGroup, ProjectGroupMembership
456
    __storm_table__ = "project"
457
458
    id = Int(name="projectid", primary=True)
1165.1.4 by William Grant
Add database classes for assessed, project_extension and project_submission.
459
    name = Unicode()
460
    short_name = Unicode()
1080.1.36 by William Grant
ivle.database: Add ProjectSet, Project, ProjectGroup, ProjectGroupMembership
461
    synopsis = Unicode()
462
    url = Unicode()
463
    project_set_id = Int(name="projectsetid")
464
    project_set = Reference(project_set_id, ProjectSet.id)
465
    deadline = DateTime()
466
1165.1.5 by William Grant
Add relevant ReferenceSets to Project and Assessed.
467
    assesseds = ReferenceSet(id, 'Assessed.project_id')
468
    submissions = ReferenceSet(id,
469
                               'Assessed.project_id',
470
                               'Assessed.id',
471
                               'ProjectSubmission.assessed_id')
472
1080.1.36 by William Grant
ivle.database: Add ProjectSet, Project, ProjectGroup, ProjectGroupMembership
473
    __init__ = _kwarg_init
474
475
    def __repr__(self):
1165.1.4 by William Grant
Add database classes for assessed, project_extension and project_submission.
476
        return "<%s '%s' in %r>" % (type(self).__name__, self.short_name,
1080.1.36 by William Grant
ivle.database: Add ProjectSet, Project, ProjectGroup, ProjectGroupMembership
477
                                  self.project_set.offering)
478
1165.1.19 by William Grant
Add Project.submit(), to create a submission for the principal, path and rev.
479
    def can_submit(self, principal):
480
        return (self in principal.get_projects() and
481
                self.deadline > datetime.datetime.now())
482
1165.1.42 by William Grant
Record who submitted each submission.
483
    def submit(self, principal, path, revision, who):
484
        """Submit a Subversion path and revision to a project.
485
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
486
        @param principal: The owner of the Subversion repository, and the
487
                          entity on behalf of whom the submission is being made
488
        @param path: A path within that repository to submit.
489
        @param revision: The revision of that path to submit.
490
        @param who: The user who is actually making the submission.
1165.1.42 by William Grant
Record who submitted each submission.
491
        """
492
1165.1.19 by William Grant
Add Project.submit(), to create a submission for the principal, path and rev.
493
        if not self.can_submit(principal):
494
            raise Exception('cannot submit')
495
496
        a = Assessed.get(Store.of(self), principal, self)
497
        ps = ProjectSubmission()
498
        ps.path = path
499
        ps.revision = revision
500
        ps.date_submitted = datetime.datetime.now()
501
        ps.assessed = a
1165.1.42 by William Grant
Record who submitted each submission.
502
        ps.submitter = who
1165.1.19 by William Grant
Add Project.submit(), to create a submission for the principal, path and rev.
503
504
        return ps
505
1165.3.2 by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to
506
    def get_permissions(self, user):
507
        return self.project_set.offering.get_permissions(user)
508
1165.1.19 by William Grant
Add Project.submit(), to create a submission for the principal, path and rev.
509
1080.1.36 by William Grant
ivle.database: Add ProjectSet, Project, ProjectGroup, ProjectGroupMembership
510
class ProjectGroup(Storm):
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
511
    """A group of students working together on a project."""
512
1080.1.36 by William Grant
ivle.database: Add ProjectSet, Project, ProjectGroup, ProjectGroupMembership
513
    __storm_table__ = "project_group"
514
515
    id = Int(name="groupid", primary=True)
516
    name = Unicode(name="groupnm")
517
    project_set_id = Int(name="projectsetid")
518
    project_set = Reference(project_set_id, ProjectSet.id)
519
    nick = Unicode()
520
    created_by_id = Int(name="createdby")
521
    created_by = Reference(created_by_id, User.id)
522
    epoch = DateTime()
523
1080.1.78 by William Grant
ivle.database.ProjectGroup.members: Use a ReferenceSet.
524
    members = ReferenceSet(id,
525
                           "ProjectGroupMembership.project_group_id",
526
                           "ProjectGroupMembership.user_id",
527
                           "User.id")
528
1080.1.36 by William Grant
ivle.database: Add ProjectSet, Project, ProjectGroup, ProjectGroupMembership
529
    __init__ = _kwarg_init
530
531
    def __repr__(self):
532
        return "<%s %s in %r>" % (type(self).__name__, self.name,
533
                                  self.project_set.offering)
534
1165.1.26 by William Grant
Add display_name properties to users and groups.
535
    @property
536
    def display_name(self):
537
        return '%s (%s)' % (self.nick, self.name)
538
1165.1.12 by William Grant
Implement ProjectGroup.get_projects(), with identical interface.
539
    def get_projects(self, offering=None, active_only=True):
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
540
        '''Find projects that the group can submit.
1165.1.12 by William Grant
Implement ProjectGroup.get_projects(), with identical interface.
541
542
        This will include projects in the project set which owns this group,
543
        unless the project set disallows groups (in which case none will be
544
        returned).
545
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
546
        @param active_only: Whether to only search active offerings.
547
        @param offering: An optional offering to restrict the search to.
1165.1.12 by William Grant
Implement ProjectGroup.get_projects(), with identical interface.
548
        '''
549
        return Store.of(self).find(Project,
550
            Project.project_set_id == ProjectSet.id,
551
            ProjectSet.id == self.project_set.id,
1165.1.46 by William Grant
Respect the new max_students_per_group semantics in Python.
552
            ProjectSet.max_students_per_group != None,
1165.1.12 by William Grant
Implement ProjectGroup.get_projects(), with identical interface.
553
            ProjectSet.offering_id == Offering.id,
554
            (offering is None) or (Offering.id == offering.id),
555
            Semester.id == Offering.semester_id,
556
            (not active_only) or (Semester.state == u'current'))
557
558
1165.1.7 by William Grant
Grant submit_project on users to themselves, and on groups to their members.
559
    def get_permissions(self, user):
560
        if user.admin or user in self.members:
561
            return set(['submit_project'])
562
        else:
563
            return set()
564
1080.1.36 by William Grant
ivle.database: Add ProjectSet, Project, ProjectGroup, ProjectGroupMembership
565
class ProjectGroupMembership(Storm):
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
566
    """A student's membership in a project group."""
567
1080.1.36 by William Grant
ivle.database: Add ProjectSet, Project, ProjectGroup, ProjectGroupMembership
568
    __storm_table__ = "group_member"
569
    __storm_primary__ = "user_id", "project_group_id"
570
571
    user_id = Int(name="loginid")
572
    user = Reference(user_id, User.id)
573
    project_group_id = Int(name="groupid")
574
    project_group = Reference(project_group_id, ProjectGroup.id)
575
576
    __init__ = _kwarg_init
577
578
    def __repr__(self):
579
        return "<%s %r in %r>" % (type(self).__name__, self.user,
580
                                  self.project_group)
581
1165.1.4 by William Grant
Add database classes for assessed, project_extension and project_submission.
582
class Assessed(Storm):
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
583
    """A composite of a user or group combined with a project.
584
585
    Each project submission and extension refers to an Assessed. It is the
586
    sole specifier of the repository and project.
587
    """
588
1165.1.4 by William Grant
Add database classes for assessed, project_extension and project_submission.
589
    __storm_table__ = "assessed"
590
591
    id = Int(name="assessedid", primary=True)
592
    user_id = Int(name="loginid")
593
    user = Reference(user_id, User.id)
594
    project_group_id = Int(name="groupid")
595
    project_group = Reference(project_group_id, ProjectGroup.id)
596
597
    project_id = Int(name="projectid")
598
    project = Reference(project_id, Project.id)
599
1165.1.5 by William Grant
Add relevant ReferenceSets to Project and Assessed.
600
    extensions = ReferenceSet(id, 'ProjectExtension.assessed_id')
601
    submissions = ReferenceSet(id, 'ProjectSubmission.assessed_id')
602
1165.1.4 by William Grant
Add database classes for assessed, project_extension and project_submission.
603
    def __repr__(self):
604
        return "<%s %r in %r>" % (type(self).__name__,
605
            self.user or self.project_group, self.project)
606
1165.3.12 by William Grant
Add an Assessed.principal property.
607
    @property
608
    def principal(self):
609
        return self.project_group or self.user
610
1165.1.18 by William Grant
Add a method to retrieve or create an Assessed given a principal and project.
611
    @classmethod
612
    def get(cls, store, principal, project):
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
613
        """Find or create an Assessed for the given user or group and project.
614
615
        @param principal: The user or group.
616
        @param project: The project.
617
        """
1165.1.18 by William Grant
Add a method to retrieve or create an Assessed given a principal and project.
618
        t = type(principal)
619
        if t not in (User, ProjectGroup):
620
            raise AssertionError('principal must be User or ProjectGroup')
621
622
        a = store.find(cls,
623
            (t is User) or (cls.project_group_id == principal.id),
624
            (t is ProjectGroup) or (cls.user_id == principal.id),
625
            Project.id == project.id).one()
626
627
        if a is None:
628
            a = cls()
629
            if t is User:
630
                a.user = principal
631
            else:
632
                a.project_group = principal
633
            a.project = project
634
            store.add(a)
635
636
        return a
637
638
1165.1.4 by William Grant
Add database classes for assessed, project_extension and project_submission.
639
class ProjectExtension(Storm):
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
640
    """An extension granted to a user or group on a particular project.
641
642
    The user or group and project are specified by the Assessed.
643
    """
644
1165.1.4 by William Grant
Add database classes for assessed, project_extension and project_submission.
645
    __storm_table__ = "project_extension"
646
647
    id = Int(name="extensionid", primary=True)
648
    assessed_id = Int(name="assessedid")
649
    assessed = Reference(assessed_id, Assessed.id)
650
    deadline = DateTime()
651
    approver_id = Int(name="approver")
652
    approver = Reference(approver_id, User.id)
653
    notes = Unicode()
654
655
class ProjectSubmission(Storm):
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
656
    """A submission from a user or group repository to a particular project.
657
658
    The content of a submission is a single path and revision inside a
659
    repository. The repository is that owned by the submission's user and
660
    group, while the path and revision are explicit.
661
662
    The user or group and project are specified by the Assessed.
663
    """
664
1165.1.4 by William Grant
Add database classes for assessed, project_extension and project_submission.
665
    __storm_table__ = "project_submission"
666
667
    id = Int(name="submissionid", primary=True)
668
    assessed_id = Int(name="assessedid")
669
    assessed = Reference(assessed_id, Assessed.id)
670
    path = Unicode()
671
    revision = Int()
1165.1.42 by William Grant
Record who submitted each submission.
672
    submitter_id = Int(name="submitter")
673
    submitter = Reference(submitter_id, User.id)
1165.1.4 by William Grant
Add database classes for assessed, project_extension and project_submission.
674
    date_submitted = DateTime()
675
676
1080.1.40 by Matt Giuca
ivle.database: Added Worksheet and Exercise classes (more to come in this
677
# WORKSHEETS AND EXERCISES #
678
679
class Exercise(Storm):
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
680
    """An exercise for students to complete in a worksheet.
681
682
    An exercise may be present in any number of worksheets.
683
    """
684
1099.1.195 by William Grant
Rename problem to exercise in the DB.
685
    __storm_table__ = "exercise"
1099.1.114 by Nick Chadwick
Modified the database so that exercises are now stored in the database, rather
686
    id = Unicode(primary=True, name="identifier")
687
    name = Unicode()
688
    description = Unicode()
689
    partial = Unicode()
690
    solution = Unicode()
691
    include = Unicode()
692
    num_rows = Int()
1080.1.40 by Matt Giuca
ivle.database: Added Worksheet and Exercise classes (more to come in this
693
1099.6.2 by Nick Chadwick
Added a listing of all exercises
694
    worksheet_exercises =  ReferenceSet(id,
695
        'WorksheetExercise.exercise_id')
696
1080.1.50 by Matt Giuca
ivle.database: Added WorksheetExercise (relates worksheets to exercises), and
697
    worksheets = ReferenceSet(id,
698
        'WorksheetExercise.exercise_id',
699
        'WorksheetExercise.worksheet_id',
700
        'Worksheet.id'
701
    )
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
702
1099.1.212 by Nick Chadwick
Added a new page to display exercises. This will then be modified to
703
    test_suites = ReferenceSet(id, 
704
        'TestSuite.exercise_id',
705
        order_by='seq_no')
1080.1.50 by Matt Giuca
ivle.database: Added WorksheetExercise (relates worksheets to exercises), and
706
1080.1.40 by Matt Giuca
ivle.database: Added Worksheet and Exercise classes (more to come in this
707
    __init__ = _kwarg_init
708
709
    def __repr__(self):
710
        return "<%s %s>" % (type(self).__name__, self.name)
711
1099.1.210 by Nick Chadwick
Modified the database layer so that exercises have a get_permissions
712
    def get_permissions(self, user):
713
        perms = set()
1099.1.234 by Nick Chadwick
Permissions for editing and deleting exercises now come from the
714
        roles = set()
1099.1.210 by Nick Chadwick
Modified the database layer so that exercises have a get_permissions
715
        if user is not None:
1101 by William Grant
Privileges (apart from admin) are now offering-local, not global.
716
            if user.admin:
1099.1.210 by Nick Chadwick
Modified the database layer so that exercises have a get_permissions
717
                perms.add('edit')
718
                perms.add('view')
1165.2.2 by Nick Chadwick
fixed a bug in which tutors weren't able to edit the exercises of
719
            elif u'lecturer' in set((e.role for e in user.active_enrolments)):
720
                perms.add('edit')
721
                perms.add('view')
722
            elif u'tutor' in set((e.role for e in user.active_enrolments)):
1099.1.234 by Nick Chadwick
Permissions for editing and deleting exercises now come from the
723
                perms.add('edit')
724
                perms.add('view')
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
725
1099.1.210 by Nick Chadwick
Modified the database layer so that exercises have a get_permissions
726
        return perms
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
727
1099.6.3 by Nick Chadwick
Edited the exercise service to delete individual parts of an exercise.
728
    def get_description(self):
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
729
        """Return the description interpreted as reStructuredText."""
1099.1.232 by Nick Chadwick
Removed XML from database. RST now generates a full xml document, not
730
        return rst(self.description)
1080.1.51 by Matt Giuca
tutorial: Replaced call to ivle.db.create_worksheet with local code (roughly
731
1099.1.242 by Nick Chadwick
Fixed a problem with exercise editor, which wasn't editing or adding
732
    def delete(self):
733
        """Deletes the exercise, providing it has no associated worksheets."""
734
        if (self.worksheet_exercises.count() > 0):
735
            raise IntegrityError()
1099.1.233 by Nick Chadwick
Exercise objects in the database module, along with their test cases,
736
        for suite in self.test_suites:
737
            suite.delete()
1099.1.242 by Nick Chadwick
Fixed a problem with exercise editor, which wasn't editing or adding
738
        Store.of(self).remove(self)
1099.1.233 by Nick Chadwick
Exercise objects in the database module, along with their test cases,
739
1080.1.40 by Matt Giuca
ivle.database: Added Worksheet and Exercise classes (more to come in this
740
class Worksheet(Storm):
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
741
    """A worksheet with exercises for students to complete.
742
743
    Worksheets are owned by offerings.
744
    """
745
1080.1.40 by Matt Giuca
ivle.database: Added Worksheet and Exercise classes (more to come in this
746
    __storm_table__ = "worksheet"
747
748
    id = Int(primary=True, name="worksheetid")
1099.1.114 by Nick Chadwick
Modified the database so that exercises are now stored in the database, rather
749
    offering_id = Int(name="offeringid")
1099.4.1 by Nick Chadwick
Working on putting worksheets into the database.
750
    identifier = Unicode()
751
    name = Unicode()
1080.1.40 by Matt Giuca
ivle.database: Added Worksheet and Exercise classes (more to come in this
752
    assessable = Bool()
1099.4.1 by Nick Chadwick
Working on putting worksheets into the database.
753
    data = Unicode()
1099.1.180 by Nick Chadwick
This commit changes the tutorial service, which now almost exclusively
754
    seq_no = Int()
755
    format = Unicode()
1080.1.40 by Matt Giuca
ivle.database: Added Worksheet and Exercise classes (more to come in this
756
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
757
    attempts = ReferenceSet(id, "ExerciseAttempt.worksheetid")
1099.1.118 by William Grant
Fix a bad reference introduced with the worksheet changes.
758
    offering = Reference(offering_id, 'Offering.id')
1099.1.114 by Nick Chadwick
Modified the database so that exercises are now stored in the database, rather
759
1103 by William Grant
Worksheet.worksheet_exercises now only contains active ones.
760
    all_worksheet_exercises = ReferenceSet(id,
761
        'WorksheetExercise.worksheet_id')
762
763
    # Use worksheet_exercises to get access to the *active* WorksheetExercise
764
    # objects binding worksheets to exercises. This is required to access the
1080.1.50 by Matt Giuca
ivle.database: Added WorksheetExercise (relates worksheets to exercises), and
765
    # "optional" field.
1099.1.220 by Nick Chadwick
Merged from trunk
766
1103 by William Grant
Worksheet.worksheet_exercises now only contains active ones.
767
    @property
768
    def worksheet_exercises(self):
769
        return self.all_worksheet_exercises.find(active=True)
1080.1.50 by Matt Giuca
ivle.database: Added WorksheetExercise (relates worksheets to exercises), and
770
1080.1.40 by Matt Giuca
ivle.database: Added Worksheet and Exercise classes (more to come in this
771
    __init__ = _kwarg_init
772
773
    def __repr__(self):
774
        return "<%s %s>" % (type(self).__name__, self.name)
1080.1.47 by Matt Giuca
ivle.database: Added Worksheet.get_by_name method.
775
1099.1.242 by Nick Chadwick
Fixed a problem with exercise editor, which wasn't editing or adding
776
    def remove_all_exercises(self):
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
777
        """Remove all exercises from this worksheet.
778
1080.1.51 by Matt Giuca
tutorial: Replaced call to ivle.db.create_worksheet with local code (roughly
779
        This does not delete the exercises themselves. It just removes them
780
        from the worksheet.
781
        """
1099.1.242 by Nick Chadwick
Fixed a problem with exercise editor, which wasn't editing or adding
782
        store = Store.of(self)
783
        for ws_ex in self.all_worksheet_exercises:
784
            if ws_ex.saves.count() > 0 or ws_ex.attempts.count() > 0:
785
                raise IntegrityError()
1080.1.51 by Matt Giuca
tutorial: Replaced call to ivle.db.create_worksheet with local code (roughly
786
        store.find(WorksheetExercise,
787
            WorksheetExercise.worksheet == self).remove()
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
788
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
789
    def get_permissions(self, user):
790
        return self.offering.get_permissions(user)
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
791
1099.1.220 by Nick Chadwick
Merged from trunk
792
    def get_xml(self):
793
        """Returns the xml of this worksheet, converts from rst if required."""
794
        if self.format == u'rst':
1099.1.232 by Nick Chadwick
Removed XML from database. RST now generates a full xml document, not
795
            ws_xml = rst(self.data)
1099.1.220 by Nick Chadwick
Merged from trunk
796
            return ws_xml
797
        else:
798
            return self.data
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
799
1099.1.242 by Nick Chadwick
Fixed a problem with exercise editor, which wasn't editing or adding
800
    def delete(self):
1099.1.233 by Nick Chadwick
Exercise objects in the database module, along with their test cases,
801
        """Deletes the worksheet, provided it has no attempts on any exercises.
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
802
1099.1.233 by Nick Chadwick
Exercise objects in the database module, along with their test cases,
803
        Returns True if delete succeeded, or False if this worksheet has
804
        attempts attached."""
805
        for ws_ex in self.all_worksheet_exercises:
806
            if ws_ex.saves.count() > 0 or ws_ex.attempts.count() > 0:
1099.1.242 by Nick Chadwick
Fixed a problem with exercise editor, which wasn't editing or adding
807
                raise IntegrityError()
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
808
1099.1.233 by Nick Chadwick
Exercise objects in the database module, along with their test cases,
809
        self.remove_all_exercises()
1099.1.242 by Nick Chadwick
Fixed a problem with exercise editor, which wasn't editing or adding
810
        Store.of(self).remove(self)
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
811
1080.1.50 by Matt Giuca
ivle.database: Added WorksheetExercise (relates worksheets to exercises), and
812
class WorksheetExercise(Storm):
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
813
    """A link between a worksheet and one of its exercises.
814
815
    These may be marked optional, in which case the exercise does not count
816
    for marking purposes. The sequence number is used to order the worksheet
817
    ToC.
818
    """
819
1099.1.195 by William Grant
Rename problem to exercise in the DB.
820
    __storm_table__ = "worksheet_exercise"
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
821
1099.1.195 by William Grant
Rename problem to exercise in the DB.
822
    id = Int(primary=True, name="ws_ex_id")
1080.1.50 by Matt Giuca
ivle.database: Added WorksheetExercise (relates worksheets to exercises), and
823
824
    worksheet_id = Int(name="worksheetid")
825
    worksheet = Reference(worksheet_id, Worksheet.id)
1099.1.195 by William Grant
Rename problem to exercise in the DB.
826
    exercise_id = Unicode(name="exerciseid")
1080.1.50 by Matt Giuca
ivle.database: Added WorksheetExercise (relates worksheets to exercises), and
827
    exercise = Reference(exercise_id, Exercise.id)
828
    optional = Bool()
1099.4.3 by Nick Chadwick
Updated the tutorial service, to now allow users to edit worksheets
829
    active = Bool()
830
    seq_no = Int()
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
831
1099.1.180 by Nick Chadwick
This commit changes the tutorial service, which now almost exclusively
832
    saves = ReferenceSet(id, "ExerciseSave.ws_ex_id")
1099.1.183 by William Grant
Fix a reference typo in ivle.database.
833
    attempts = ReferenceSet(id, "ExerciseAttempt.ws_ex_id")
1080.1.50 by Matt Giuca
ivle.database: Added WorksheetExercise (relates worksheets to exercises), and
834
835
    __init__ = _kwarg_init
836
837
    def __repr__(self):
838
        return "<%s %s in %s>" % (type(self).__name__, self.exercise.name,
1099.4.1 by Nick Chadwick
Working on putting worksheets into the database.
839
                                  self.worksheet.identifier)
1080.1.55 by Matt Giuca
ivle.database: Added ExerciseAttempt and ExerciseSave classes.
840
1131 by William Grant
Offerings now give 'view' only to user enrolled in them. 'edit' is granted
841
    def get_permissions(self, user):
842
        return self.worksheet.get_permissions(user)
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
843
1131 by William Grant
Offerings now give 'view' only to user enrolled in them. 'edit' is granted
844
1080.1.55 by Matt Giuca
ivle.database: Added ExerciseAttempt and ExerciseSave classes.
845
class ExerciseSave(Storm):
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
846
    """A potential exercise solution submitted by a user for storage.
847
848
    This is not an actual tested attempt at an exercise, it's just a save of
849
    the editing session.
850
    """
851
1099.1.195 by William Grant
Rename problem to exercise in the DB.
852
    __storm_table__ = "exercise_save"
1099.1.180 by Nick Chadwick
This commit changes the tutorial service, which now almost exclusively
853
    __storm_primary__ = "ws_ex_id", "user_id"
854
1099.1.195 by William Grant
Rename problem to exercise in the DB.
855
    ws_ex_id = Int(name="ws_ex_id")
1099.1.180 by Nick Chadwick
This commit changes the tutorial service, which now almost exclusively
856
    worksheet_exercise = Reference(ws_ex_id, "WorksheetExercise.id")
857
1080.1.55 by Matt Giuca
ivle.database: Added ExerciseAttempt and ExerciseSave classes.
858
    user_id = Int(name="loginid")
859
    user = Reference(user_id, User.id)
860
    date = DateTime()
861
    text = Unicode()
862
863
    __init__ = _kwarg_init
864
865
    def __repr__(self):
866
        return "<%s %s by %s at %s>" % (type(self).__name__,
867
            self.exercise.name, self.user.login, self.date.strftime("%c"))
868
869
class ExerciseAttempt(ExerciseSave):
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
870
    """An attempt at solving an exercise.
871
872
    This is a special case of ExerciseSave, used when the user submits a
873
    candidate solution. Like an ExerciseSave, it constitutes exercise solution
874
    data.
875
876
    In addition, it contains information about the result of the submission:
877
878
     - complete - True if this submission was successful, rendering this
879
                  exercise complete for this user in this worksheet.
880
     - active   - True if this submission is "active" (usually true).
881
                  Submissions may be de-activated by privileged users for
882
                  special reasons, and then they won't count (either as a
883
                  penalty or success), but will still be stored.
884
    """
885
1099.1.195 by William Grant
Rename problem to exercise in the DB.
886
    __storm_table__ = "exercise_attempt"
1099.1.180 by Nick Chadwick
This commit changes the tutorial service, which now almost exclusively
887
    __storm_primary__ = "ws_ex_id", "user_id", "date"
1080.1.55 by Matt Giuca
ivle.database: Added ExerciseAttempt and ExerciseSave classes.
888
889
    # The "text" field is the same but has a different name in the DB table
890
    # for some reason.
891
    text = Unicode(name="attempt")
892
    complete = Bool()
893
    active = Bool()
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
894
1099.1.113 by William Grant
Give console and tutorial services security declarations.
895
    def get_permissions(self, user):
896
        return set(['view']) if user is self.user else set()
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
897
1099.1.114 by Nick Chadwick
Modified the database so that exercises are now stored in the database, rather
898
class TestSuite(Storm):
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
899
    """A container to group an exercise's test cases.
900
901
    The test suite contains some information on how to test. The function to
902
    test, variables to set and stdin data are stored here.
903
    """
904
1099.1.114 by Nick Chadwick
Modified the database so that exercises are now stored in the database, rather
905
    __storm_table__ = "test_suite"
906
    __storm_primary__ = "exercise_id", "suiteid"
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
907
1099.1.114 by Nick Chadwick
Modified the database so that exercises are now stored in the database, rather
908
    suiteid = Int()
1099.1.195 by William Grant
Rename problem to exercise in the DB.
909
    exercise_id = Unicode(name="exerciseid")
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
910
    description = Unicode()
911
    seq_no = Int()
912
    function = Unicode()
913
    stdin = Unicode()
1099.1.114 by Nick Chadwick
Modified the database so that exercises are now stored in the database, rather
914
    exercise = Reference(exercise_id, Exercise.id)
1099.1.212 by Nick Chadwick
Added a new page to display exercises. This will then be modified to
915
    test_cases = ReferenceSet(suiteid, 'TestCase.suiteid', order_by="seq_no")
916
    variables = ReferenceSet(suiteid, 'TestSuiteVar.suiteid', order_by='arg_no')
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
917
1099.1.242 by Nick Chadwick
Fixed a problem with exercise editor, which wasn't editing or adding
918
    def delete(self):
1099.1.233 by Nick Chadwick
Exercise objects in the database module, along with their test cases,
919
        """Delete this suite, without asking questions."""
920
        for vaariable in self.variables:
921
            variable.delete()
922
        for test_case in self.test_cases:
923
            test_case.delete()
1099.1.242 by Nick Chadwick
Fixed a problem with exercise editor, which wasn't editing or adding
924
        Store.of(self).remove(self)
1099.1.114 by Nick Chadwick
Modified the database so that exercises are now stored in the database, rather
925
926
class TestCase(Storm):
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
927
    """A container for actual tests (see TestCasePart), inside a test suite.
928
929
    It is the lowest level shown to students on their pass/fail status."""
930
1099.1.114 by Nick Chadwick
Modified the database so that exercises are now stored in the database, rather
931
    __storm_table__ = "test_case"
932
    __storm_primary__ = "testid", "suiteid"
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
933
1099.1.114 by Nick Chadwick
Modified the database so that exercises are now stored in the database, rather
934
    testid = Int()
935
    suiteid = Int()
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
936
    suite = Reference(suiteid, "TestSuite.suiteid")
1099.1.114 by Nick Chadwick
Modified the database so that exercises are now stored in the database, rather
937
    passmsg = Unicode()
938
    failmsg = Unicode()
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
939
    test_default = Unicode()
1099.1.114 by Nick Chadwick
Modified the database so that exercises are now stored in the database, rather
940
    seq_no = Int()
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
941
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
942
    parts = ReferenceSet(testid, "TestCasePart.testid")
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
943
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
944
    __init__ = _kwarg_init
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
945
1099.1.242 by Nick Chadwick
Fixed a problem with exercise editor, which wasn't editing or adding
946
    def delete(self):
1099.1.233 by Nick Chadwick
Exercise objects in the database module, along with their test cases,
947
        for part in self.parts:
948
            part.delete()
1099.1.242 by Nick Chadwick
Fixed a problem with exercise editor, which wasn't editing or adding
949
        Store.of(self).remove(self)
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
950
951
class TestSuiteVar(Storm):
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
952
    """A variable used by an exercise test suite.
953
954
    This may represent a function argument or a normal variable.
955
    """
956
1099.1.195 by William Grant
Rename problem to exercise in the DB.
957
    __storm_table__ = "suite_variable"
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
958
    __storm_primary__ = "varid"
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
959
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
960
    varid = Int()
961
    suiteid = Int()
962
    var_name = Unicode()
963
    var_value = Unicode()
964
    var_type = Unicode()
965
    arg_no = Int()
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
966
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
967
    suite = Reference(suiteid, "TestSuite.suiteid")
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
968
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
969
    __init__ = _kwarg_init
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
970
1099.1.242 by Nick Chadwick
Fixed a problem with exercise editor, which wasn't editing or adding
971
    def delete(self):
972
        Store.of(self).remove(self)
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
973
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
974
class TestCasePart(Storm):
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
975
    """An actual piece of code to test an exercise solution."""
976
1099.1.195 by William Grant
Rename problem to exercise in the DB.
977
    __storm_table__ = "test_case_part"
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
978
    __storm_primary__ = "partid"
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
979
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
980
    partid = Int()
981
    testid = Int()
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
982
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
983
    part_type = Unicode()
984
    test_type = Unicode()
985
    data = Unicode()
986
    filename = Unicode()
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
987
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
988
    test = Reference(testid, "TestCase.testid")
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
989
1099.1.114 by Nick Chadwick
Modified the database so that exercises are now stored in the database, rather
990
    __init__ = _kwarg_init
1241 by William Grant
Vastly improve docstrings throughout ivle.database.
991
1099.1.242 by Nick Chadwick
Fixed a problem with exercise editor, which wasn't editing or adding
992
    def delete(self):
993
        Store.of(self).remove(self)