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

« back to all changes in this revision

Viewing changes to ivle/database.py

  • Committer: William Grant
  • Date: 2009-02-26 02:08:28 UTC
  • Revision ID: grantw@unimelb.edu.au-20090226020828-0qrhe3llq9r5olmr
ivle-showenrolment: Swap year and semester, and show the role.

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
            '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
    enrolments = ReferenceSet(id,
 
239
                              'Offering.semester_id',
 
240
                              'Offering.id',
 
241
                              'Enrolment.offering_id')
 
242
 
 
243
    __init__ = _kwarg_init
 
244
 
 
245
    def __repr__(self):
 
246
        return "<%s %s/%s>" % (type(self).__name__, self.year, self.semester)
 
247
 
 
248
class Offering(Storm):
 
249
    __storm_table__ = "offering"
 
250
 
 
251
    id = Int(primary=True, name="offeringid")
 
252
    subject_id = Int(name="subject")
 
253
    subject = Reference(subject_id, Subject.id)
 
254
    semester_id = Int(name="semesterid")
 
255
    semester = Reference(semester_id, Semester.id)
 
256
    groups_student_permissions = Unicode()
 
257
 
 
258
    enrolments = ReferenceSet(id, 'Enrolment.offering_id')
 
259
    members = ReferenceSet(id,
 
260
                           'Enrolment.offering_id',
 
261
                           'Enrolment.user_id',
 
262
                           'User.id')
 
263
    project_sets = ReferenceSet(id, 'ProjectSet.offering_id')
 
264
 
 
265
    worksheets = ReferenceSet(id, 
 
266
        'Worksheet.offering_id', 
 
267
        order_by="Worksheet.seq_no"
 
268
    )
 
269
 
 
270
    __init__ = _kwarg_init
 
271
 
 
272
    def __repr__(self):
 
273
        return "<%s %r in %r>" % (type(self).__name__, self.subject,
 
274
                                  self.semester)
 
275
 
 
276
    def enrol(self, user, role=u'student'):
 
277
        '''Enrol a user in this offering.'''
 
278
        enrolment = Store.of(self).find(Enrolment,
 
279
                               Enrolment.user_id == user.id,
 
280
                               Enrolment.offering_id == self.id).one()
 
281
 
 
282
        if enrolment is None:
 
283
            enrolment = Enrolment(user=user, offering=self)
 
284
            self.enrolments.add(enrolment)
 
285
 
 
286
        enrolment.active = True
 
287
        enrolment.role = role
 
288
 
 
289
    def get_permissions(self, user):
 
290
        perms = set()
 
291
        if user is not None:
 
292
            perms.add('view')
 
293
            if user.admin:
 
294
                perms.add('edit')
 
295
        return perms
 
296
 
 
297
class Enrolment(Storm):
 
298
    __storm_table__ = "enrolment"
 
299
    __storm_primary__ = "user_id", "offering_id"
 
300
 
 
301
    user_id = Int(name="loginid")
 
302
    user = Reference(user_id, User.id)
 
303
    offering_id = Int(name="offeringid")
 
304
    offering = Reference(offering_id, Offering.id)
 
305
    role = Unicode()
 
306
    notes = Unicode()
 
307
    active = Bool()
 
308
 
 
309
    @property
 
310
    def groups(self):
 
311
        return Store.of(self).find(ProjectGroup,
 
312
                ProjectSet.offering_id == self.offering.id,
 
313
                ProjectGroup.project_set_id == ProjectSet.id,
 
314
                ProjectGroupMembership.project_group_id == ProjectGroup.id,
 
315
                ProjectGroupMembership.user_id == self.user.id)
 
316
 
 
317
    __init__ = _kwarg_init
 
318
 
 
319
    def __repr__(self):
 
320
        return "<%s %r in %r>" % (type(self).__name__, self.user,
 
321
                                  self.offering)
 
322
 
 
323
# PROJECTS #
 
324
 
 
325
class ProjectSet(Storm):
 
326
    __storm_table__ = "project_set"
 
327
 
 
328
    id = Int(name="projectsetid", primary=True)
 
329
    offering_id = Int(name="offeringid")
 
