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

« back to all changes in this revision

Viewing changes to ivle/database.py

  • Committer: Matt Giuca
  • Date: 2009-02-25 03:04:08 UTC
  • mto: This revision was merged to the branch mainline in revision 1119.
  • Revision ID: matt.giuca@gmail.com-20090225030408-5e13v4felqc4aybr
(almost) all HTML pages updated: Moved <h1>s outside of the ivle-padding div.
    Added new <h1>s to pages which didn't have any, and reduced <h1s> down to
    <h2s> if they didn't represent the page heading.

h1s now represent the top-level heading of a page. The new style sheet styles
them specially, designed to appear directly underneath the tab bar. Therefore,
they need to appear outside of any padding.

util.py: Removed the <h1> from the default-generated terms of service page.
    (All other TOS pages should follow). There is now a <h1> at the top of the
    TOS template page instead.

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
 
 
33
import ivle.conf
 
34
 
 
35
__all__ = ['get_store',
 
36
            'User',
 
37
            'Subject', 'Semester', 'Offering', 'Enrolment',
 
38
            'ProjectSet', 'Project', 'ProjectGroup', 'ProjectGroupMembership',
 
39
            'Exercise', 'Worksheet', 'WorksheetExercise',
 
40
            'ExerciseSave', 'ExerciseAttempt',
 
41
            'AlreadyEnrolledError', 'TestCase', 'TestSuite', 'TestSuiteVar'
 
42
        ]
 
43
 
 
44
def _kwarg_init(self, **kwargs):
 
45
    for k,v in kwargs.items():
 
46
        if k.startswith('_') or not hasattr(self.__class__, k):
 
47
            raise TypeError("%s got an unexpected keyword argument '%s'"
 
48
                % (self.__class__.__name__, k))
 
49
        setattr(self, k, v)
 
50
 
 
51
def get_conn_string():
 
52
    """
 
53
    Returns the Storm connection string, generated from the conf file.
 
54
    """
 
55
 
 
56
    clusterstr = ''
 
57
    if ivle.conf.db_user:
 
58
        clusterstr += ivle.conf.db_user
 
59
        if ivle.conf.db_password:
 
60
            clusterstr += ':' + ivle.conf.db_password
 
61
        clusterstr += '@'
 
62
 
 
63
    host = ivle.conf.db_host or 'localhost'
 
64
    port = ivle.conf.db_port or 5432
 
65
 
 
66
    clusterstr += '%s:%d' % (host, port)
 
67
 
 
68
    return "postgres://%s/%s" % (clusterstr, ivle.conf.db_dbname)
 
69
 
 
70
def get_store():
 
71
    """
 
72
    Open a database connection and transaction. Return a storm.store.Store
 
73
    instance connected to the configured IVLE database.
 
74
    """
 
75
    return Store(create_database(get_conn_string()))
 
76
 
 
77
# USERS #
 
78
 
 
79
class User(Storm):
 
80
    """
 
81
    Represents an IVLE user.
 
82
    """
 
83
    __storm_table__ = "login"
 
84
 
 
85
    id = Int(primary=True, name="loginid")
 
86
    login = Unicode()
 
87
    passhash = Unicode()
 
88
    state = Unicode()
 
89
    admin = Bool()
 
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
 
 
101
    __init__ = _kwarg_init
 
102
 
 
103
    def __repr__(self):
 
104
        return "<%s '%s'>" % (type(self).__name__, self.login)
 
105
 
 
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
 
 
117
    @property
 
118
    def password_expired(self):
 
119
        fieldval = self.pass_exp
 
120
        return fieldval is not None and datetime.datetime.now() > fieldval
 
121
 
 
122
    @property
 
123
    def account_expired(self):
 
124
        fieldval = self.acct_exp
 
125
        return fieldval is not None and datetime.datetime.now() > fieldval
 
126
 
 
127
    @property
 
128
    def valid(self):
 
129
        return self.state == 'enabled' and not self.account_expired
 
130
 
 
131
    def _get_enrolments(self, justactive):
 
