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

« back to all changes in this revision

Viewing changes to ivle/database.py

  • Committer: Nick Chadwick
  • Date: 2009-03-10 14:12:36 UTC
  • mto: This revision was merged to the branch mainline in revision 1162.
  • Revision ID: chadnickbok@gmail.com-20090310141236-39jkqud21agjso25
Worksheet Edit is no longer under development. The comment saying so
has been removed.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
 
 
27
import md5
 
28
import datetime
 
29
 
 
30
from storm.locals import create_database, Store, Int, Unicode, DateTime, \
 
31
                         Reference, ReferenceSet, Bool, Storm, Desc
 
32
from storm.exceptions import NotOneError
 
33
 
 
34
import ivle.conf
 
35
from ivle.worksheet.rst import rst
 
36
 
 
37
__all__ = ['get_store',
 
38
            'User',
 
39
            'Subject', 'Semester', 'Offering', 'Enrolment',
 
40
            'ProjectSet', 'Project', 'ProjectGroup', 'ProjectGroupMembership',
 
41
            'Exercise', 'Worksheet', 'WorksheetExercise',
 
42
            'ExerciseSave', 'ExerciseAttempt',
 
43
            'TestCase', 'TestSuite', 'TestSuiteVar'
 
44
        ]
 
45
 
 
46
def _kwarg_init(self, **kwargs):
 
47
    for k,v in kwargs.items():
 
48
        if k.startswith('_') or not hasattr(self.__class__, k):
 
49
            raise TypeError("%s got an unexpected keyword argument '%s'"
 
50
                % (self.__class__.__name__, k))
 
51
        setattr(self, k, v)
 
52
 
 
53
def get_conn_string():
 
54
    """
 
55
    Returns the Storm connection string, generated from the conf file.
 
56
    """
 
57
 
 
58
    clusterstr = ''
 
59
    if ivle.conf.db_user:
 
60
        clusterstr += ivle.conf.db_user
 
61
        if ivle.conf.db_password:
 
62
            clusterstr += ':' + ivle.conf.db_password
 
63
        clusterstr += '@'
 
64
 
 
65
    host = ivle.conf.db_host or 'localhost'
 
66
    port = ivle.conf.db_port or 5432
 
67
 
 
68
    clusterstr += '%s:%d' % (host, port)
 
69
 
 
70
    return "postgres://%s/%s" % (clusterstr, ivle.conf.db_dbname)
 
71
 
 
72
def get_store():
 
73
    """
 
74
    Open a database connection and transaction. Return a storm.store.Store
 
75
    instance connected to the configured IVLE database.
 
76
    """
 
77
    return Store(create_database(get_conn_string()))
 
78
 
 
79
# USERS #
 
80
 
 
81
class User(Storm):
 
82
    """
 
83
    Represents an IVLE user.
 
84
    """
 
85
    __storm_table__ = "login"
 
86
 
 
87
    id = Int(primary=True, name="loginid")
 
88
    login = Unicode()
 
89
    passhash = Unicode()
 
90
    state = Unicode()
 
91
    admin = Bool()
 
92
    unixid = Int()
 
93
    nick = Unicode()
 
94
    pass_exp = DateTime()
 
95
    acct_exp = DateTime()
 
96
    last_login = DateTime()
 
97
    svn_pass = Unicode()
 
98
    email = Unicode()
 
99
    fullname = Unicode()
 
100
    studentid = Unicode()
 
101
    settings = Unicode()
 
102
 
 
103
    __init__ = _kwarg_init
 
104
 
 
105
    def __repr__(self):
 
106
        return "<%s '%s'>" % (type(self).__name__, self.login)
 
107
 
 
108
    def authenticate(self, password):
 
109
        """Validate a given password against this user.
 
110
 
 
111
        Returns True if the given password matches the password hash for this
 
112
        User, False if it doesn't match, and None if there is no hash for the
 
113
        user.
 
114
        """
 
115
        if self.passhash is None:
 
116
            return None
 
117
        return self.hash_password(password) == self.passhash
 
118
 
 
119
    @property
 
120
    def password_expired(self):
 
121
        fieldval = self.pass_exp
 
122
        return fieldval is not None and datetime.datetime.now() > fieldval
 
123
 
 
124
    @property
 
125
    def account_expired(self):
 
