~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',
42
            'AlreadyEnrolledError'
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
    """
56
    return "postgres://%s:%s@%s:%d/%s" % (ivle.conf.db_user,
57
        ivle.conf.db_password, ivle.conf.db_host, ivle.conf.db_port,
58
        ivle.conf.db_dbname)
59
60
def get_store():
61
    """
62
    Open a database connection and transaction. Return a storm.store.Store
63
    instance connected to the configured IVLE database.
64
    """
65
    return Store(create_database(get_conn_string()))
1080.1.4 by matt.giuca
ivle.database: Added User class.
66
1080.1.39 by Matt Giuca
ivle.database: Added __all__ to the top of the file.
67
# USERS #
68
1080.1.26 by me at id
ivle.database: Add an Enrolment class, and reference(set)s between all of the
69
class User(Storm):
1080.1.4 by matt.giuca
ivle.database: Added User class.
70
    """
71
    Represents an IVLE user.
72
    """
73
    __storm_table__ = "login"
74
75
    id = Int(primary=True, name="loginid")
76
    login = Unicode()
77
    passhash = Unicode()
78
    state = Unicode()
79
    rolenm = Unicode()
80
    unixid = Int()
81
    nick = Unicode()
82
    pass_exp = DateTime()
83
    acct_exp = DateTime()
84
    last_login = DateTime()
85
    svn_pass = Unicode()
86
    email = Unicode()
87
    fullname = Unicode()
88
    studentid = Unicode()
89
    settings = Unicode()
90
91
    def _get_role(self):
92
        if self.rolenm is None:
93
            return None
94
        return ivle.caps.Role(self.rolenm)
95
    def _set_role(self, value):
96
        if not isinstance(value, ivle.caps.Role):
97
            raise TypeError("role must be an ivle.caps.Role")
98
        self.rolenm = unicode(value)
99
    role = property(_get_role, _set_role)
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.7 by matt.giuca
The new ivle.database.User class is now used in Request and usrmgt, which
117
    def hasCap(self, capability):
1080.1.5 by matt.giuca
ivle.database.User: Add the missing methods from ivle.user.User.
118
        """Given a capability (which is a Role object), returns True if this
119
        User has that capability, False otherwise.
