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

« back to all changes in this revision

Viewing changes to ivle/database.py

Modified the edit view of a worksheet to allow editing of the format
for the worksheet

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
import ivle.caps
 
35
 
 
36
__all__ = ['get_store',
 
37
            'User',
 
38
            'Subject', 'Semester', 'Offering', 'Enrolment',
 
39
            'ProjectSet', 'Project', 'ProjectGroup', 'ProjectGroupMembership',
 
40
            'Exercise', 'Worksheet', 'WorksheetExercise',
 
41
            'ExerciseSave', 'ExerciseAttempt',
 
42
            'AlreadyEnrolledError', 'TestCase', 'TestSuite', 'TestSuiteVar'
 
43
        ]
 
44
 
 
45
def _kwarg_init(self, **kwargs):
 
46
    for k,v in kwargs.items():
 
47
        if k.startswith('_') or not hasattr(self.__class__, k):
 
48
            raise TypeError("%s got an unexpected keyword argument '%s'"
 
49
                % (self.__class__.__name__, k))
 
50
        setattr(self, k, v)
 
51
 
 
52
def get_conn_string():
 
53
    """
 
54
    Returns the Storm connection string, generated from the conf file.
 
55
    """
 
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)
 
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()))
 
77
 
 
78
# USERS #
 
79
 
 
80
class User(Storm):
 
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
 
 
112
    __init__ = _kwarg_init
 
113
 
 
114
    def __repr__(self):
 
115
        return "<%s '%s'>" % (type(self).__name__, self.login)
 
116
 
 
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
 
 
128
    def hasCap(self, capability):
 
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
 
 
134
    @property
 
135
    def password_expired(self):
 
136
        fieldval = self.pass_exp
 
137
        return fieldval is not None and datetime.datetime.now() > fieldval
 
138
 
 
139
    @property
 
140
    def account_expired(self):
 
141
        fieldval = self.acct_exp
 
142
        return fieldval is not None and datetime.datetime.now() > fieldval
 
143
 
 
144
    @property
 
145
    def valid(self):
 
146
        return self.state == 'enabled' and not self.account_expired
 
147
 
 
148
    def _get_enrolments(self, justactive):
 
149
        return Store.of(self).find(Enrolment,
 
150
            Enrolment.user_id == self.id,
 
151
            (Enrolment.active == True) if justactive else True,
 
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
 
 
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
 
 
167
    @property
 
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
 
 
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
 
 
192
    @property
 
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) 
 
201
 
 
202
    @staticmethod
 
203
    def hash_password(password):
 
204
        return md5.md5(password).hexdigest()
 