132
        return Store.of(self).find(Enrolment,
 
133
            Enrolment.user_id == self.id,
 
134
            (Enrolment.active == True) if justactive else True,
 
135
            Enrolment.offering_id == Offering.id,
 
136
            Offering.semester_id == Semester.id,
 
137
            Offering.subject_id == Subject.id).order_by(
 
138
                Desc(Semester.year),
 
139
                Desc(Semester.semester),
 
140
                Desc(Subject.code)
 
141
            )
 
142
 
 
143
    def _set_password(self, password):
 
144
        if password is None:
 
145
            self.passhash = None
 
146
        else:
 
147
            self.passhash = unicode(User.hash_password(password))
 
148
    password = property(fset=_set_password)
 
149
 
 
150
    @property
 
151
    def subjects(self):
 
152
        return Store.of(self).find(Subject,
 
153
            Enrolment.user_id == self.id,
 
154
            Enrolment.active == True,
 
155
            Offering.id == Enrolment.offering_id,
 
156
            Subject.id == Offering.subject_id).config(distinct=True)
 
157
 
 
158
    # TODO: Invitations should be listed too?
 
159
    def get_groups(self, offering=None):
 
160
        preds = [
 
161
            ProjectGroupMembership.user_id == self.id,
 
162
            ProjectGroup.id == ProjectGroupMembership.project_group_id,
 
163
        ]
 
164
        if offering:
 
165
            preds.extend([
 
166
                ProjectSet.offering_id == offering.id,
 
167
                ProjectGroup.project_set_id == ProjectSet.id,
 
168
            ])
 
169
        return Store.of(self).find(ProjectGroup, *preds)
 
170
 
 
171
    @property
 
172
    def groups(self):
 
173
        return self.get_groups()
 
174
 
 
175
    @property
 
176
    def active_enrolments(self):
 
177
        '''A sanely ordered list of the user's active enrolments.'''
 
178
        return self._get_enrolments(True)
 
179
 
 
180
    @property
 
181
    def enrolments(self):
 
182
        '''A sanely ordered list of all of the user's enrolments.'''
 
183
        return self._get_enrolments(False) 
 
184
 
 
185
    @staticmethod
 
186
    def hash_password(password):
 
187
        return md5.md5(password).hexdigest()
 
188
 
 
189
    @classmethod
 
190
    def get_by_login(cls, store, login):
 
191
        """
 
192
        Get the User from the db associated with a given store and
 
193
        login.
 
194
        """
 
195
        return store.find(cls, cls.login == unicode(login)).one()
 
196
 
 
197
    def get_permissions(self, user):
 
198
        if user and user.admin or user is self:
 
199
            return set(['view', 'edit'])
 
200
        else:
 
201
            return set()
 
202
 
 
203
# SUBJECTS AND ENROLMENTS #
 
204
 
 
205
class Subject(Storm):
 
206
    __storm_table__ = "subject"
 
207
 
 
208
    id = Int(primary=True, name="subjectid")
 
209
    code = Unicode(name="subj_code")
 
210
    name = Unicode(name="subj_name")
 
211
    short_name = Unicode(name="subj_short_name")
 
212
    url = Unicode()
 
213
 
 
214
    offerings = ReferenceSet(id, 'Offering.subject_id')
 
215
 
 
216
    __init__ = _kwarg_init
 
217
 
 
218
    def __repr__(self):
 
219
        return "<%s '%s'>" % (type(self).__name__, self.short_name)
 
220
 
 
221
    def get_permissions(self, user):
 
222
        perms = set()
 
223
        if user is not None:
 
224
            perms.add('view')
 
225
            if user.admin:
 
226
                perms.add('edit')
 
227
        return perms
 
228
 
 
229
class Semester(Storm):
 
230
    __storm_table__ = "semester"
 
231
 
 
232
    id = Int(primary=True, name="semesterid")
 
233
    year = Unicode()
 
234
    semester = Unicode()
 
235
    state = Unicode()
 
236
 
 
237
    offerings = ReferenceSet(id, 'Offering.semester_id')
 