126
        fieldval = self.acct_exp
 
127
        return fieldval is not None and datetime.datetime.now() > fieldval
 
128
 
 
129
    @property
 
130
    def valid(self):
 
131
        return self.state == 'enabled' and not self.account_expired
 
132
 
 
133
    def _get_enrolments(self, justactive):
 
134
        return Store.of(self).find(Enrolment,
 
135
            Enrolment.user_id == self.id,
 
136
            (Enrolment.active == True) if justactive else True,
 
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
 
 
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
 
 
152
    @property
 
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
 
 
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
 
 
177
    @property
 
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) 
 
186
 
 
187
    @staticmethod
 
188
    def hash_password(password):
 
189
        return md5.md5(password).hexdigest()
 
190
 
 
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
        """
 
197
        return store.find(cls, cls.login == unicode(login)).one()
 
198
 
 
199
    def get_permissions(self, user):
 
200
        if user and user.admin or user is self:
 
201
            return set(['view', 'edit'])
 
202
        else:
 
203
            return set()
 
204
 
 
205
# SUBJECTS AND ENROLMENTS #
 
206
 
 
207
class Subject(Storm):
 
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
 
 
216
    offerings = ReferenceSet(id, 'Offering.subject_id')
 
217
 
 
218
    __init__ = _kwarg_init
 
219
 
 
220
    def __repr__(self):
 
221
        return "<%s '%s'>" % (type(self).__name__, self.short_name)
 
222
 
 
223
    def get_permissions(self, user):
 
224
        perms = set()
 
225
        if user is not None:
 
226
            perms.add('view')
 
227
            if user.admin:
 
228
                perms.add('edit')
 
229
        return perms
 
230
 
 
231
class Semester(Storm):
 
232
    __storm_table__ = "semester"
 
233
 
 
234
    id = Int(primary=True, name="semesterid")
 
235
    year = Unicode()
 
236
    semester = Unicode()
 
237
    state = Unicode()
 
238
 
 
239
    offerings = ReferenceSet(id, 'Offering.semester_id')
 
240
    enrolments = ReferenceSet(id,
 
241
                              'Offering.semester_id',
 
242
                              'Offering.id',
 
243
                              'Enrolment.offering_id')
 
244
 
 
245
    __init__ = _kwarg_init
 
246
 
 
247
    def __repr__(self):
 
248
        return "<%s %s/%s>" % (type(self).__name__, self.year, self.semester)
 
249
 
 
250
class Offering(Storm):
 
251
    __storm_table__ = "offering"
 
252
 
 
253
    id = Int(primary=True, name="offeringid")
 
254
    subject_id = Int(name="subject")
 
255
    subject = Reference(subject_id, Subject.id)
 
256
    semester_id = Int(name="semesterid")
 
257
    semester = Reference(semester_id, Semester.id)
 
258
    groups_student_permissions = Unicode()
 
259
 
 
260
    enrolments = ReferenceSet(id, 'Enrolment.offering_id')
 
261
    members = ReferenceSet(id,
 
262
                           'Enrolment.offering_id',
 
263
                           'Enrolment.user_id',
 
264
                           'User.id')
 
265
    project_sets = ReferenceSet(id, 'ProjectSet.offering_id')
 
266
 
 
267
    worksheets = ReferenceSet(id, 
 
268
        'Worksheet.offering_id', 
 
269
        order_by="seq_no"
 
270
    )
 
271
 
 
272
    __init__ = _kwarg_init
 
273
 
 
274
    def __repr__(self):
 
275
        return "<%s %r in %r>" % (type(self).__name__, self.subject,
 
276
                                  self.semester)
 
277
 
 
278
    def enrol(self, user, role=u'student'):
 
279
        '''Enrol a user in this offering.'''
 
280
        enrolment = Store.of(self).find(Enrolment,
 
281
                               Enrolment.user_id == user.id,
 
282
                               Enrolment.offering_id == self.id).one()
 
283
 
 
284
        if enrolment is None:
 
285
            enrolment = Enrolment(user=user, offering=self)
 
286
            self.enrolments.add(enrolment)
 
287
 
 
288
        enrolment.active = True
 
289
        enrolment.role = role
 
290
 
 
291
    def unenrol(self, user):
 
292
        '''Unenrol a user from this offering.'''
 
293
        enrolment = Store.of(self).find(Enrolment,
 
294
                               Enrolment.user_id == user.id,
 
295
                               Enrolment.offering_id == self.id).one()
 
296
        Store.of(enrolment).remove(enrolment)
 
297
 
 
298
    def get_permissions(self, user):
 
299
        perms = set()
 
300
        if user is not None:
 
301
            enrolment = self.get_enrolment(user)
 
302
            if enrolment or user.admin:
 
303
                perms.add('view')
 
304
            if (enrolment and enrolment.role in (u'tutor', u'lecturer')) \
 
305
               or user.admin:
 
306
                perms.add('edit')
 
307
        return perms
 
308
 
 
309
    def get_enrolment(self, user):
 
310
        try:
 
311
            enrolment = self.enrolments.find(user=user).one()
 
312
        except NotOneError:
 
313
            enrolment = None
 
314
 
 
315
        return enrolment
 
316
 
 
317
class Enrolment(Storm):
 
318
    __storm_table__ = "enrolment"
 
319
    __storm_primary__ = "user_id", "offering_id"
 
320
 
 
321
    user_id = Int(name="loginid")
 
322
    user = Reference(user_id, User.id)
 
323
    offering_id = Int(name="offeringid")
 
324
    offering = Reference(offering_id, Offering.id)
 
325
    role = Unicode()
 
326
    notes = Unicode()
 
327
    active = Bool()
 
328
 
 
329
    @property
 
330
    def groups(self):
 
331
        return Store.of(self).find(ProjectGroup,
 
332
                ProjectSet.offering_id == self.offering.id,
 
333
                ProjectGroup.project_set_id == ProjectSet.id,
 
334
                ProjectGroupMembership.project_group_id == ProjectGroup.id,
 
335
                ProjectGroupMembership.user_id == self.user.id)
 
336
 
 
337
    __init__ = _kwarg_init
 
338
 
 
339
    def __repr__(self):
 
340
        return "<%s %r in %r>" % (type(self).__name__, self.user,
 
341
                                  self.offering)
 
342
 
 
343
# PROJECTS #
 
344
 
 
345
class ProjectSet(Storm):
 
346
    __storm_table__ = "project_set"
 
347
 
 
348
    id = Int(name="projectsetid", primary=True)
 
349
    offering_id = Int(name="offeringid")
 
350
    offering = Reference(offering_id, Offering.id)
 
351
    max_students_per_group = Int()
 
352
 
 
353
    projects = ReferenceSet(id, 'Project.project_set_id')
 
354
    project_groups = ReferenceSet(id, 'ProjectGroup.project_set_id')
 
355
 
 
356
    __init__ = _kwarg_init
 
357
 
 
358
    def __repr__(self):
 
359
        return "<%s %d in %r>" % (type(self).__name__, self.id,
 
360
                                  self.offering)
 
361
 
 
362
class Project(Storm):
 
363
    __storm_table__ = "project"
 
364
 
 
365
    id = Int(name="projectid", primary=True)
 
366
    synopsis = Unicode()
 
367
    url = Unicode()
 
368
    project_set_id = Int(name="projectsetid")
 
369
    project_set = Reference(project_set_id, ProjectSet.id)
 
370
    deadline = DateTime()
 
371
 
 
372
    __init__ = _kwarg_init
 
373
 
 
374
    def __repr__(self):
 
375
        return "<%s '%s' in %r>" % (type(self).__name__, self.synopsis,
 
376
                                  self.project_set.offering)
 
377
 
 
378
class ProjectGroup(Storm):
 
379
    __storm_table__ = "project_group"
 
380
 
 
381
    id = Int(name="groupid", primary=True)
 
382
    name = Unicode(name="groupnm")
 
383
    project_set_id = Int(name="projectsetid")
 
384
    project_set = Reference(project_set_id, ProjectSet.id)
 
385
    nick = Unicode()
 
386
    created_by_id = Int(name="createdby")
 
387
    created_by = Reference(created_by_id, User.id)
 
388
    epoch = DateTime()
 
389
 
 
390
    members = ReferenceSet(id,
 
391
                           "ProjectGroupMembership.project_group_id",
 
392
                           "ProjectGroupMembership.user_id",
 
393
                           "User.id")
 
394
 
 
395
    __init__ = _kwarg_init
 
396
 
 
397
    def __repr__(self):
 
398
        return "<%s %s in %r>" % (type(self).__name__, self.name,
 
399
                                  self.project_set.offering)
 
400
 
 
401
class ProjectGroupMembership(Storm):
 
402
    __storm_table__ = "group_member"
 
403
    __storm_primary__ = "user_id", "project_group_id"
 
404
 
 
405
    user_id = Int(name="loginid")
 
406
    user = Reference(user_id, User.id)
 
407
    project_group_id = Int(name="groupid")
 
408
    project_group = Reference(project_group_id, ProjectGroup.id)
 
409
 
 
410
    __init__ = _kwarg_init
 
411
 
 
412
    def __repr__(self):
 
413
        return "<%s %r in %r>" % (type(self).__name__, self.user,
 
414
                                  self.project_group)
 
415
 
 
416
# WORKSHEETS AND EXERCISES #
 
417
 
 
418
class Exercise(Storm):
 
419
    __storm_table__ = "exercise"
 
420
    id = Unicode(primary=True, name="identifier")
 
421
    name = Unicode()
 
422
    description = Unicode()
 
423
    partial = Unicode()
 
424
    solution = Unicode()
 
425
    include = Unicode()
 
426
    num_rows = Int()
 
427
 
 
428
    worksheet_exercises =  ReferenceSet(id,
 
429
        'WorksheetExercise.exercise_id')
 
430
 
 
431
    worksheets = ReferenceSet(id,
 
432
        'WorksheetExercise.exercise_id',
 
433
        'WorksheetExercise.worksheet_id',
 
434
        'Worksheet.id'
 
435
    )
 
436
    
 
437
    test_suites = ReferenceSet(id, 
 
438
        'TestSuite.exercise_id',
 
439
        order_by='seq_no')
 
440
 
 
441
    __init__ = _kwarg_init
 
442
 
 
443
    def __repr__(self):
 
444
        return "<%s %s>" % (type(self).__name__, self.name)
 
445
 
 
446
    def get_permissions(self, user):
 
447
        perms = set()
 
448
        roles = set()
 
449
        if user is not None:
 
450
            if user.admin:
 
451
                perms.add('edit')
 
452
                perms.add('view')
 
453
            elif 'lecturer' in set((e.role for e in user.active_enrolments)):
 
454
                perms.add('edit')
 
455
                perms.add('view')
 
456
            
 
457
        return perms
 
458
    
 
459
    def get_description(self):
 
460
        return rst(self.description)
 
461
 
 
462
    def delete(self, store):
 
463
        """Deletes the exercise, providing it has no associated worksheets.
 