120
        """
121
        return self.role.hasCap(capability)
122
1080.1.15 by me at id
Give ivle.database.User {password,account}_expired attributes, and get
123
    @property
124
    def password_expired(self):
1080.1.5 by matt.giuca
ivle.database.User: Add the missing methods from ivle.user.User.
125
        fieldval = self.pass_exp
1080.1.15 by me at id
Give ivle.database.User {password,account}_expired attributes, and get
126
        return fieldval is not None and datetime.datetime.now() > fieldval
127
128
    @property
129
    def account_expired(self):
1080.1.5 by matt.giuca
ivle.database.User: Add the missing methods from ivle.user.User.
130
        fieldval = self.acct_exp
1080.1.15 by me at id
Give ivle.database.User {password,account}_expired attributes, and get
131
        return fieldval is not None and datetime.datetime.now() > fieldval
1080.1.6 by matt.giuca
ivle.database.User: Added get_by_login method.
132
1080.1.29 by me at id
ivle.database.User: Order 'enrolments' the same way as 'active_enrolments'.
133
    def _get_enrolments(self, justactive):
1080.1.27 by me at id
ivle.database.User: Add an 'active_enrolments' property, which returns a list
134
        return Store.of(self).find(Enrolment,
135
            Enrolment.user_id == self.id,
1080.1.29 by me at id
ivle.database.User: Order 'enrolments' the same way as 'active_enrolments'.
136
            (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
137
            Enrolment.offering_id == Offering.id,
138
            Offering.semester_id == Semester.id,
139
            Offering.subject_id == Subject.id).order_by(
140
                Desc(Semester.year),
141
                Desc(Semester.semester),
142
                Desc(Subject.code)
143
            )
144
1080.1.68 by William Grant
ivle.database.User: Add a write-only 'password' attribute. When set, it will
145
    def _set_password(self, password):
146
        if password is None:
147
            self.passhash = None
148
        else:
149
            self.passhash = unicode(User.hash_password(password))
150
    password = property(fset=_set_password)
151
1080.1.29 by me at id
ivle.database.User: Order 'enrolments' the same way as 'active_enrolments'.
152
    @property
1080.1.31 by me at id
ivle.database.User: Add 'subjects', an attribute containing currently
153
    def subjects(self):
154
        return Store.of(self).find(Subject,
155
            Enrolment.user_id == self.id,
156
            Enrolment.active == True,
157
            Offering.id == Enrolment.offering_id,
158
            Subject.id == Offering.subject_id).config(distinct=True)
159
1080.1.36 by William Grant
ivle.database: Add ProjectSet, Project, ProjectGroup, ProjectGroupMembership
160
    # TODO: Invitations should be listed too?
161
    def get_groups(self, offering=None):
162
        preds = [
163
            ProjectGroupMembership.user_id == self.id,
164
            ProjectGroup.id == ProjectGroupMembership.project_group_id,
165
        ]
166
        if offering:
167
            preds.extend([
168
                ProjectSet.offering_id == offering.id,
169
                ProjectGroup.project_set_id == ProjectSet.id,
170
            ])
171
        return Store.of(self).find(ProjectGroup, *preds)
172
173
    @property
174
    def groups(self):
175
        return self.get_groups()
176
1080.1.31 by me at id
ivle.database.User: Add 'subjects', an attribute containing currently
177
    @property
1080.1.29 by me at id
ivle.database.User: Order 'enrolments' the same way as 'active_enrolments'.
178
    def active_enrolments(self):
179
        '''A sanely ordered list of the user's active enrolments.'''
180
        return self._get_enrolments(True)
181
182
    @property
183
    def enrolments(self):
184
        '''A sanely ordered list of all of the user's enrolments.'''
185
        return self._get_enrolments(False) 
1080.1.27 by me at id
ivle.database.User: Add an 'active_enrolments' property, which returns a list
186
1080.1.13 by me at id
ivle.database.User: Add an authenticate() method, and a hash_password()
187
    @staticmethod
188
    def hash_password(password):
189
        return md5.md5(password).hexdigest()
190
1080.1.6 by matt.giuca
ivle.database.User: Added get_by_login method.
191
    @classmethod
192
    def get_by_login(cls, store, login):
193
        """
194
        Get the User from the db associated with a given store and
195
        login.