238
 
 
239
    __init__ = _kwarg_init
 
240
 
 
241
    def __repr__(self):
 
242
        return "<%s %s/%s>" % (type(self).__name__, self.year, self.semester)
 
243
 
 
244
class Offering(Storm):
 
245
    __storm_table__ = "offering"
 
246
 
 
247
    id = Int(primary=True, name="offeringid")
 
248
    subject_id = Int(name="subject")
 
249
    subject = Reference(subject_id, Subject.id)
 
250
    semester_id = Int(name="semesterid")
 
251
    semester = Reference(semester_id, Semester.id)
 
252
    groups_student_permissions = Unicode()
 
253
 
 
254
    enrolments = ReferenceSet(id, 'Enrolment.offering_id')
 
255
    members = ReferenceSet(id,
 
256
                           'Enrolment.offering_id',
 
257
                           'Enrolment.user_id',
 
258
                           'User.id')
 
259
    project_sets = ReferenceSet(id, 'ProjectSet.offering_id')
 
260
 
 
261
    worksheets = ReferenceSet(id, 
 
262
        'Worksheet.offering_id', 
 
263
        order_by="Worksheet.seq_no"
 
264
    )
 
265
 
 
266
    __init__ = _kwarg_init
 
267
 
 
268
    def __repr__(self):
 
269
        return "<%s %r in %r>" % (type(self).__name__, self.subject,
 
270
                                  self.semester)
 
271
 
 
272
    def enrol(self, user):
 
273
        '''Enrol a user in this offering.'''
 
274
        # We'll get a horrible database constraint violation error if we try
 
275
        # to add a second enrolment.
 
276
        if Store.of(self).find(Enrolment,
 
277
                               Enrolment.user_id == user.id,
 
278
                               Enrolment.offering_id == self.id).count() == 1:
 
279
            raise AlreadyEnrolledError()
 
280
 
 
281
        e = Enrolment(user=user, offering=self, active=True)
 
282
        self.enrolments.add(e)
 
283
 
 
284
    def get_permissions(self, user):
 
285
        perms = set()
 
286
        if user is not None:
 
287
            perms.add('view')
 
288
            if user.admin:
 
289
                perms.add('edit')
 
290
        return perms
 
291
 
 
292
class Enrolment(Storm):
 
293
    __storm_table__ = "enrolment"
 
294
    __storm_primary__ = "user_id", "offering_id"
 
295
 
 
296
    user_id = Int(name="loginid")
 
297
    user = Reference(user_id, User.id)
 
298
    offering_id = Int(name="offeringid")
 
299
    offering = Reference(offering_id, Offering.id)
 
300
    role = Unicode()
 
301
    notes = Unicode()
 
302
    active = Bool()
 
303
 
 
304
    @property
 
305
    def groups(self):
 
306
        return Store.of(self).find(ProjectGroup,
 
307
                ProjectSet.offering_id == self.offering.id,
 
308
                ProjectGroup.project_set_id == ProjectSet.id,
 
309
                ProjectGroupMembership.project_group_id == ProjectGroup.id,
 
310
                ProjectGroupMembership.user_id == self.user.id)
 
311
 
 
312
    __init__ = _kwarg_init
 
313
 
 
314
    def __repr__(self):
 
315
        return "<%s %r in %r>" % (type(self).__name__, self.user,
 
316
                                  self.offering)
 
317
 
 
318
class AlreadyEnrolledError(Exception):
 
319
    pass
 
320
 
 
321
# PROJECTS #
 
322
 
 
323
class ProjectSet(Storm):
 
324
    __storm_table__ = "project_set"
 
325
 
 
326
    id = Int(name="projectsetid", primary=True)
 
327
    offering_id = Int(name="offeringid")
 
328
    offering = Reference(offering_id, Offering.id)
 
329
    max_students_per_group = Int()
 
330
 
 
331
    projects = ReferenceSet(id, 'Project.project_set_id')
 
332
    project_groups = ReferenceSet(id, 'ProjectGroup.project_set_id')
 