330
    offering = Reference(offering_id, Offering.id)
 
331
    max_students_per_group = Int()
 
332
 
 
333
    projects = ReferenceSet(id, 'Project.project_set_id')
 
334
    project_groups = ReferenceSet(id, 'ProjectGroup.project_set_id')
 
335
 
 
336
    __init__ = _kwarg_init
 
337
 
 
338
    def __repr__(self):
 
339
        return "<%s %d in %r>" % (type(self).__name__, self.id,
 
340
                                  self.offering)
 
341
 
 
342
class Project(Storm):
 
343
    __storm_table__ = "project"
 
344
 
 
345
    id = Int(name="projectid", primary=True)
 
346
    synopsis = Unicode()
 
347
    url = Unicode()
 
348
    project_set_id = Int(name="projectsetid")
 
349
    project_set = Reference(project_set_id, ProjectSet.id)
 
350
    deadline = DateTime()
 
351
 
 
352
    __init__ = _kwarg_init
 
353
 
 
354
    def __repr__(self):
 
355
        return "<%s '%s' in %r>" % (type(self).__name__, self.synopsis,
 
356
                                  self.project_set.offering)
 
357
 
 
358
class ProjectGroup(Storm):
 
359
    __storm_table__ = "project_group"
 
360
 
 
361
    id = Int(name="groupid", primary=True)
 
362
    name = Unicode(name="groupnm")
 
363
    project_set_id = Int(name="projectsetid")
 
364
    project_set = Reference(project_set_id, ProjectSet.id)
 
365
    nick = Unicode()
 
366
    created_by_id = Int(name="createdby")
 
367
    created_by = Reference(created_by_id, User.id)
 
368
    epoch = DateTime()
 
369
 
 
370
    members = ReferenceSet(id,
 
371
                           "ProjectGroupMembership.project_group_id",
 
372
                           "ProjectGroupMembership.user_id",
 
373
                           "User.id")
 
374
 
 
375
    __init__ = _kwarg_init
 
376
 
 
377
    def __repr__(self):
 
378
        return "<%s %s in %r>" % (type(self).__name__, self.name,
 
379
                                  self.project_set.offering)
 
380
 
 
381
class ProjectGroupMembership(Storm):
 
382
    __storm_table__ = "group_member"
 
383
    __storm_primary__ = "user_id", "project_group_id"
 
384
 
 
385
    user_id = Int(name="loginid")
 
386
    user = Reference(user_id, User.id)
 
387
    project_group_id = Int(name="groupid")
 
388
    project_group = Reference(project_group_id, ProjectGroup.id)
 
389
 
 
390
    __init__ = _kwarg_init
 
391
 
 
392
    def __repr__(self):
 
393
        return "<%s %r in %r>" % (type(self).__name__, self.user,
 
394
                                  self.project_group)
 
395
 
 
396
# WORKSHEETS AND EXERCISES #
 
397
 
 
398
class Exercise(Storm):
 
399
    __storm_table__ = "exercise"
 
400
    id = Unicode(primary=True, name="identifier")
 
401
    name = Unicode()
 
402
    description = Unicode()
 
403
    partial = Unicode()
 
404
    solution = Unicode()
 
405
    include = Unicode()
 
406
    num_rows = Int()
 
407
 
 
408
    worksheets = ReferenceSet(id,
 
409
        'WorksheetExercise.exercise_id',
 
410
        'WorksheetExercise.worksheet_id',
 
411
        'Worksheet.id'
 
412
    )
 
413
    
 
414
    test_suites = ReferenceSet(id, 'TestSuite.exercise_id')
 
415
 
 
416
    __init__ = _kwarg_init
 
417
 
 
418
    def __repr__(self):
 
419
        return "<%s %s>" % (type(self).__name__, self.name)
 
420
 
 
421
    def get_permissions(self, user):
 
422
        perms = set()
 
423
        if user is not None:
 
424
            if user.admin:
 
425
                perms.add('edit')
 
426
                perms.add('view')
 
427
        return perms
 
428
 
 
429
class Worksheet(Storm):
 
430
    __storm_table__ = "worksheet"
 