196
        """
1080.1.7 by matt.giuca
The new ivle.database.User class is now used in Request and usrmgt, which
197
        return store.find(cls, cls.login == unicode(login)).one()
1080.1.25 by me at id
ivle.database: Add Subject, Semester and Offering.
198
1099.1.110 by William Grant
Implement an authorization system in the new framework. This breaks the REST
199
    def get_permissions(self, user):
200
        if user and user.rolenm == 'admin' or user is self:
201
            return set(['view', 'edit'])
202
        else:
203
            return set()
204
1080.1.39 by Matt Giuca
ivle.database: Added __all__ to the top of the file.
205
# SUBJECTS AND ENROLMENTS #
206
1080.1.26 by me at id
ivle.database: Add an Enrolment class, and reference(set)s between all of the
207
class Subject(Storm):
1080.1.25 by me at id
ivle.database: Add Subject, Semester and Offering.
208
    __storm_table__ = "subject"
209
210
    id = Int(primary=True, name="subjectid")
211
    code = Unicode(name="subj_code")
212
    name = Unicode(name="subj_name")
213
    short_name = Unicode(name="subj_short_name")
214
    url = Unicode()
215
1080.1.26 by me at id
ivle.database: Add an Enrolment class, and reference(set)s between all of the
216
    offerings = ReferenceSet(id, 'Offering.subject_id')
217
1080.1.25 by me at id
ivle.database: Add Subject, Semester and Offering.
218
    __init__ = _kwarg_init
219
220
    def __repr__(self):
221
        return "<%s '%s'>" % (type(self).__name__, self.short_name)
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):
224
        perms = set()
225
        if user is not None:
226
            perms.add('view')
227
        if user.rolenm == 'admin':
228
            perms.add('edit')
229
        return perms
230
1080.1.26 by me at id
ivle.database: Add an Enrolment class, and reference(set)s between all of the
231
class Semester(Storm):
1080.1.25 by me at id
ivle.database: Add Subject, Semester and Offering.
232
    __storm_table__ = "semester"
233
234
    id = Int(primary=True, name="semesterid")
235
    year = Unicode()
236
    semester = Unicode()
237
    active = Bool()
238
1080.1.26 by me at id
ivle.database: Add an Enrolment class, and reference(set)s between all of the
239
    offerings = ReferenceSet(id, 'Offering.semester_id')
240
1080.1.25 by me at id
ivle.database: Add Subject, Semester and Offering.
241
    __init__ = _kwarg_init
242
243
    def __repr__(self):
244
        return "<%s %s/%s>" % (type(self).__name__, self.year, self.semester)
245
1080.1.26 by me at id
ivle.database: Add an Enrolment class, and reference(set)s between all of the
246
class Offering(Storm):
1080.1.25 by me at id
ivle.database: Add Subject, Semester and Offering.
247
    __storm_table__ = "offering"
248
249
    id = Int(primary=True, name="offeringid")
250
    subject_id = Int(name="subject")
251
    subject = Reference(subject_id, Subject.id)
252
    semester_id = Int(name="semesterid")
253
    semester = Reference(semester_id, Semester.id)
254
    groups_student_permissions = Unicode()
255
1080.1.26 by me at id
ivle.database: Add an Enrolment class, and reference(set)s between all of the
256
    enrolments = ReferenceSet(id, 'Enrolment.offering_id')
1080.1.79 by William Grant
ivle.database.Offering: Add a members ReferenceSet.
257
    members = ReferenceSet(id,
258
                           'Enrolment.offering_id',
259
                           'Enrolment.user_id',
260
                           'User.id')
1080.1.76 by William Grant
ivle.database.Offering: Add project_sets referenceset.
261
    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
262
1080.1.25 by me at id
ivle.database: Add Subject, Semester and Offering.
263
    __init__ = _kwarg_init
264
265
    def __repr__(self):
266
        return "<%s %r in %r>" % (type(self).__name__, self.subject,
267
                                  self.semester)
1080.1.26 by me at id
ivle.database: Add an Enrolment class, and reference(set)s between all of the
268
1080.1.61 by William Grant
ivle.database: Add an Offering.enrol(user) method, which enrols the user in
269
    def enrol(self, user):
270
        '''Enrol a user in this offering.'''
271
        # We'll get a horrible database constraint violation error if we try
272
        # to add a second enrolment.
273
        if Store.of(self).find(Enrolment,
274
                               Enrolment.user_id == user.id,
275
                               Enrolment.offering_id == self.id).count() == 1:
276
            raise AlreadyEnrolledError()
277
278
        e = Enrolment(user=user, offering=self, active=True)
279
        self.enrolments.add(e)
280
1080.1.26 by me at id
ivle.database: Add an Enrolment class, and reference(set)s between all of the
281
class Enrolment(Storm):
282
    __storm_table__ = "enrolment"
283
    __storm_primary__ = "user_id", "offering_id"
284
285
    user_id = Int(name="loginid")
286
    user = Reference(user_id, User.id)
287
    offering_id = Int(name="offeringid")
288
    offering = Reference(offering_id, Offering.id)
289
    notes = Unicode()
290
    active = Bool()
291
1080.1.81 by William Grant
ivle.database.Enrolment: Add a groups attribute, containing groups of which
292
    @property
293
    def groups(self):
294
        return Store.of(self).find(ProjectGroup,
295
                ProjectSet.offering_id == self.offering.id,
296
                ProjectGroup.project_set_id == ProjectSet.id,
297
                ProjectGroupMembership.project_group_id == ProjectGroup.id,
298
                ProjectGroupMembership.user_id == self.user.id)
299
1080.1.26 by me at id
ivle.database: Add an Enrolment class, and reference(set)s between all of the
300
    __init__ = _kwarg_init
301
302
    def __repr__(self):
303
        return "<%s %r in %r>" % (type(self).__name__, self.user,
304
                                  self.offering)
1080.1.36 by William Grant
ivle.database: Add ProjectSet, Project, ProjectGroup, ProjectGroupMembership
305
1080.1.61 by William Grant
ivle.database: Add an Offering.enrol(user) method, which enrols the user in
306
class AlreadyEnrolledError(Exception):
307
    pass
308
1080.1.39 by Matt Giuca
ivle.database: Added __all__ to the top of the file.
309
# PROJECTS #
310
1080.1.36 by William Grant
ivle.database: Add ProjectSet, Project, ProjectGroup, ProjectGroupMembership
311
class ProjectSet(Storm):
312
    __storm_table__ = "project_set"
313
314
    id = Int(name="projectsetid", primary=True)
315
    offering_id = Int(name="offeringid")
316
    offering = Reference(offering_id, Offering.id)
317
    max_students_per_group = Int()
318
1080.1.77 by William Grant
ivle.database.ProjectSet: Add projects and project_groups referencesets.
319
    projects = ReferenceSet(id, 'Project.project_set_id')
320
    project_groups = ReferenceSet(id, 'ProjectGroup.project_set_id')
321
1080.1.36 by William Grant
ivle.database: Add ProjectSet, Project, ProjectGroup, ProjectGroupMembership
322
    __init__ = _kwarg_init
323
324
    def __repr__(self):
325
        return "<%s %d in %r>" % (type(self).__name__, self.id,
326
                                  self.offering)
327
328
class Project(Storm):
329
    __storm_table__ = "project"
330
331
    id = Int(name="projectid", primary=True)
332
    synopsis = Unicode()
333
    url = Unicode()
334
    project_set_id = Int(name="projectsetid")
335
    project_set = Reference(project_set_id, ProjectSet.id)
336
    deadline = DateTime()
337
338
    __init__ = _kwarg_init
339
340
    def __repr__(self):
341
        return "<%s '%s' in %r>" % (type(self).__name__, self.synopsis,
342
                                  self.project_set.offering)
343
344
class ProjectGroup(Storm):
345
    __storm_table__ = "project_group"
346
347
    id = Int(name="groupid", primary=True)
348
    name = Unicode(name="groupnm")
349
    project_set_id = Int(name="projectsetid")
350
    project_set = Reference(project_set_id, ProjectSet.id)
351
    nick = Unicode()
352
    created_by_id = Int(name="createdby")
353
    created_by = Reference(created_by_id, User.id)
354
    epoch = DateTime()
355
1080.1.78 by William Grant
ivle.database.ProjectGroup.members: Use a ReferenceSet.
356
    members = ReferenceSet(id,
357
                           "ProjectGroupMembership.project_group_id",
358
                           "ProjectGroupMembership.user_id",
359
                           "User.id")
360
1080.1.36 by William Grant
ivle.database: Add ProjectSet, Project, ProjectGroup, ProjectGroupMembership
361
    __init__ = _kwarg_init
362
363
    def __repr__(self):
364
        return "<%s %s in %r>" % (type(self).__name__, self.name,
365
                                  self.project_set.offering)
366
367
class ProjectGroupMembership(Storm):
368
    __storm_table__ = "group_member"
369
    __storm_primary__ = "user_id", "project_group_id"
370
371
    user_id = Int(name="loginid")
372
    user = Reference(user_id, User.id)
373
    project_group_id = Int(name="groupid")
374
    project_group = Reference(project_group_id, ProjectGroup.id)
375
376
    __init__ = _kwarg_init
377
378
    def __repr__(self):
379
        return "<%s %r in %r>" % (type(self).__name__, self.user,
380
                                  self.project_group)
381
1080.1.40 by Matt Giuca
ivle.database: Added Worksheet and Exercise classes (more to come in this
382
# WORKSHEETS AND EXERCISES #
383
384
class Exercise(Storm):
385
    # Note: Table "problem" is called "Exercise" in the Object layer, since
386
    # it's called that everywhere else.
387
    __storm_table__ = "problem"
388
389
    id = Int(primary=True, name="problemid")
390
    name = Unicode(name="identifier")
391
    spec = Unicode()
392
1080.1.50 by Matt Giuca
ivle.database: Added WorksheetExercise (relates worksheets to exercises), and
393
    worksheets = ReferenceSet(id,
394
        'WorksheetExercise.exercise_id',
395
        'WorksheetExercise.worksheet_id',
396
        'Worksheet.id'
397
    )
398
1080.1.40 by Matt Giuca
ivle.database: Added Worksheet and Exercise classes (more to come in this
399
    __init__ = _kwarg_init
400
401
    def __repr__(self):
402
        return "<%s %s>" % (type(self).__name__, self.name)
403
1080.1.51 by Matt Giuca
tutorial: Replaced call to ivle.db.create_worksheet with local code (roughly
404
    @classmethod
405
    def get_by_name(cls, store, name):
406
        """