205
 
 
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
        """
 
212
        return store.find(cls, cls.login == unicode(login)).one()
 
213
 
 
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
 
 
220
# SUBJECTS AND ENROLMENTS #
 
221
 
 
222
class Subject(Storm):
 
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
 
 
231
    offerings = ReferenceSet(id, 'Offering.subject_id')
 
232
 
 
233
    __init__ = _kwarg_init
 
234
 
 
235
    def __repr__(self):
 
236
        return "<%s '%s'>" % (type(self).__name__, self.short_name)
 
237
 
 
238
    def get_permissions(self, user):
 
239
        perms = set()
 
240
        if user is not None:
 
241
            perms.add('view')
 
242
            if user.rolenm == 'admin':
 
243
                perms.add('edit')
 
244
        return perms
 
245
 
 
246
class Semester(Storm):
 
247
    __storm_table__ = "semester"
 
248
 
 
249
    id = Int(primary=True, name="semesterid")
 
250
    year = Unicode()
 
251
    semester = Unicode()
 
252
    active = Bool()
 
253
 
 
254
    offerings = ReferenceSet(id, 'Offering.semester_id')
 
255
 
 
256
    __init__ = _kwarg_init
 
257
 
 
258
    def __repr__(self):
 
259
        return "<%s %s/%s>" % (type(self).__name__, self.year, self.semester)
 
260
 
 
261
class Offering(Storm):
 
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
 
 
271
    enrolments = ReferenceSet(id, 'Enrolment.offering_id')
 
272
    members = ReferenceSet(id,
 
273
                           'Enrolment.offering_id',
 
274
                           'Enrolment.user_id',
 
275
                           'User.id')
 
276
    project_sets = ReferenceSet(id, 'ProjectSet.offering_id')
 
277
 
 
278
    worksheets = ReferenceSet(id, 
 
279
        'Worksheet.offering_id', 
 
280
        order_by="Worksheet.seq_no"
 
281
    )
 
282
 
 
283
    __init__ = _kwarg_init
 
284
 
 
285
    def __repr__(self):
 
286
        return "<%s %r in %r>" % (type(self).__name__, self.subject,
 
287
                                  self.semester)
 
288
 
 
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
 
 
301
    def get_permissions(self, user):
 
302
        perms = set()
 
303
        if user is not None:
 
304
            perms.add('view')
 
305
            if user.rolenm == 'admin':
 
306
                perms.add('edit')
 
307
        return perms
 
308
 
 
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
 
 
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
 
 
328
    __init__ = _kwarg_init
 
329
 
 
330
    def __repr__(self):
 
331
        return "<%s %r in %r>" % (type(self).__name__, self.user,
 
332
                                  self.offering)
 
333
 
 
334
class AlreadyEnrolledError(Exception):
 
335
    pass
 
336
 
 
337
# PROJECTS #
 
338
 
 
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
 
 
347
    projects = ReferenceSet(id, 'Project.project_set_id')
 
348
    project_groups = ReferenceSet(id, 'ProjectGroup.project_set_id')
 
349
 
 
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
 
 
384
    members = ReferenceSet(id,
 
385
                           "ProjectGroupMembership.project_group_id",
 
386
                           "ProjectGroupMembership.user_id",
 
387
                           "User.id")
 
388
 
 
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
 
 
410
# WORKSHEETS AND EXERCISES #
 
411
 
 
412
class Exercise(Storm):
 
413
    # Note: Table "problem" is called "Exercise" in the Object layer, since
 
414
    # it's called that everywhere else.
 
415
    __storm_table__ = "problem"
 
416
    id = Unicode(primary=True, name="identifier")
 
417
    name = Unicode()
 
418
    description = Unicode()
 
419
    partial = Unicode()
 
420
    solution = Unicode()
 
421
    include = Unicode()
 
422
    num_rows = Int()
 
423
 
 
424
    worksheets = ReferenceSet(id,
 
425
        'WorksheetExercise.exercise_id',
 
426
        'WorksheetExercise.worksheet_id',
 
427
        'Worksheet.id'
 
428
    )
 
429
    
 
430
    test_suites = ReferenceSet(id, 'TestSuite.exercise_id')
 
431
 
 
432
    __init__ = _kwarg_init
 
433
 
 
434
    def __repr__(self):
 
435
        return "<%s %s>" % (type(self).__name__, self.name)
 
436
 
 
437
 
 
438
class Worksheet(Storm):
 
439
    __storm_table__ = "worksheet"
 
440
 
 
441
    id = Int(primary=True, name="worksheetid")
 
442
    offering_id = Int(name="offeringid")
 
443
    identifier = Unicode()
 
444
    name = Unicode()
 
445
    assessable = Bool()
 
446
    data = Unicode()
 
447
    seq_no = Int()
 
448
    format = Unicode()
 
449
 
 
450
    attempts = ReferenceSet(id, "ExerciseAttempt.worksheetid")
 
451
    offering = Reference(offering_id, 'Offering.id')
 
452
 
 
453
    # Use worksheet_exercises to get access to the WorksheetExercise objects
 
454
    # binding worksheets to exercises. This is required to access the
 
455
    # "optional" field.
 
456
    worksheet_exercises = ReferenceSet(id,
 
457
        'WorksheetExercise.worksheet_id')
 
458
        
 
459
 
 
460
    __init__ = _kwarg_init
 
461
 
 
462
    def __repr__(self):
 
463
        return "<%s %s>" % (type(self).__name__, self.name)
 
464
 
 
465
    # XXX Refactor this - make it an instance method of Subject rather than a
 
466
    # class method of Worksheet. Can't do that now because Subject isn't
 
467
    # linked referentially to the Worksheet.
 
468
    @classmethod
 
469
    def get_by_name(cls, store, subjectname, worksheetname):
 
470
        """
 
471
        Get the Worksheet from the db associated with a given store, subject
 
472
        name and worksheet name.
 
473
        """
 
474
        return store.find(cls, cls.subject == unicode(subjectname),
 
475
            cls.name == unicode(worksheetname)).one()
 
476
 
 
477
    def remove_all_exercises(self, store):
 
478
        """
 
479
        Remove all exercises from this worksheet.
 
480
        This does not delete the exercises themselves. It just removes them
 
481
        from the worksheet.
 
482
        """
 
483
        store.find(WorksheetExercise,
 
484
            WorksheetExercise.worksheet == self).remove()
 
485
            
 
486
    def get_permissions(self, user):
 
487
        return self.offering.get_permissions(user)
 
488
 
 
489
class WorksheetExercise(Storm):
 
490
    __storm_table__ = "worksheet_problem"
 
491
    
 
492
    id = Int(primary=True, name="ws_prob_id")
 
493
 
 
494
    worksheet_id = Int(name="worksheetid")
 
495
    worksheet = Reference(worksheet_id, Worksheet.id)
 
496
    exercise_id = Unicode(name="problemid")
 
497
    exercise = Reference(exercise_id, Exercise.id)
 
498
    optional = Bool()
 
499
    active = Bool()
 
500
    seq_no = Int()
 
501
    
 
502
    saves = ReferenceSet(id, "ExerciseSave.ws_ex_id")
 
503
    attempts = ReferenceSet(id, "ExerciseAttempt.ws_ex_id")
 
504
 
 
505
    __init__ = _kwarg_init
 
506
 
 
507
    def __repr__(self):
 
508
        return "<%s %s in %s>" % (type(self).__name__, self.exercise.name,
 
509
                                  self.worksheet.identifier)
 
510
 
 
511
class ExerciseSave(Storm):
 
512
    """
 