464
        
 
465
        Returns True if delete successful. Otherwise returns False."""
 
466
        if self.worksheet_exercises.count() > 0:
 
467
            return False
 
468
 
 
469
        for suite in self.test_suites:
 
470
            suite.delete()
 
471
        store.remove(self)
 
472
        return True
 
473
 
 
474
class Worksheet(Storm):
 
475
    __storm_table__ = "worksheet"
 
476
 
 
477
    id = Int(primary=True, name="worksheetid")
 
478
    offering_id = Int(name="offeringid")
 
479
    identifier = Unicode()
 
480
    name = Unicode()
 
481
    assessable = Bool()
 
482
    data = Unicode()
 
483
    seq_no = Int()
 
484
    format = Unicode()
 
485
 
 
486
    attempts = ReferenceSet(id, "ExerciseAttempt.worksheetid")
 
487
    offering = Reference(offering_id, 'Offering.id')
 
488
 
 
489
    all_worksheet_exercises = ReferenceSet(id,
 
490
        'WorksheetExercise.worksheet_id')
 
491
 
 
492
    # Use worksheet_exercises to get access to the *active* WorksheetExercise
 
493
    # objects binding worksheets to exercises. This is required to access the
 
494
    # "optional" field.
 
495
 
 
496
    @property
 
497
    def worksheet_exercises(self):
 
498
        return self.all_worksheet_exercises.find(active=True)
 
499
 
 
500
    __init__ = _kwarg_init
 
501
 
 
502
    def __repr__(self):
 
503
        return "<%s %s>" % (type(self).__name__, self.name)
 
504
 
 
505
    # XXX Refactor this - make it an instance method of Subject rather than a
 
506
    # class method of Worksheet. Can't do that now because Subject isn't
 
507
    # linked referentially to the Worksheet.
 
508
    @classmethod
 
509
    def get_by_name(cls, store, subjectname, worksheetname):
 
510
        """
 
