~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
20
"""
21
Database Classes and Utilities for Storm ORM
22
23
This module provides all of the classes which map to database tables.
24
It also provides miscellaneous utility functions for database interaction.
25
"""
26
1080.1.13 by me at id
ivle.database.User: Add an authenticate() method, and a hash_password()
27
import md5
1080.1.15 by me at id
Give ivle.database.User {password,account}_expired attributes, and get
28
import datetime
1080.1.13 by me at id
ivle.database.User: Add an authenticate() method, and a hash_password()
29
1080.1.4 by matt.giuca
ivle.database: Added User class.
30
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
31
                         Reference, ReferenceSet, Bool, Storm, Desc
1080.1.2 by matt.giuca
New module: ivle.database. Classes and utilities for Storm ORM.
32
33
import ivle.conf
1080.1.4 by matt.giuca
ivle.database: Added User class.
34
import ivle.caps
1080.1.2 by matt.giuca
New module: ivle.database. Classes and utilities for Storm ORM.
35
1080.1.39 by Matt Giuca
ivle.database: Added __all__ to the top of the file.
36
__all__ = ['get_store',
37
            'User',
38
            'Subject', 'Semester', 'Offering', 'Enrolment',
39
            'ProjectSet', 'Project', 'ProjectGroup', 'ProjectGroupMembership',
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',
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
42
            'AlreadyEnrolledError', '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
1080.1.2 by matt.giuca
New module: ivle.database. Classes and utilities for Storm ORM.
52
def get_conn_string():
53
    """
54
    Returns the Storm connection string, generated from the conf file.
55
    """
1099.1.174 by William Grant
ivle.database.get_conn_string() now defaults to localhost:5432, rather than
56
57
    clusterstr = ''
58
    if ivle.conf.db_user:
59
        clusterstr += ivle.conf.db_user
60
        if ivle.conf.db_password:
61
            clusterstr += ':' + ivle.conf.db_password
62
        clusterstr += '@'
63
64
    host = ivle.conf.db_host or 'localhost'
65
    port = ivle.conf.db_port or 5432
66
67
    clusterstr += '%s:%d' % (host, port)
68
69
    return "postgres://%s/%s" % (clusterstr, ivle.conf.db_dbname)
1080.1.2 by matt.giuca
New module: ivle.database. Classes and utilities for Storm ORM.
70
71
def get_store():
72
    """
73
    Open a database connection and transaction. Return a storm.store.Store
74
    instance connected to the configured IVLE database.
75
    """
76
    return Store(create_database(get_conn_string()))
1080.1.4 by matt.giuca
ivle.database: Added User class.
77
1080.1.39 by Matt Giuca
ivle.database: Added __all__ to the top of the file.
78
# USERS #
79
1080.1.26 by me at id
ivle.database: Add an Enrolment class, and reference(set)s between all of the
80
class User(Storm):
1080.1.4 by matt.giuca
ivle.database: Added User class.
81
    """
82
    Represents an IVLE user.
83
    """
84
    __storm_table__ = "login"
85
86
    id = Int(primary=True, name="loginid")
87
    login = Unicode()
88
    passhash = Unicode()
89
    state = Unicode()
90
    rolenm = Unicode()
91
    unixid = Int()
92
    nick = Unicode()
93
    pass_exp = DateTime()
94
    acct_exp = DateTime()
95
    last_login = DateTime()
96
    svn_pass = Unicode()
97
    email = Unicode()
98
    fullname = Unicode()
99
    studentid = Unicode()
100
    settings = Unicode()
101
102
    def _get_role(self):
103
        if self.rolenm is None:
104
            return None
105
        return ivle.caps.Role(self.rolenm)
106
    def _set_role(self, value):
107
        if not isinstance(value, ivle.caps.Role):
108
            raise TypeError("role must be an ivle.caps.Role")
109
        self.rolenm = unicode(value)
110
    role = property(_get_role, _set_role)
111
1080.1.25 by me at id
ivle.database: Add Subject, Semester and Offering.
112
    __init__ = _kwarg_init
1080.1.4 by matt.giuca
ivle.database: Added User class.
113
114
    def __repr__(self):
115
        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.
116
1080.1.13 by me at id
ivle.database.User: Add an authenticate() method, and a hash_password()
117
    def authenticate(self, password):
118
        """Validate a given password against this user.
119
120
        Returns True if the given password matches the password hash for this
121
        User, False if it doesn't match, and None if there is no hash for the
122
        user.
123
        """
124
        if self.passhash is None:
125
            return None
126
        return self.hash_password(password) == self.passhash
127
1080.1.7 by matt.giuca
The new ivle.database.User class is now used in Request and usrmgt, which
128
    def hasCap(self, capability):
1080.1.5 by matt.giuca
ivle.database.User: Add the missing methods from ivle.user.User.
129
        """Given a capability (which is a Role object), returns True if this
130
        User has that capability, False otherwise.
131
        """
132
        return self.role.hasCap(capability)
133
1080.1.15 by me at id
Give ivle.database.User {password,account}_expired attributes, and get
134
    @property
135
    def password_expired(self):
1080.1.5 by matt.giuca
ivle.database.User: Add the missing methods from ivle.user.User.
136
        fieldval = self.pass_exp
1080.1.15 by me at id
Give ivle.database.User {password,account}_expired attributes, and get
137
        return fieldval is not None and datetime.datetime.now() > fieldval
138
139
    @property
140
    def account_expired(self):
1080.1.5 by matt.giuca
ivle.database.User: Add the missing methods from ivle.user.User.
141
        fieldval = self.acct_exp
1080.1.15 by me at id
Give ivle.database.User {password,account}_expired attributes, and get
142
        return fieldval is not None and datetime.datetime.now() > fieldval
1080.1.6 by matt.giuca
ivle.database.User: Added get_by_login method.
143
1099.1.121 by William Grant
Don't set req.user unless the login in the session specifies a valid user.
144
    @property
145
    def valid(self):
146
        return self.state == 'enabled' and not self.account_expired
147
1080.1.29 by me at id
ivle.database.User: Order 'enrolments' the same way as 'active_enrolments'.
148
    def _get_enrolments(self, justactive):
1080.1.27 by me at id
ivle.database.User: Add an 'active_enrolments' property, which returns a list
149
        return Store.of(self).find(Enrolment,
150
            Enrolment.user_id == self.id,
1080.1.29 by me at id
ivle.database.User: Order 'enrolments' the same way as 'active_enrolments'.
151
            (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
152
            Enrolment.offering_id == Offering.id,
153
            Offering.semester_id == Semester.id,
154
            Offering.subject_id == Subject.id).order_by(
155
                Desc(Semester.year),
156
                Desc(Semester.semester),
157
                Desc(Subject.code)
158
            )
159
1080.1.68 by William Grant
ivle.database.User: Add a write-only 'password' attribute. When set, it will
160
    def _set_password(self, password):
161
        if password is None:
162
            self.passhash = None
163
        else:
164
            self.passhash = unicode(User.hash_password(password))
165
    password = property(fset=_set_password)
166
1080.1.29 by me at id
ivle.database.User: Order 'enrolments' the same way as 'active_enrolments'.
167
    @property
1080.1.31 by me at id
ivle.database.User: Add 'subjects', an attribute containing currently
168
    def subjects(self):
169
        return Store.of(self).find(Subject,
170
            Enrolment.user_id == self.id,
171
            Enrolment.active == True,
172
            Offering.id == Enrolment.offering_id,
173
            Subject.id == Offering.subject_id).config(distinct=True)
174
1080.1.36 by William Grant
ivle.database: Add ProjectSet, Project, ProjectGroup, ProjectGroupMembership
175
    # TODO: Invitations should be listed too?
176
    def get_groups(self, offering=None):
177
        preds = [
178
            ProjectGroupMembership.user_id == self.id,
179
            ProjectGroup.id == ProjectGroupMembership.project_group_id,
180
        ]
181
        if offering:
182
            preds.extend([
183
                ProjectSet.offering_id == offering.id,
184
                ProjectGroup.project_set_id == ProjectSet.id,
185
            ])
186
        return Store.of(self).find(ProjectGroup, *preds)
187
188
    @property
189
    def groups(self):
190
        return self.get_groups()
191
1080.1.31 by me at id
ivle.database.User: Add 'subjects', an attribute containing currently
192
    @property
1080.1.29 by me at id
ivle.database.User: Order 'enrolments' the same way as 'active_enrolments'.
193
    def active_enrolments(self):
194
        '''A sanely ordered list of the user's active enrolments.'''
195
        return self._get_enrolments(True)
196
197
    @property
198
    def enrolments(self):
199
        '''A sanely ordered list of all of the user's enrolments.'''
200
        return self._get_enrolments(False) 
1080.1.27 by me at id
ivle.database.User: Add an 'active_enrolments' property, which returns a list
201
1080.1.13 by me at id
ivle.database.User: Add an authenticate() method, and a hash_password()
202
    @staticmethod
203
    def hash_password(password):
204
        return md5.md5(password).hexdigest()
205
1080.1.6 by matt.giuca
ivle.database.User: Added get_by_login method.
206
    @classmethod
207
    def get_by_login(cls, store, login):
208
        """
209
        Get the User from the db associated with a given store and
210
        login.
211
        """
1080.1.7 by matt.giuca
The new ivle.database.User class is now used in Request and usrmgt, which
212
        return store.find(cls, cls.login == unicode(login)).one()
1080.1.25 by me at id
ivle.database: Add Subject, Semester and Offering.
213
1099.1.110 by William Grant
Implement an authorization system in the new framework. This breaks the REST
214
    def get_permissions(self, user):
215
        if user and user.rolenm == 'admin' or user is self:
216
            return set(['view', 'edit'])
217
        else:
218
            return set()
219
1080.1.39 by Matt Giuca
ivle.database: Added __all__ to the top of the file.
220
# SUBJECTS AND ENROLMENTS #
221
1080.1.26 by me at id
ivle.database: Add an Enrolment class, and reference(set)s between all of the
222
class Subject(Storm):
1080.1.25 by me at id
ivle.database: Add Subject, Semester and Offering.
223
    __storm_table__ = "subject"
224
225
    id = Int(primary=True, name="subjectid")
226
    code = Unicode(name="subj_code")
227
    name = Unicode(name="subj_name")
228
    short_name = Unicode(name="subj_short_name")
229
    url = Unicode()
230
1080.1.26 by me at id
ivle.database: Add an Enrolment class, and reference(set)s between all of the
231
    offerings = ReferenceSet(id, 'Offering.subject_id')
232
1080.1.25 by me at id
ivle.database: Add Subject, Semester and Offering.
233
    __init__ = _kwarg_init
234
235
    def __repr__(self):
236
        return "<%s '%s'>" % (type(self).__name__, self.short_name)
237
1099.1.110 by William Grant
Implement an authorization system in the new framework. This breaks the REST
238
    def get_permissions(self, user):
239
        perms = set()
240
        if user is not None:
241
            perms.add('view')
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
242
            if user.rolenm == 'admin':
243
                perms.add('edit')
1099.1.110 by William Grant
Implement an authorization system in the new framework. This breaks the REST
244
        return perms
245
1080.1.26 by me at id
ivle.database: Add an Enrolment class, and reference(set)s between all of the
246
class Semester(Storm):
1080.1.25 by me at id
ivle.database: Add Subject, Semester and Offering.
247
    __storm_table__ = "semester"
248
249
    id = Int(primary=True, name="semesterid")
250
    year = Unicode()
251
    semester = Unicode()
252
    active = Bool()
253
1080.1.26 by me at id
ivle.database: Add an Enrolment class, and reference(set)s between all of the
254
    offerings = ReferenceSet(id, 'Offering.semester_id')
255
1080.1.25 by me at id
ivle.database: Add Subject, Semester and Offering.
256
    __init__ = _kwarg_init
257
258
    def __repr__(self):
259
        return "<%s %s/%s>" % (type(self).__name__, self.year, self.semester)
260
1080.1.26 by me at id
ivle.database: Add an Enrolment class, and reference(set)s between all of the
261
class Offering(Storm):
1080.1.25 by me at id
ivle.database: Add Subject, Semester and Offering.
262
    __storm_table__ = "offering"
263
264
    id = Int(primary=True, name="offeringid")
265
    subject_id = Int(name="subject")
266
    subject = Reference(subject_id, Subject.id)
267
    semester_id = Int(name="semesterid")
268
    semester = Reference(semester_id, Semester.id)
269
    groups_student_permissions = Unicode()
270
1080.1.26 by me at id
ivle.database: Add an Enrolment class, and reference(set)s between all of the
271
    enrolments = ReferenceSet(id, 'Enrolment.offering_id')
1080.1.79 by William Grant
ivle.database.Offering: Add a members ReferenceSet.
272
    members = ReferenceSet(id,
273
                           'Enrolment.offering_id',
274
                           'Enrolment.user_id',
275
                           'User.id')
1080.1.76 by William Grant
ivle.database.Offering: Add project_sets referenceset.
276
    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
277
1099.1.180 by Nick Chadwick
This commit changes the tutorial service, which now almost exclusively
278
    worksheets = ReferenceSet(id, 
279
        'Worksheet.offering_id', 
280
        order_by="Worksheet.seq_no"
281
    )
1099.1.114 by Nick Chadwick
Modified the database so that exercises are now stored in the database, rather
282
1080.1.25 by me at id
ivle.database: Add Subject, Semester and Offering.
283
    __init__ = _kwarg_init
284
285
    def __repr__(self):
286
        return "<%s %r in %r>" % (type(self).__name__, self.subject,
287
                                  self.semester)
1080.1.26 by me at id
ivle.database: Add an Enrolment class, and reference(set)s between all of the
288
1080.1.61 by William Grant
ivle.database: Add an Offering.enrol(user) method, which enrols the user in
289
    def enrol(self, user):
290
        '''Enrol a user in this offering.'''
291
        # We'll get a horrible database constraint violation error if we try
292
        # to add a second enrolment.
293
        if Store.of(self).find(Enrolment,
294
                               Enrolment.user_id == user.id,
295
                               Enrolment.offering_id == self.id).count() == 1:
296
            raise AlreadyEnrolledError()
297
298
        e = Enrolment(user=user, offering=self, active=True)
299
        self.enrolments.add(e)
300
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
301
    def get_permissions(self, user):
302
        perms = set()
303
        if user is not None:
304
            perms.add('view')
1099.1.211 by Nick Chadwick
Fixed database so that lecturers can edit worksheets
305
            if user.rolenm in ('admin', 'lecturer'):
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
306
                perms.add('edit')
307
        return perms
308
1080.1.26 by me at id
ivle.database: Add an Enrolment class, and reference(set)s between all of the
309
class Enrolment(Storm):
310
    __storm_table__ = "enrolment"
311
    __storm_primary__ = "user_id", "offering_id"
312
313
    user_id = Int(name="loginid")
314
    user = Reference(user_id, User.id)
315
    offering_id = Int(name="offeringid")
316
    offering = Reference(offering_id, Offering.id)
317
    notes = Unicode()
318
    active = Bool()
319
1080.1.81 by William Grant
ivle.database.Enrolment: Add a groups attribute, containing groups of which
320
    @property
321
    def groups(self):
322
        return Store.of(self).find(ProjectGroup,
323
                ProjectSet.offering_id == self.offering.id,
324
                ProjectGroup.project_set_id == ProjectSet.id,
325
                ProjectGroupMembership.project_group_id == ProjectGroup.id,
326
                ProjectGroupMembership.user_id == self.user.id)
327
1080.1.26 by me at id
ivle.database: Add an Enrolment class, and reference(set)s between all of the
328
    __init__ = _kwarg_init
329
330
    def __repr__(self):
331
        return "<%s %r in %r>" % (type(self).__name__, self.user,
332
                                  self.offering)
1080.1.36 by William Grant
ivle.database: Add ProjectSet, Project, ProjectGroup, ProjectGroupMembership
333
1080.1.61 by William Grant
ivle.database: Add an Offering.enrol(user) method, which enrols the user in
334
class AlreadyEnrolledError(Exception):
335
    pass
336
1080.1.39 by Matt Giuca
ivle.database: Added __all__ to the top of the file.
337
# PROJECTS #
338
1080.1.36 by William Grant
ivle.database: Add ProjectSet, Project, ProjectGroup, ProjectGroupMembership
339
class ProjectSet(Storm):
340
    __storm_table__ = "project_set"
341
342
    id = Int(name="projectsetid", primary=True)
343
    offering_id = Int(name="offeringid")
344
    offering = Reference(offering_id, Offering.id)
345
    max_students_per_group = Int()
346
1080.1.77 by William Grant
ivle.database.ProjectSet: Add projects and project_groups referencesets.
347
    projects = ReferenceSet(id, 'Project.project_set_id')
348
    project_groups = ReferenceSet(id, 'ProjectGroup.project_set_id')
349
1080.1.36 by William Grant
ivle.database: Add ProjectSet, Project, ProjectGroup, ProjectGroupMembership
350
    __init__ = _kwarg_init
351
352
    def __repr__(self):
353
        return "<%s %d in %r>" % (type(self).__name__, self.id,
354
                                  self.offering)
355
356
class Project(Storm):
357
    __storm_table__ = "project"
358
359
    id = Int(name="projectid", primary=True)
360
    synopsis = Unicode()
361
    url = Unicode()
362
    project_set_id = Int(name="projectsetid")
363
    project_set = Reference(project_set_id, ProjectSet.id)
364
    deadline = DateTime()
365
366
    __init__ = _kwarg_init
367
368
    def __repr__(self):
369
        return "<%s '%s' in %r>" % (type(self).__name__, self.synopsis,
370
                                  self.project_set.offering)
371
372
class ProjectGroup(Storm):
373
    __storm_table__ = "project_group"
374
375
    id = Int(name="groupid", primary=True)
376
    name = Unicode(name="groupnm")
377
    project_set_id = Int(name="projectsetid")
378
    project_set = Reference(project_set_id, ProjectSet.id)
379
    nick = Unicode()
380
    created_by_id = Int(name="createdby")
381
    created_by = Reference(created_by_id, User.id)
382
    epoch = DateTime()
383
1080.1.78 by William Grant
ivle.database.ProjectGroup.members: Use a ReferenceSet.
384
    members = ReferenceSet(id,
385
                           "ProjectGroupMembership.project_group_id",
386
                           "ProjectGroupMembership.user_id",
387
                           "User.id")
388
1080.1.36 by William Grant
ivle.database: Add ProjectSet, Project, ProjectGroup, ProjectGroupMembership
389
    __init__ = _kwarg_init
390
391
    def __repr__(self):
392
        return "<%s %s in %r>" % (type(self).__name__, self.name,
393
                                  self.project_set.offering)
394
395
class ProjectGroupMembership(Storm):
396
    __storm_table__ = "group_member"
397
    __storm_primary__ = "user_id", "project_group_id"
398
399
    user_id = Int(name="loginid")
400
    user = Reference(user_id, User.id)
401
    project_group_id = Int(name="groupid")
402
    project_group = Reference(project_group_id, ProjectGroup.id)
403
404
    __init__ = _kwarg_init
405
406
    def __repr__(self):
407
        return "<%s %r in %r>" % (type(self).__name__, self.user,
408
                                  self.project_group)
409
1080.1.40 by Matt Giuca
ivle.database: Added Worksheet and Exercise classes (more to come in this
410
# WORKSHEETS AND EXERCISES #
411
412
class Exercise(Storm):
1099.1.195 by William Grant
Rename problem to exercise in the DB.
413
    __storm_table__ = "exercise"
1099.1.114 by Nick Chadwick
Modified the database so that exercises are now stored in the database, rather
414
    id = Unicode(primary=True, name="identifier")
415
    name = Unicode()
416
    description = Unicode()
417
    partial = Unicode()
418
    solution = Unicode()
419
    include = Unicode()
420
    num_rows = Int()
1080.1.40 by Matt Giuca
ivle.database: Added Worksheet and Exercise classes (more to come in this
421
1080.1.50 by Matt Giuca
ivle.database: Added WorksheetExercise (relates worksheets to exercises), and
422
    worksheets = ReferenceSet(id,
423
        'WorksheetExercise.exercise_id',
424
        'WorksheetExercise.worksheet_id',
425
        'Worksheet.id'
426
    )
1099.1.114 by Nick Chadwick
Modified the database so that exercises are now stored in the database, rather
427
    
428
    test_suites = ReferenceSet(id, 'TestSuite.exercise_id')
1080.1.50 by Matt Giuca
ivle.database: Added WorksheetExercise (relates worksheets to exercises), and
429
1080.1.40 by Matt Giuca
ivle.database: Added Worksheet and Exercise classes (more to come in this
430
    __init__ = _kwarg_init
431
432
    def __repr__(self):
433
        return "<%s %s>" % (type(self).__name__, self.name)
434
1099.1.210 by Nick Chadwick
Modified the database layer so that exercises have a get_permissions
435
    def get_permissions(self, user):
436
        perms = set()
437
        if user is not None:
1099.1.211 by Nick Chadwick
Fixed database so that lecturers can edit worksheets
438
            if user.rolenm in ('admin', 'lecturer'):
1099.1.210 by Nick Chadwick
Modified the database layer so that exercises have a get_permissions
439
                perms.add('edit')
440
                perms.add('view')
441
        return perms
1080.1.51 by Matt Giuca
tutorial: Replaced call to ivle.db.create_worksheet with local code (roughly
442
1080.1.40 by Matt Giuca
ivle.database: Added Worksheet and Exercise classes (more to come in this
443
class Worksheet(Storm):
444
    __storm_table__ = "worksheet"
445
446
    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
447
    offering_id = Int(name="offeringid")
1099.4.1 by Nick Chadwick
Working on putting worksheets into the database.
448
    identifier = Unicode()
449
    name = Unicode()
1080.1.40 by Matt Giuca
ivle.database: Added Worksheet and Exercise classes (more to come in this
450
    assessable = Bool()
1099.4.1 by Nick Chadwick
Working on putting worksheets into the database.
451
    data = Unicode()
1099.1.180 by Nick Chadwick
This commit changes the tutorial service, which now almost exclusively
452
    seq_no = Int()
453
    format = Unicode()
1080.1.40 by Matt Giuca
ivle.database: Added Worksheet and Exercise classes (more to come in this
454
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
455
    attempts = ReferenceSet(id, "ExerciseAttempt.worksheetid")
1099.1.118 by William Grant
Fix a bad reference introduced with the worksheet changes.
456
    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
457
1080.1.50 by Matt Giuca
ivle.database: Added WorksheetExercise (relates worksheets to exercises), and
458
    # Use worksheet_exercises to get access to the WorksheetExercise objects
459
    # binding worksheets to exercises. This is required to access the
460
    # "optional" field.
461
    worksheet_exercises = ReferenceSet(id,
462
        'WorksheetExercise.worksheet_id')
1099.1.114 by Nick Chadwick
Modified the database so that exercises are now stored in the database, rather
463
        
1080.1.50 by Matt Giuca
ivle.database: Added WorksheetExercise (relates worksheets to exercises), and
464
1080.1.40 by Matt Giuca
ivle.database: Added Worksheet and Exercise classes (more to come in this
465
    __init__ = _kwarg_init
466
467
    def __repr__(self):
468
        return "<%s %s>" % (type(self).__name__, self.name)
1080.1.47 by Matt Giuca
ivle.database: Added Worksheet.get_by_name method.
469
470
    # XXX Refactor this - make it an instance method of Subject rather than a
471
    # class method of Worksheet. Can't do that now because Subject isn't
472
    # linked referentially to the Worksheet.
473
    @classmethod
474
    def get_by_name(cls, store, subjectname, worksheetname):
475
        """
476
        Get the Worksheet from the db associated with a given store, subject
477
        name and worksheet name.
478
        """
479
        return store.find(cls, cls.subject == unicode(subjectname),
480
            cls.name == unicode(worksheetname)).one()
1080.1.50 by Matt Giuca
ivle.database: Added WorksheetExercise (relates worksheets to exercises), and
481
1080.1.51 by Matt Giuca
tutorial: Replaced call to ivle.db.create_worksheet with local code (roughly
482
    def remove_all_exercises(self, store):
483
        """
484
        Remove all exercises from this worksheet.
485
        This does not delete the exercises themselves. It just removes them
486
        from the worksheet.
487
        """
488
        store.find(WorksheetExercise,
489
            WorksheetExercise.worksheet == self).remove()
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
490
            
491
    def get_permissions(self, user):
492
        return self.offering.get_permissions(user)
1080.1.51 by Matt Giuca
tutorial: Replaced call to ivle.db.create_worksheet with local code (roughly
493
1080.1.50 by Matt Giuca
ivle.database: Added WorksheetExercise (relates worksheets to exercises), and
494
class WorksheetExercise(Storm):
1099.1.195 by William Grant
Rename problem to exercise in the DB.
495
    __storm_table__ = "worksheet_exercise"
1099.4.4 by Nick Chadwick
Made what should (hopefully) be the last changes to the database schema.
496
    
1099.1.195 by William Grant
Rename problem to exercise in the DB.
497
    id = Int(primary=True, name="ws_ex_id")
1080.1.50 by Matt Giuca
ivle.database: Added WorksheetExercise (relates worksheets to exercises), and
498
499
    worksheet_id = Int(name="worksheetid")
500
    worksheet = Reference(worksheet_id, Worksheet.id)
1099.1.195 by William Grant
Rename problem to exercise in the DB.
501
    exercise_id = Unicode(name="exerciseid")
1080.1.50 by Matt Giuca
ivle.database: Added WorksheetExercise (relates worksheets to exercises), and
502
    exercise = Reference(exercise_id, Exercise.id)
503
    optional = Bool()
1099.4.3 by Nick Chadwick
Updated the tutorial service, to now allow users to edit worksheets
504
    active = Bool()
505
    seq_no = Int()
1099.1.180 by Nick Chadwick
This commit changes the tutorial service, which now almost exclusively
506
    
507
    saves = ReferenceSet(id, "ExerciseSave.ws_ex_id")
1099.1.183 by William Grant
Fix a reference typo in ivle.database.
508
    attempts = ReferenceSet(id, "ExerciseAttempt.ws_ex_id")
1080.1.50 by Matt Giuca
ivle.database: Added WorksheetExercise (relates worksheets to exercises), and
509
510
    __init__ = _kwarg_init
511
512
    def __repr__(self):
513
        return "<%s %s in %s>" % (type(self).__name__, self.exercise.name,
1099.4.1 by Nick Chadwick
Working on putting worksheets into the database.
514
                                  self.worksheet.identifier)
1080.1.55 by Matt Giuca
ivle.database: Added ExerciseAttempt and ExerciseSave classes.
515
516
class ExerciseSave(Storm):
517
    """
518
    Represents a potential solution to an exercise that a user has submitted
519
    to the server for storage.
520
    A basic ExerciseSave is just the current saved text for this exercise for
521
    this user (doesn't count towards their attempts).
522
    ExerciseSave may be extended with additional semantics (such as
523
    ExerciseAttempt).
524
    """
1099.1.195 by William Grant
Rename problem to exercise in the DB.
525
    __storm_table__ = "exercise_save"
1099.1.180 by Nick Chadwick
This commit changes the tutorial service, which now almost exclusively
526
    __storm_primary__ = "ws_ex_id", "user_id"
527
1099.1.195 by William Grant
Rename problem to exercise in the DB.
528
    ws_ex_id = Int(name="ws_ex_id")
1099.1.180 by Nick Chadwick
This commit changes the tutorial service, which now almost exclusively
529
    worksheet_exercise = Reference(ws_ex_id, "WorksheetExercise.id")
530
1080.1.55 by Matt Giuca
ivle.database: Added ExerciseAttempt and ExerciseSave classes.
531
    user_id = Int(name="loginid")
532
    user = Reference(user_id, User.id)
533
    date = DateTime()
534
    text = Unicode()
535
536
    __init__ = _kwarg_init
537
538
    def __repr__(self):
539
        return "<%s %s by %s at %s>" % (type(self).__name__,
540
            self.exercise.name, self.user.login, self.date.strftime("%c"))
541
542
class ExerciseAttempt(ExerciseSave):
543
    """
544
    An ExerciseAttempt is a special case of an ExerciseSave. Like an
545
    ExerciseSave, it constitutes exercise solution data that the user has
546
    submitted to the server for storage.
547
    In addition, it contains additional information about the submission.
548
    complete - True if this submission was successful, rendering this exercise
549
        complete for this user.
550
    active - True if this submission is "active" (usually true). Submissions
551
        may be de-activated by privileged users for special reasons, and then
552
        they won't count (either as a penalty or success), but will still be
553
        stored.
554
    """
1099.1.195 by William Grant
Rename problem to exercise in the DB.
555
    __storm_table__ = "exercise_attempt"
1099.1.180 by Nick Chadwick
This commit changes the tutorial service, which now almost exclusively
556
    __storm_primary__ = "ws_ex_id", "user_id", "date"
1080.1.55 by Matt Giuca
ivle.database: Added ExerciseAttempt and ExerciseSave classes.
557
558
    # The "text" field is the same but has a different name in the DB table
559
    # for some reason.
560
    text = Unicode(name="attempt")
561
    complete = Bool()
562
    active = Bool()
1099.1.114 by Nick Chadwick
Modified the database so that exercises are now stored in the database, rather
563
    
1099.1.113 by William Grant
Give console and tutorial services security declarations.
564
    def get_permissions(self, user):
565
        return set(['view']) if user is self.user else set()
1099.1.114 by Nick Chadwick
Modified the database so that exercises are now stored in the database, rather
566
  
567
class TestSuite(Storm):
568
    """A Testsuite acts as a container for the test cases of an exercise."""
569
    __storm_table__ = "test_suite"
570
    __storm_primary__ = "exercise_id", "suiteid"
571
    
572
    suiteid = Int()
1099.1.195 by William Grant
Rename problem to exercise in the DB.
573
    exercise_id = Unicode(name="exerciseid")
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
574
    description = Unicode()
575
    seq_no = Int()
576
    function = Unicode()
577
    stdin = Unicode()
1099.1.114 by Nick Chadwick
Modified the database so that exercises are now stored in the database, rather
578
    exercise = Reference(exercise_id, Exercise.id)
579
    test_cases = ReferenceSet(suiteid, 'TestCase.suiteid')
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
580
    variables = ReferenceSet(suiteid, 'TestSuiteVar.suiteid')
1099.1.114 by Nick Chadwick
Modified the database so that exercises are now stored in the database, rather
581
582
class TestCase(Storm):
583
    """A TestCase is a member of a TestSuite.
584
    
585
    It contains the data necessary to check if an exercise is correct"""
586
    __storm_table__ = "test_case"
587
    __storm_primary__ = "testid", "suiteid"
588
    
589
    testid = Int()
590
    suiteid = Int()
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
591
    suite = Reference(suiteid, "TestSuite.suiteid")
1099.1.114 by Nick Chadwick
Modified the database so that exercises are now stored in the database, rather
592
    passmsg = Unicode()
593
    failmsg = Unicode()
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
594
    test_default = Unicode()
1099.1.114 by Nick Chadwick
Modified the database so that exercises are now stored in the database, rather
595
    seq_no = Int()
596
    
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
597
    parts = ReferenceSet(testid, "TestCasePart.testid")
598
    
599
    __init__ = _kwarg_init
600
601
class TestSuiteVar(Storm):
602
    """A container for the arguments of a Test Suite"""
1099.1.195 by William Grant
Rename problem to exercise in the DB.
603
    __storm_table__ = "suite_variable"
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
604
    __storm_primary__ = "varid"
605
    
606
    varid = Int()
607
    suiteid = Int()
608
    var_name = Unicode()
609
    var_value = Unicode()
610
    var_type = Unicode()
611
    arg_no = Int()
612
    
613
    suite = Reference(suiteid, "TestSuite.suiteid")
614
    
615
    __init__ = _kwarg_init
616
    
617
class TestCasePart(Storm):
618
    """A container for the test elements of a Test Case"""
1099.1.195 by William Grant
Rename problem to exercise in the DB.
619
    __storm_table__ = "test_case_part"
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
620
    __storm_primary__ = "partid"
621
    
622
    partid = Int()
623
    testid = Int()
624
    
625
    part_type = Unicode()
626
    test_type = Unicode()
627
    data = Unicode()
628
    filename = Unicode()
629
    
630
    test = Reference(testid, "TestCase.testid")
631
    
1099.1.114 by Nick Chadwick
Modified the database so that exercises are now stored in the database, rather
632
    __init__ = _kwarg_init