431
 
 
432
    id = Int(primary=True, name="worksheetid")
 
433
    offering_id = Int(name="offeringid")
 
434
    identifier = Unicode()
 
435
    name = Unicode()
 
436
    assessable = Bool()
 
437
    data = Unicode()
 
438
    seq_no = Int()
 
439
    format = Unicode()
 
440
 
 
441
    attempts = ReferenceSet(id, "ExerciseAttempt.worksheetid")
 
442
    offering = Reference(offering_id, 'Offering.id')
 
443
 
 
444
    all_worksheet_exercises = ReferenceSet(id,
 
445
        'WorksheetExercise.worksheet_id')
 
446
 
 
447
    # Use worksheet_exercises to get access to the *active* WorksheetExercise
 
448
    # objects binding worksheets to exercises. This is required to access the
 
449
    # "optional" field.
 
450
    @property
 
451
    def worksheet_exercises(self):
 
452
        return self.all_worksheet_exercises.find(active=True)
 
453
 
 
454
    __init__ = _kwarg_init
 
455
 
 
456
    def __repr__(self):
 
457
        return "<%s %s>" % (type(self).__name__, self.name)
 
458
 
 
459
    # XXX Refactor this - make it an instance method of Subject rather than a
 
460
    # class method of Worksheet. Can't do that now because Subject isn't
 
461
    # linked referentially to the Worksheet.
 
462
    @classmethod
 
463
    def get_by_name(cls, store, subjectname, worksheetname):
 
464
        """
 
465
        Get the Worksheet from the db associated with a given store, subject
 
466
        name and worksheet name.
 
467
        """
 
468
        return store.find(cls, cls.subject == unicode(subjectname),
 
469
            cls.name == unicode(worksheetname)).one()
 
470
 
 
471
    def remove_all_exercises(self, store):
 
472
        """
 
473
        Remove all exercises from this worksheet.
 
474
        This does not delete the exercises themselves. It just removes them
 
475
        from the worksheet.
 
476
        """
 
477
        store.find(WorksheetExercise,
 
478
            WorksheetExercise.worksheet == self).remove()
 
479
            
 
480
    def get_permissions(self, user):
 
481
        return self.offering.get_permissions(user)
 
482
 
 
483
class WorksheetExercise(Storm):
 
484
    __storm_table__ = "worksheet_exercise"
 
485
    
 
486
    id = Int(primary=True, name="ws_ex_id")
 
487
 
 
488
    worksheet_id = Int(name="worksheetid")
 
489
    worksheet = Reference(worksheet_id, Worksheet.id)
 
490
    exercise_id = Unicode(name="exerciseid")
 
491
    exercise = Reference(exercise_id, Exercise.id)
 
492
    optional = Bool()
 
493
    active = Bool()
 
494
    seq_no = Int()
 
495
    
 
496
    saves = ReferenceSet(id, "ExerciseSave.ws_ex_id")
 
497
    attempts = ReferenceSet(id, "ExerciseAttempt.ws_ex_id")
 
498
 
 
499
    __init__ = _kwarg_init
 
500
 
 
501
    def __repr__(self):
 
502
        return "<%s %s in %s>" % (type(self).__name__, self.exercise.name,
 
503
                                  self.worksheet.identifier)
 
504
 
 
505
class ExerciseSave(Storm):
 
506
    """
 
507
    Represents a potential solution to an exercise that a user has submitted
 
508
    to the server for storage.
 
509
    A basic ExerciseSave is just the current saved text for this exercise for
 
510
    this user (doesn't count towards their attempts).
 
511
    ExerciseSave may be extended with additional semantics (such as
 
512
    ExerciseAttempt).
 
513
    """
 
514
    __storm_table__ = "exercise_save"
 
515
    __storm_primary__ = "ws_ex_id", "user_id"
 
516
 
 
517
    ws_ex_id = Int(name="ws_ex_id")
 
518
    worksheet_exercise = Reference(ws_ex_id, "WorksheetExercise.id")
 
519
 
 
520
    user_id = Int(name="loginid")
 
521
    user = Reference(user_id, User.id)
 
522
    date = DateTime()
 