407
        Get the Exercise from the db associated with a given store and name.
1080.1.52 by Matt Giuca
ivle.database: Exercise.get_by_name, now auto-inserts and returns a new
408
        If the exercise is not in the database, creates it and inserts it
409
        automatically.
1080.1.51 by Matt Giuca
tutorial: Replaced call to ivle.db.create_worksheet with local code (roughly
410
        """
1080.1.52 by Matt Giuca
ivle.database: Exercise.get_by_name, now auto-inserts and returns a new
411
        ex = store.find(cls, cls.name == unicode(name)).one()
412
        if ex is not None:
413
            return ex
414
        ex = Exercise(name=unicode(name))
415
        store.add(ex)
416
        store.commit()
417
        return ex
1080.1.51 by Matt Giuca
tutorial: Replaced call to ivle.db.create_worksheet with local code (roughly
418
1080.1.40 by Matt Giuca
ivle.database: Added Worksheet and Exercise classes (more to come in this
419
class Worksheet(Storm):
420
    __storm_table__ = "worksheet"
421
422
    id = Int(primary=True, name="worksheetid")
423
    # XXX subject is not linked to a Subject object. This is a property of
424
    # the database, and will be refactored.
425
    subject = Unicode()
426
    name = Unicode(name="identifier")
427
    assessable = Bool()
428
    mtime = DateTime()
429
1080.1.50 by Matt Giuca
ivle.database: Added WorksheetExercise (relates worksheets to exercises), and
430
    exercises = ReferenceSet(id,
431
        'WorksheetExercise.worksheet_id',
432
        'WorksheetExercise.exercise_id',
433
        Exercise.id)
434
    # Use worksheet_exercises to get access to the WorksheetExercise objects
435
    # binding worksheets to exercises. This is required to access the
436
    # "optional" field.
437
    worksheet_exercises = ReferenceSet(id,
438
        'WorksheetExercise.worksheet_id')
439
1080.1.40 by Matt Giuca
ivle.database: Added Worksheet and Exercise classes (more to come in this
440
    __init__ = _kwarg_init
441
442
    def __repr__(self):
443
        return "<%s %s>" % (type(self).__name__, self.name)
1080.1.47 by Matt Giuca
ivle.database: Added Worksheet.get_by_name method.
444
445
    # XXX Refactor this - make it an instance method of Subject rather than a
446
    # class method of Worksheet. Can't do that now because Subject isn't
447
    # linked referentially to the Worksheet.
448
    @classmethod
449
    def get_by_name(cls, store, subjectname, worksheetname):
450
        """
451
        Get the Worksheet from the db associated with a given store, subject
452
        name and worksheet name.
453
        """
454
        return store.find(cls, cls.subject == unicode(subjectname),
455
            cls.name == unicode(worksheetname)).one()
1080.1.50 by Matt Giuca
ivle.database: Added WorksheetExercise (relates worksheets to exercises), and
456
1080.1.51 by Matt Giuca
tutorial: Replaced call to ivle.db.create_worksheet with local code (roughly
457
    def remove_all_exercises(self, store):
458
        """
459
        Remove all exercises from this worksheet.
460
        This does not delete the exercises themselves. It just removes them
461
        from the worksheet.
462
        """
463
        store.find(WorksheetExercise,
464
            WorksheetExercise.worksheet == self).remove()
465
1080.1.50 by Matt Giuca
ivle.database: Added WorksheetExercise (relates worksheets to exercises), and
466
class WorksheetExercise(Storm):
467
    __storm_table__ = "worksheet_problem"
468
    __storm_primary__ = "worksheet_id", "exercise_id"
469
470
    worksheet_id = Int(name="worksheetid")
471
    worksheet = Reference(worksheet_id, Worksheet.id)
472
    exercise_id = Int(name="problemid")
473
    exercise = Reference(exercise_id, Exercise.id)
474
    optional = Bool()
475
476
    __init__ = _kwarg_init
477
478
    def __repr__(self):
479
        return "<%s %s in %s>" % (type(self).__name__, self.exercise.name,
480
                                  self.worksheet.name)
1080.1.55 by Matt Giuca
ivle.database: Added ExerciseAttempt and ExerciseSave classes.
481
482
class ExerciseSave(Storm):
483
    """
484
    Represents a potential solution to an exercise that a user has submitted
485
    to the server for storage.
486
    A basic ExerciseSave is just the current saved text for this exercise for
487
    this user (doesn't count towards their attempts).
488
    ExerciseSave may be extended with additional semantics (such as
489
    ExerciseAttempt).
490
    """
491
    __storm_table__ = "problem_save"
492
    __storm_primary__ = "exercise_id", "user_id", "date"
493
494
    exercise_id = Int(name="problemid")
495
    exercise = Reference(exercise_id, Exercise.id)
496
    user_id = Int(name="loginid")
497
    user = Reference(user_id, User.id)
498
    date = DateTime()
499
    text = Unicode()
500
501
    __init__ = _kwarg_init
502
503
    def __repr__(self):
504
        return "<%s %s by %s at %s>" % (type(self).__name__,
505
            self.exercise.name, self.user.login, self.date.strftime("%c"))
506
507
class ExerciseAttempt(ExerciseSave):
508
    """
509
    An ExerciseAttempt is a special case of an ExerciseSave. Like an
510
    ExerciseSave, it constitutes exercise solution data that the user has
511
    submitted to the server for storage.
512
    In addition, it contains additional information about the submission.
513
    complete - True if this submission was successful, rendering this exercise
514
        complete for this user.
515
    active - True if this submission is "active" (usually true). Submissions
516
        may be de-activated by privileged users for special reasons, and then
517
        they won't count (either as a penalty or success), but will still be
518
        stored.
519
    """
520
    __storm_table__ = "problem_attempt"
521
    __storm_primary__ = "exercise_id", "user_id", "date"
522
523
    # The "text" field is the same but has a different name in the DB table
524
    # for some reason.
525
    text = Unicode(name="attempt")
526
    complete = Bool()
527
    active = Bool()