333
 
 
334
    __init__ = _kwarg_init
 
335
 
 
336
    def __repr__(self):
 
337
        return "<%s %d in %r>" % (type(self).__name__, self.id,
 
338
                                  self.offering)
 
339
 
 
340
class Project(Storm):
 
341
    __storm_table__ = "project"
 
342
 
 
343
    id = Int(name="projectid", primary=True)
 
344
    synopsis = Unicode()
 
345
    url = Unicode()
 
346
    project_set_id = Int(name="projectsetid")
 
347
    project_set = Reference(project_set_id, ProjectSet.id)
 
348
    deadline = DateTime()
 
349
 
 
350
    __init__ = _kwarg_init
 
351
 
 
352
    def __repr__(self):
 
353
        return "<%s '%s' in %r>" % (type(self).__name__, self.synopsis,
 
354
                                  self.project_set.offering)
 
355
 
 
356
class ProjectGroup(Storm):
 
357
    __storm_table__ = "project_group"
 
358
 
 
359
    id = Int(name="groupid", primary=True)
 
360
    name = Unicode(name="groupnm")
 
361
    project_set_id = Int(name="projectsetid")
 
362
    project_set = Reference(project_set_id, ProjectSet.id)
 
363
    nick = Unicode()
 
364
    created_by_id = Int(name="createdby")
 
365
    created_by = Reference(created_by_id, User.id)
 
366
    epoch = DateTime()
 
367
 
 
368
    members = ReferenceSet(id,
 
369
                           "ProjectGroupMembership.project_group_id",
 
370
                           "ProjectGroupMembership.user_id",
 
371
                           "User.id")
 
372
 
 
373
    __init__ = _kwarg_init
 
374
 
 
375
    def __repr__(self):
 
376
        return "<%s %s in %r>" % (type(self).__name__, self.name,
 
377
                                  self.project_set.offering)
 
378
 
 
379
class ProjectGroupMembership(Storm):
 
380
    __storm_table__ = "group_member"
 
381
    __storm_primary__ = "user_id", "project_group_id"
 
382
 
 
383
    user_id = Int(name="loginid")
 
384
    user = Reference(user_id, User.id)
 
385
    project_group_id = Int(name="groupid")
 
386
    project_group = Reference(project_group_id, ProjectGroup.id)
 
387
 
 
388
    __init__ = _kwarg_init
 
389
 
 
390
    def __repr__(self):
 
391
        return "<%s %r in %r>" % (type(self).__name__, self.user,
 
392
                                  self.project_group)
 
393
 
 
394
# WORKSHEETS AND EXERCISES #
 
395
 
 
396
class Exercise(Storm):
 
397
    __storm_table__ = "exercise"
 
398
    id = Unicode(primary=True, name="identifier")
 
399
    name = Unicode()
 
400
    description = Unicode()
 
401
    partial = Unicode()
 
402
    solution = Unicode()
 
403
    include = Unicode()
 
404
    num_rows = Int()
 
405
 
 
406
    worksheets = ReferenceSet(id,
 
407
        'WorksheetExercise.exercise_id',
 
408
        'WorksheetExercise.worksheet_id',
 
409
        'Worksheet.id'
 
410
    )
 
411
    
 
412
    test_suites = ReferenceSet(id, 'TestSuite.exercise_id')
 
413
 
 
414
    __init__ = _kwarg_init
 
415
 
 
416
    def __repr__(self):
 
417
        return "<%s %s>" % (type(self).__name__, self.name)
 
418
 
 
419
    def get_permissions(self, user):
 
420
        perms = set()
 
421
        if user is not None:
 
422
            if user.admin:
 
423
                perms.add('edit')
 
424
                perms.add('view')
 
425
        return perms
 
426
 
 
427
class Worksheet(Storm):
 
428
    __storm_table__ = "worksheet"
 
429
 
 
430
    id = Int(primary=True, name="worksheetid")
 
431
    offering_id = Int(name="offeringid")
 
432
    identifier = Unicode()
 
433
    name = Unicode()
 