513
    Represents a potential solution to an exercise that a user has submitted
 
514
    to the server for storage.
 
515
    A basic ExerciseSave is just the current saved text for this exercise for
 
516
    this user (doesn't count towards their attempts).
 
517
    ExerciseSave may be extended with additional semantics (such as
 
518
    ExerciseAttempt).
 
519
    """
 
520
    __storm_table__ = "problem_save"
 
521
    __storm_primary__ = "ws_ex_id", "user_id"
 
522
 
 
523
    ws_ex_id = Int(name="ws_prob_id")
 
524
    worksheet_exercise = Reference(ws_ex_id, "WorksheetExercise.id")
 
525
 
 
526
    user_id = Int(name="loginid")
 
527
    user = Reference(user_id, User.id)
 
528
    date = DateTime()
 
529
    text = Unicode()
 
530
 
 
531
    __init__ = _kwarg_init
 
532
 
 
533
    def __repr__(self):
 
534
        return "<%s %s by %s at %s>" % (type(self).__name__,
 
535
            self.exercise.name, self.user.login, self.date.strftime("%c"))
 
536
 
 
537
class ExerciseAttempt(ExerciseSave):
 
538
    """
 
539
    An ExerciseAttempt is a special case of an ExerciseSave. Like an
 
540
    ExerciseSave, it constitutes exercise solution data that the user has
 
541
    submitted to the server for storage.
 
542
    In addition, it contains additional information about the submission.
 
543
    complete - True if this submission was successful, rendering this exercise
 
544
        complete for this user.
 
545
    active - True if this submission is "active" (usually true). Submissions
 
546
        may be de-activated by privileged users for special reasons, and then
 
547
        they won't count (either as a penalty or success), but will still be
 
548
        stored.
 
549
    """
 
550
    __storm_table__ = "problem_attempt"
 
551
    __storm_primary__ = "ws_ex_id", "user_id", "date"
 
552
 
 
553
    # The "text" field is the same but has a different name in the DB table
 
554
    # for some reason.
 
555
    text = Unicode(name="attempt")
 
556
    complete = Bool()
 
557
    active = Bool()
 
558
    
 
559
    def get_permissions(self, user):
 
560
        return set(['view']) if user is self.user else set()
 
561
  
 
562
class TestSuite(Storm):
 
563
    """A Testsuite acts as a container for the test cases of an exercise."""
 
564
    __storm_table__ = "test_suite"
 
565
    __storm_primary__ = "exercise_id", "suiteid"
 
566
    
 
567
    suiteid = Int()
 
568
    exercise_id = Unicode(name="problemid")
 
569
    description = Unicode()
 
570
    seq_no = Int()
 
571
    function = Unicode()
 
572
    stdin = Unicode()
 
573
    exercise = Reference(exercise_id, Exercise.id)
 
574
    test_cases = ReferenceSet(suiteid, 'TestCase.suiteid')
 
575
    variables = ReferenceSet(suiteid, 'TestSuiteVar.suiteid')
 
576
 
 
577
class TestCase(Storm):
 
578
    """A TestCase is a member of a TestSuite.
 
579
    
 
580
    It contains the data necessary to check if an exercise is correct"""
 
581
    __storm_table__ = "test_case"
 
582
    __storm_primary__ = "testid", "suiteid"
 
583
    
 
584
    testid = Int()
 
585
    suiteid = Int()
 
586
    suite = Reference(suiteid, "TestSuite.suiteid")
 
587
    passmsg = Unicode()
 
588
    failmsg = Unicode()
 
589
    test_default = Unicode()
 
590
    seq_no = Int()
 
591
    
 
592
    parts = ReferenceSet(testid, "TestCasePart.testid")
 
593
    
 
594
    __init__ = _kwarg_init
 
595
 
 
596
class TestSuiteVar(Storm):
 
597
    """A container for the arguments of a Test Suite"""
 
598
    __storm_table__ = "suite_variables"
 
599
    __storm_primary__ = "varid"
 
600
    
 
601
    varid = Int()
 
602
    suiteid = Int()
 
603
    var_name = Unicode()
 
604
    var_value = Unicode()
 
605
    var_type = Unicode()
 
606
    arg_no = Int()
 
607
    
 
608
    suite = Reference(suiteid, "TestSuite.suiteid")
 
609
    
 
610
    __init__ = _kwarg_init
 
611
    
 
612
class TestCasePart(Storm):
 
613
    """A container for the test elements of a Test Case"""
 
614
    __storm_table__ = "test_case_parts"
 
615
    __storm_primary__ = "partid"
 
616
    
 
617
    partid = Int()
 
618
    testid = Int()
 
619
    
 
620
    part_type = Unicode()
 
621
    test_type = Unicode()
 
622
    data = Unicode()
 
623
    filename = Unicode()
 
624
    
 
625
    test = Reference(testid, "TestCase.testid")
 
626
    
 
627
    __init__ = _kwarg_init