523
    text = Unicode()
 
524
 
 
525
    __init__ = _kwarg_init
 
526
 
 
527
    def __repr__(self):
 
528
        return "<%s %s by %s at %s>" % (type(self).__name__,
 
529
            self.exercise.name, self.user.login, self.date.strftime("%c"))
 
530
 
 
531
class ExerciseAttempt(ExerciseSave):
 
532
    """
 
533
    An ExerciseAttempt is a special case of an ExerciseSave. Like an
 
534
    ExerciseSave, it constitutes exercise solution data that the user has
 
535
    submitted to the server for storage.
 
536
    In addition, it contains additional information about the submission.
 
537
    complete - True if this submission was successful, rendering this exercise
 
538
        complete for this user.
 
539
    active - True if this submission is "active" (usually true). Submissions
 
540
        may be de-activated by privileged users for special reasons, and then
 
541
        they won't count (either as a penalty or success), but will still be
 
542
        stored.
 
543
    """
 
544
    __storm_table__ = "exercise_attempt"
 
545
    __storm_primary__ = "ws_ex_id", "user_id", "date"
 
546
 
 
547
    # The "text" field is the same but has a different name in the DB table
 
548
    # for some reason.
 
549
    text = Unicode(name="attempt")
 
550
    complete = Bool()
 
551
    active = Bool()
 
552
    
 
553
    def get_permissions(self, user):
 
554
        return set(['view']) if user is self.user else set()
 
555
  
 
556
class TestSuite(Storm):
 
557
    """A Testsuite acts as a container for the test cases of an exercise."""
 
558
    __storm_table__ = "test_suite"
 
559
    __storm_primary__ = "exercise_id", "suiteid"
 
560
    
 
561
    suiteid = Int()
 
562
    exercise_id = Unicode(name="exerciseid")
 
563
    description = Unicode()
 
564
    seq_no = Int()
 
565
    function = Unicode()
 
566
    stdin = Unicode()
 
567
    exercise = Reference(exercise_id, Exercise.id)
 
568
    test_cases = ReferenceSet(suiteid, 'TestCase.suiteid')
 
569
    variables = ReferenceSet(suiteid, 'TestSuiteVar.suiteid')
 
570
 
 
571
class TestCase(Storm):
 
572
    """A TestCase is a member of a TestSuite.
 
573
    
 
574
    It contains the data necessary to check if an exercise is correct"""
 
575
    __storm_table__ = "test_case"
 
576
    __storm_primary__ = "testid", "suiteid"
 
577
    
 
578
    testid = Int()
 
579
    suiteid = Int()
 
580
    suite = Reference(suiteid, "TestSuite.suiteid")
 
581
    passmsg = Unicode()
 
582
    failmsg = Unicode()
 
583
    test_default = Unicode()
 
584
    seq_no = Int()
 
585
    
 
586
    parts = ReferenceSet(testid, "TestCasePart.testid")
 
587
    
 
588
    __init__ = _kwarg_init
 
589
 
 
590
class TestSuiteVar(Storm):
 
591
    """A container for the arguments of a Test Suite"""
 
592
    __storm_table__ = "suite_variable"
 
593
    __storm_primary__ = "varid"
 
594
    
 
595
    varid = Int()
 
596
    suiteid = Int()
 
597
    var_name = Unicode()
 
598
    var_value = Unicode()
 
599
    var_type = Unicode()
 
600
    arg_no = Int()
 
601
    
 
602
    suite = Reference(suiteid, "TestSuite.suiteid")
 
603
    
 
604
    __init__ = _kwarg_init
 
605
    
 
606
class TestCasePart(Storm):
 
607
    """A container for the test elements of a Test Case"""
 
608
    __storm_table__ = "test_case_part"
 
609
    __storm_primary__ = "partid"
 
610
    
 
611
    partid = Int()
 
612
    testid = Int()
 
613
    
 
614
    part_type = Unicode()
 
615
    test_type = Unicode()
 
616
    data = Unicode()
 
617
    filename = Unicode()
 
618
    
 
619
    test = Reference(testid, "TestCase.testid")
 
620
    
 
621
    __init__ = _kwarg_init