434
    assessable = Bool()
 
435
    data = Unicode()
 
436
    seq_no = Int()
 
437
    format = Unicode()
 
438
 
 
439
    attempts = ReferenceSet(id, "ExerciseAttempt.worksheetid")
 
440
    offering = Reference(offering_id, 'Offering.id')
 
441
 
 
442
    all_worksheet_exercises = ReferenceSet(id,
 
443
        'WorksheetExercise.worksheet_id')
 
444
 
 
445
    # Use worksheet_exercises to get access to the *active* WorksheetExercise
 
446
    # objects binding worksheets to exercises. This is required to access the
 
447
    # "optional" field.
 
448
    @property
 
449
    def worksheet_exercises(self):
 
450
        return self.all_worksheet_exercises.find(active=True)
 
451
 
 
452
    __init__ = _kwarg_init
 
453
 
 
454
    def __repr__(self):
 
455
        return "<%s %s>" % (type(self).__name__, self.name)
 
456
 
 
457
    # XXX Refactor this - make it an instance method of Subject rather than a
 
458
    # class method of Worksheet. Can't do that now because Subject isn't
 
459
    # linked referentially to the Worksheet.
 
460
    @classmethod
 
461
    def get_by_name(cls, store, subjectname, worksheetname):
 
462
        """
 
463
        Get the Worksheet from the db associated with a given store, subject
 
464
        name and worksheet name.
 
465
        """
 
466
        return store.find(cls, cls.subject == unicode(subjectname),
 
467
            cls.name == unicode(worksheetname)).one()
 
468
 
 
469
    def remove_all_exercises(self, store):
 
470
        """
 
471
        Remove all exercises from this worksheet.
 
472
        This does not delete the exercises themselves. It just removes them
 
473
        from the worksheet.
 
474
        """
 
475
        store.find(WorksheetExercise,
 
476
            WorksheetExercise.worksheet == self).remove()
 
477
            
 
478
    def get_permissions(self, user):
 
479
        return self.offering.get_permissions(user)
 
480
 
 
481
class WorksheetExercise(Storm):
 
482
    __storm_table__ = "worksheet_exercise"
 
483
    
 
484
    id = Int(primary=True, name="ws_ex_id")
 
485
 
 
486
    worksheet_id = Int(name="worksheetid")
 
487
    worksheet = Reference(worksheet_id, Worksheet.id)
 
488
    exercise_id = Unicode(name="exerciseid")
 
489
    exercise = Reference(exercise_id, Exercise.id)
 
490
    optional = Bool()
 
491
    active = Bool()
 
492
    seq_no = Int()
 
493
    
 
494
    saves = ReferenceSet(id, "ExerciseSave.ws_ex_id")
 
495
    attempts = ReferenceSet(id, "ExerciseAttempt.ws_ex_id")
 
496
 
 
497
    __init__ = _kwarg_init
 
498
 
 
499
    def __repr__(self):
 
500
        return "<%s %s in %s>" % (type(self).__name__, self.exercise.name,
 
501
                                  self.worksheet.identifier)
 
502
 
 
503
class ExerciseSave(Storm):
 
504
    """
 
505
    Represents a potential solution to an exercise that a user has submitted
 
506
    to the server for storage.
 
507
    A basic ExerciseSave is just the current saved text for this exercise for
 
508
    this user (doesn't count towards their attempts).
 
509
    ExerciseSave may be extended with additional semantics (such as
 
510
    ExerciseAttempt).
 
511
    """
 
512
    __storm_table__ = "exercise_save"
 
513
    __storm_primary__ = "ws_ex_id", "user_id"
 
514
 
 
515
    ws_ex_id = Int(name="ws_ex_id")
 
516
    worksheet_exercise = Reference(ws_ex_id, "WorksheetExercise.id")
 
517
 
 
518
    user_id = Int(name="loginid")
 
519
    user = Reference(user_id, User.id)
 
520
    date = DateTime()
 
521
    text = Unicode()
 
522
 
 
523
    __init__ = _kwarg_init
 