511
        Get the Worksheet from the db associated with a given store, subject
 
512
        name and worksheet name.
 
513
        """
 
514
        return store.find(cls, cls.subject == unicode(subjectname),
 
515
            cls.name == unicode(worksheetname)).one()
 
516
 
 
517
    def remove_all_exercises(self, store):
 
518
        """
 
519
        Remove all exercises from this worksheet.
 
520
        This does not delete the exercises themselves. It just removes them
 
521
        from the worksheet.
 
522
        """
 
523
        store.find(WorksheetExercise,
 
524
            WorksheetExercise.worksheet == self).remove()
 
525
            
 
526
    def get_permissions(self, user):
 
527
        return self.offering.get_permissions(user)
 
528
    
 
529
    def get_xml(self):
 
530
        """Returns the xml of this worksheet, converts from rst if required."""
 
531
        if self.format == u'rst':
 
532
            ws_xml = rst(self.data)
 
533
            return ws_xml
 
534
        else:
 
535
            return self.data
 
536
    
 
537
    def delete(self, store):
 
538
        """Deletes the worksheet, provided it has no attempts on any exercises.
 
539
        
 
540
        Returns True if delete succeeded, or False if this worksheet has
 
541
        attempts attached."""
 
542
        for ws_ex in self.all_worksheet_exercises:
 
543
            if ws_ex.saves.count() > 0 or ws_ex.attempts.count() > 0:
 
544
                return False
 
545
        
 
546
        self.remove_all_exercises()
 
547
        store.remove(self)
 
548
        return True
 
549
        
 
550
class WorksheetExercise(Storm):
 
551
    __storm_table__ = "worksheet_exercise"
 
552
    
 
553
    id = Int(primary=True, name="ws_ex_id")
 
554
 
 
555
    worksheet_id = Int(name="worksheetid")
 
556
    worksheet = Reference(worksheet_id, Worksheet.id)
 
557
    exercise_id = Unicode(name="exerciseid")
 
558
    exercise = Reference(exercise_id, Exercise.id)
 
559
    optional = Bool()
 
560
    active = Bool()
 
561
    seq_no = Int()
 
562
    
 
563
    saves = ReferenceSet(id, "ExerciseSave.ws_ex_id")
 
564
    attempts = ReferenceSet(id, "ExerciseAttempt.ws_ex_id")
 
565
 
 
566
    __init__ = _kwarg_init
 
567
 
 
568
    def __repr__(self):
 
569
        return "<%s %s in %s>" % (type(self).__name__, self.exercise.name,
 
570
                                  self.worksheet.identifier)
 
571
 
 
572
    def get_permissions(self, user):
 
573
        return self.worksheet.get_permissions(user)
 
574
    
 
575
 
 
576
class ExerciseSave(Storm):
 
577
    """
 