524
 
 
525
    def __repr__(self):
 
526
        return "<%s %s by %s at %s>" % (type(self).__name__,
 
527
            self.exercise.name, self.user.login, self.date.strftime("%c"))
 
528
 
 
529
class ExerciseAttempt(ExerciseSave):
 
530
    """
 
531
    An ExerciseAttempt is a special case of an ExerciseSave. Like an
 
532
    ExerciseSave, it constitutes exercise solution data that the user has
 
533
    submitted to the server for storage.
 
534
    In addition, it contains additional information about the submission.
 
535
    complete - True if this submission was successful, rendering this exercise
 
536
        complete for this user.
 
537
    active - True if this submission is "active" (usually true). Submissions
 
538
        may be de-activated by privileged users for special reasons, and then
 
539
        they won't count (either as a penalty or success), but will still be
 
540
        stored.
 
541
    """
 
542
    __storm_table__ = "exercise_attempt"
 
543
    __storm_primary__ = "ws_ex_id", "user_id", "date"
 
544
 
 
545
    # The "text" field is the same but has a different name in the DB table
 
546
    # for some reason.
 
547
    text = Unicode(name="attempt")
 
548
    complete = Bool()
 
549
    active = Bool()
 
550
    
 
551
    def get_permissions(self, user):
 
552
        return set(['view']) if user is self.user else set()
 
553
  
 
554
class TestSuite(Storm):
 
555
    """A Testsuite acts as a container for the test cases of an exercise."""
 
556
    __storm_table__ = "test_suite"
 
557
    __storm_primary__ = "exercise_id", "suiteid"
 
558
    
 
559
    suiteid = Int()
 
560
    exercise_id = Unicode(name="exerciseid")
 
561
    description = Unicode()
 
562
    seq_no = Int()
 
563
    function = Unicode()
 
564
    stdin = Unicode()
 
565
    exercise = Reference(exercise_id, Exercise.id)
 
566
    test_cases = ReferenceSet(suiteid, 'TestCase.suiteid')
 
567
    variables = ReferenceSet(suiteid, 'TestSuiteVar.suiteid')
 
568
 
 
569
class TestCase(Storm):
 
570
    """A TestCase is a member of a TestSuite.
 
571
    
 
572
    It contains the data necessary to check if an exercise is correct"""
 
573
    __storm_table__ = "test_case"
 
574
    __storm_primary__ = "testid", "suiteid"
 
575
    
 
576
    testid = Int()
 
577
    suiteid = Int()
 
578
    suite = Reference(suiteid, "TestSuite.suiteid")
 
579
    passmsg = Unicode()
 
580
    failmsg = Unicode()
 
581
    test_default = Unicode()
 
582
    seq_no = Int()
 
583
    
 
584
    parts = ReferenceSet(testid, "TestCasePart.testid")
 
585
    
 
586
    __init__ = _kwarg_init
 
587
 
 
588
class TestSuiteVar(Storm):
 
589
    """A container for the arguments of a Test Suite"""
 
590
    __storm_table__ = "suite_variable"
 
591
    __storm_primary__ = "varid"
 
592
    
 
593
    varid = Int()
 
594
    suiteid = Int()
 
595
    var_name = Unicode()
 
596
    var_value = Unicode()
 
597
    var_type = Unicode()
 
598
    arg_no = Int()
 
599
    
 
600
    suite = Reference(suiteid, "TestSuite.suiteid")
 
601
    
 
602
    __init__ = _kwarg_init
 
603
    
 
604
class TestCasePart(Storm):
 
605
    """A container for the test elements of a Test Case"""
 
606
    __storm_table__ = "test_case_part"
 
607
    __storm_primary__ = "partid"
 
608
    
 
609
    partid = Int()
 
610
    testid = Int()
 
611
    
 
612
    part_type = Unicode()
 
613
    test_type = Unicode()
 
614
    data = Unicode()
 
615
    filename = Unicode()
 
616
    
 
617
    test = Reference(testid, "TestCase.testid")
 
618
    
 
619
    __init__ = _kwarg_init