578
    Represents a potential solution to an exercise that a user has submitted
 
579
    to the server for storage.
 
580
    A basic ExerciseSave is just the current saved text for this exercise for
 
581
    this user (doesn't count towards their attempts).
 
582
    ExerciseSave may be extended with additional semantics (such as
 
583
    ExerciseAttempt).
 
584
    """
 
585
    __storm_table__ = "exercise_save"
 
586
    __storm_primary__ = "ws_ex_id", "user_id"
 
587
 
 
588
    ws_ex_id = Int(name="ws_ex_id")
 
589
    worksheet_exercise = Reference(ws_ex_id, "WorksheetExercise.id")
 
590
 
 
591
    user_id = Int(name="loginid")
 
592
    user = Reference(user_id, User.id)
 
593
    date = DateTime()
 
594
    text = Unicode()
 
595
 
 
596
    __init__ = _kwarg_init
 
597
 
 
598
    def __repr__(self):
 
599
        return "<%s %s by %s at %s>" % (type(self).__name__,
 
600
            self.exercise.name, self.user.login, self.date.strftime("%c"))
 
601
 
 
602
class ExerciseAttempt(ExerciseSave):
 
603
    """
 
604
    An ExerciseAttempt is a special case of an ExerciseSave. Like an
 
605
    ExerciseSave, it constitutes exercise solution data that the user has
 
606
    submitted to the server for storage.
 
607
    In addition, it contains additional information about the submission.
 
608
    complete - True if this submission was successful, rendering this exercise
 
609
        complete for this user.
 
610
    active - True if this submission is "active" (usually true). Submissions
 
611
        may be de-activated by privileged users for special reasons, and then
 
612
        they won't count (either as a penalty or success), but will still be
 
613
        stored.
 
614
    """
 
615
    __storm_table__ = "exercise_attempt"
 
616
    __storm_primary__ = "ws_ex_id", "user_id", "date"
 
617
 
 
618
    # The "text" field is the same but has a different name in the DB table
 
619
    # for some reason.
 
620
    text = Unicode(name="attempt")
 
621
    complete = Bool()
 
622
    active = Bool()
 
623
    
 
624
    def get_permissions(self, user):
 
625
        return set(['view']) if user is self.user else set()
 
626
  
 
627
class TestSuite(Storm):
 
628
    """A Testsuite acts as a container for the test cases of an exercise."""
 
629
    __storm_table__ = "test_suite"
 
630
    __storm_primary__ = "exercise_id", "suiteid"
 
631
    
 
632
    suiteid = Int()
 
633
    exercise_id = Unicode(name="exerciseid")
 
634
    description = Unicode()
 
635
    seq_no = Int()
 
636
    function = Unicode()
 
637
    stdin = Unicode()
 
638
    exercise = Reference(exercise_id, Exercise.id)
 
639
    test_cases = ReferenceSet(suiteid, 'TestCase.suiteid', order_by="seq_no")
 
640
    variables = ReferenceSet(suiteid, 'TestSuiteVar.suiteid', order_by='arg_no')
 
641
    
 
642
    def delete(self, store):
 
643
        """Delete this suite, without asking questions."""
 
644
        for vaariable in self.variables:
 
645
            variable.delete()
 
646
        for test_case in self.test_cases:
 
647
            test_case.delete()
 
648
        store.remove(self)
 
649
 
 
650
class TestCase(Storm):
 
651
    """A TestCase is a member of a TestSuite.
 
652
    
 
653
    It contains the data necessary to check if an exercise is correct"""
 
654
    __storm_table__ = "test_case"
 
655
    __storm_primary__ = "testid", "suiteid"
 
656
    
 
657
    testid = Int()
 
658
    suiteid = Int()
 
659
    suite = Reference(suiteid, "TestSuite.suiteid")
 
660
    passmsg = Unicode()
 
661
    failmsg = Unicode()
 
662
    test_default = Unicode()
 
663
    seq_no = Int()
 
664
    
 
665
    parts = ReferenceSet(testid, "TestCasePart.testid")
 
666
    
 
667
    __init__ = _kwarg_init
 
668
    
 
669
    def delete(self, store):
 
670
        for part in self.parts:
 
671
            part.delete()
 
672
        store.remove(self)
 
673
 
 
674
class TestSuiteVar(Storm):
 
675
    """A container for the arguments of a Test Suite"""
 
676
    __storm_table__ = "suite_variable"
 
677
    __storm_primary__ = "varid"
 
678
    
 
679
    varid = Int()
 
680
    suiteid = Int()
 
681
    var_name = Unicode()
 
682
    var_value = Unicode()
 
683
    var_type = Unicode()
 
684
    arg_no = Int()
 
685
    
 
686
    suite = Reference(suiteid, "TestSuite.suiteid")
 
687
    
 
688
    __init__ = _kwarg_init
 
689
    
 
690
    def delete(self, store):
 
691
        store.remove(self)
 
692
    
 
693
class TestCasePart(Storm):
 
694
    """A container for the test elements of a Test Case"""
 
695
    __storm_table__ = "test_case_part"
 
696
    __storm_primary__ = "partid"
 
697
    
 
698
    partid = Int()
 
699
    testid = Int()
 
700
    
 
701
    part_type = Unicode()
 
702
    test_type = Unicode()
 
703
    data = Unicode()
 
704
    filename = Unicode()
 
705
    
 
706
    test = Reference(testid, "TestCase.testid")
 
707
    
 
708
    __init__ = _kwarg_init
 
709
    
 
710
    def delete(self, store):
 
711
        store.remove(self)