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

1080.1.2 by matt.giuca
New module: ivle.database. Classes and utilities for Storm ORM.
1
# IVLE - Informatics Virtual Learning Environment
2
# Copyright (C) 2007-2009 The University of Melbourne
3
#
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17
18
# Author: Matt Giuca, Will Grant
19
20
"""
21
Database Classes and Utilities for Storm ORM
22
23
This module provides all of the classes which map to database tables.
24
It also provides miscellaneous utility functions for database interaction.
25
"""
26
1080.1.13 by me at id
ivle.database.User: Add an authenticate() method, and a hash_password()
27
import md5
1080.1.15 by me at id
Give ivle.database.User {password,account}_expired attributes, and get
28
import datetime
1080.1.13 by me at id
ivle.database.User: Add an authenticate() method, and a hash_password()
29
1080.1.4 by matt.giuca
ivle.database: Added User class.
30
from storm.locals import create_database, Store, Int, Unicode, DateTime, \
1080.1.27 by me at id
ivle.database.User: Add an 'active_enrolments' property, which returns a list
31
                         Reference, ReferenceSet, Bool, Storm, Desc
1080.1.2 by matt.giuca
New module: ivle.database. Classes and utilities for Storm ORM.
32
33
import ivle.conf
1080.1.4 by matt.giuca
ivle.database: Added User class.
34
import ivle.caps
1080.1.2 by matt.giuca
New module: ivle.database. Classes and utilities for Storm ORM.
35
1080.1.39 by Matt Giuca
ivle.database: Added __all__ to the top of the file.
36
__all__ = ['get_store',
37
            'User',
38
            'Subject', 'Semester', 'Offering', 'Enrolment',
39
            'ProjectSet', 'Project', 'ProjectGroup', 'ProjectGroupMembership',
1080.1.40 by Matt Giuca
ivle.database: Added Worksheet and Exercise classes (more to come in this
40
            'Exercise', 'Worksheet',
1080.1.39 by Matt Giuca
ivle.database: Added __all__ to the top of the file.
41
        ]
42
1080.1.25 by me at id
ivle.database: Add Subject, Semester and Offering.
43
def _kwarg_init(self, **kwargs):
44
    for k,v in kwargs.items():
1080.1.46 by William Grant
ivle.database._kwarg_init: Check with hasattr() on the class, not the object,
45
        if k.startswith('_') or not hasattr(self.__class__, k):
1080.1.25 by me at id
ivle.database: Add Subject, Semester and Offering.
46
            raise TypeError("%s got an unexpected keyword argument '%s'"
1080.1.45 by William Grant
ivle.database._kwarg_init: Fix exception throwing.
47
                % (self.__class__.__name__, k))
1080.1.25 by me at id
ivle.database: Add Subject, Semester and Offering.
48
        setattr(self, k, v)
49
1080.1.2 by matt.giuca
New module: ivle.database. Classes and utilities for Storm ORM.
50
def get_conn_string():
51
    """
52
    Returns the Storm connection string, generated from the conf file.
53
    """
54
    return "postgres://%s:%s@%s:%d/%s" % (ivle.conf.db_user,
55
        ivle.conf.db_password, ivle.conf.db_host, ivle.conf.db_port,
56
        ivle.conf.db_dbname)
57
58
def get_store():
59
    """
60
    Open a database connection and transaction. Return a storm.store.Store
61
    instance connected to the configured IVLE database.
62
    """
63
    return Store(create_database(get_conn_string()))
1080.1.4 by matt.giuca
ivle.database: Added User class.
64
1080.1.39 by Matt Giuca
ivle.database: Added __all__ to the top of the file.
65
# USERS #
66
1080.1.26 by me at id
ivle.database: Add an Enrolment class, and reference(set)s between all of the
67
class User(Storm):
1080.1.4 by matt.giuca
ivle.database: Added User class.
68
    """
69
    Represents an IVLE user.
70
    """
71
    __storm_table__ = "login"
72
73
    id = Int(primary=True, name="loginid")
74
    login = Unicode()
75
    passhash = Unicode()
76
    state = Unicode()
77
    rolenm = Unicode()
78
    unixid = Int()
79
    nick = Unicode()
80
    pass_exp = DateTime()
81
    acct_exp = DateTime()
82
    last_login = DateTime()
83
    svn_pass = Unicode()
84
    email = Unicode()
85
    fullname = Unicode()
86
    studentid = Unicode()
87
    settings = Unicode()
88
89
    def _get_role(self):
90
        if self.rolenm is None:
91
            return None
92
        return ivle.caps.Role(self.rolenm)
93
    def _set_role(self, value):
94
        if not isinstance(value, ivle.caps.Role):
95
            raise TypeError("role must be an ivle.caps.Role")
96
        self.rolenm = unicode(value)
97
    role = property(_get_role, _set_role)
98
1080.1.25 by me at id
ivle.database: Add Subject, Semester and Offering.
99
    __init__ = _kwarg_init
1080.1.4 by matt.giuca
ivle.database: Added User class.
100
101
    def __repr__(self):
102
        return "<%s '%s'>" % (type(self).__name__, self.login)
1080.1.5 by matt.giuca
ivle.database.User: Add the missing methods from ivle.user.User.
103
1080.1.13 by me at id
ivle.database.User: Add an authenticate() method, and a hash_password()
104
    def authenticate(self, password):
105
        """Validate a given password against this user.
106
107
        Returns True if the given password matches the password hash for this
108
        User, False if it doesn't match, and None if there is no hash for the
109
        user.
110
        """
111
        if self.passhash is None:
112
            return None
113
        return self.hash_password(password) == self.passhash
114
1080.1.7 by matt.giuca
The new ivle.database.User class is now used in Request and usrmgt, which
115
    def hasCap(self, capability):
1080.1.5 by matt.giuca
ivle.database.User: Add the missing methods from ivle.user.User.
116
        """Given a capability (which is a Role object), returns True if this
117
        User has that capability, False otherwise.
118
        """
119
        return self.role.hasCap(capability)
120
1080.1.15 by me at id
Give ivle.database.User {password,account}_expired attributes, and get
121
    @property
122
    def password_expired(self):
1080.1.5 by matt.giuca
ivle.database.User: Add the missing methods from ivle.user.User.
123
        fieldval = self.pass_exp
1080.1.15 by me at id
Give ivle.database.User {password,account}_expired attributes, and get
124
        return fieldval is not None and datetime.datetime.now() > fieldval
125
126
    @property
127
    def account_expired(self):
1080.1.5 by matt.giuca
ivle.database.User: Add the missing methods from ivle.user.User.
128
        fieldval = self.acct_exp
1080.1.15 by me at id
Give ivle.database.User {password,account}_expired attributes, and get
129
        return fieldval is not None and datetime.datetime.now() > fieldval
1080.1.6 by matt.giuca
ivle.database.User: Added get_by_login method.
130
1080.1.29 by me at id
ivle.database.User: Order 'enrolments' the same way as 'active_enrolments'.
131
    def _get_enrolments(self, justactive):
1080.1.27 by me at id
ivle.database.User: Add an 'active_enrolments' property, which returns a list
132
        return Store.of(self).find(Enrolment,
133
            Enrolment.user_id == self.id,
1080.1.29 by me at id
ivle.database.User: Order 'enrolments' the same way as 'active_enrolments'.
134
            (Enrolment.active == True) if justactive else True,
1080.1.27 by me at id
ivle.database.User: Add an 'active_enrolments' property, which returns a list
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
1080.1.29 by me at id
ivle.database.User: Order 'enrolments' the same way as 'active_enrolments'.
143
    @property
1080.1.31 by me at id
ivle.database.User: Add 'subjects', an attribute containing currently
144
    def subjects(self):
145
        return Store.of(self).find(Subject,
146
            Enrolment.user_id == self.id,
147
            Enrolment.active == True,
148
            Offering.id == Enrolment.offering_id,
149
            Subject.id == Offering.subject_id).config(distinct=True)
150
1080.1.36 by William Grant
ivle.database: Add ProjectSet, Project, ProjectGroup, ProjectGroupMembership
151
    # TODO: Invitations should be listed too?
152
    def get_groups(self, offering=None):
153
        preds = [
154
            ProjectGroupMembership.user_id == self.id,
155
            ProjectGroup.id == ProjectGroupMembership.project_group_id,
156
        ]
157
        if offering:
158
            preds.extend([
159
                ProjectSet.offering_id == offering.id,
160
                ProjectGroup.project_set_id == ProjectSet.id,
161
            ])
162
        return Store.of(self).find(ProjectGroup, *preds)
163
164
    @property
165
    def groups(self):
166
        return self.get_groups()
167
1080.1.31 by me at id
ivle.database.User: Add 'subjects', an attribute containing currently
168
    @property
1080.1.29 by me at id
ivle.database.User: Order 'enrolments' the same way as 'active_enrolments'.
169
    def active_enrolments(self):
170
        '''A sanely ordered list of the user's active enrolments.'''
171
        return self._get_enrolments(True)
172
173
    @property
174
    def enrolments(self):
175
        '''A sanely ordered list of all of the user's enrolments.'''
176
        return self._get_enrolments(False) 
1080.1.27 by me at id
ivle.database.User: Add an 'active_enrolments' property, which returns a list
177
1080.1.13 by me at id
ivle.database.User: Add an authenticate() method, and a hash_password()
178
    @staticmethod
179
    def hash_password(password):
180
        return md5.md5(password).hexdigest()
181
1080.1.6 by matt.giuca
ivle.database.User: Added get_by_login method.
182
    @classmethod
183
    def get_by_login(cls, store, login):
184
        """
185
        Get the User from the db associated with a given store and
186
        login.
187
        """
1080.1.7 by matt.giuca
The new ivle.database.User class is now used in Request and usrmgt, which
188
        return store.find(cls, cls.login == unicode(login)).one()
1080.1.25 by me at id
ivle.database: Add Subject, Semester and Offering.
189
1080.1.39 by Matt Giuca
ivle.database: Added __all__ to the top of the file.
190
# SUBJECTS AND ENROLMENTS #
191
1080.1.26 by me at id
ivle.database: Add an Enrolment class, and reference(set)s between all of the
192
class Subject(Storm):
1080.1.25 by me at id
ivle.database: Add Subject, Semester and Offering.
193
    __storm_table__ = "subject"
194
195
    id = Int(primary=True, name="subjectid")
196
    code = Unicode(name="subj_code")
197
    name = Unicode(name="subj_name")
198
    short_name = Unicode(name="subj_short_name")
199
    url = Unicode()
200
1080.1.26 by me at id
ivle.database: Add an Enrolment class, and reference(set)s between all of the
201
    offerings = ReferenceSet(id, 'Offering.subject_id')
202
1080.1.25 by me at id
ivle.database: Add Subject, Semester and Offering.
203
    __init__ = _kwarg_init
204
205
    def __repr__(self):
206
        return "<%s '%s'>" % (type(self).__name__, self.short_name)
207
1080.1.26 by me at id
ivle.database: Add an Enrolment class, and reference(set)s between all of the
208
class Semester(Storm):
1080.1.25 by me at id
ivle.database: Add Subject, Semester and Offering.
209
    __storm_table__ = "semester"
210
211
    id = Int(primary=True, name="semesterid")
212
    year = Unicode()
213
    semester = Unicode()
214
    active = Bool()
215
1080.1.26 by me at id
ivle.database: Add an Enrolment class, and reference(set)s between all of the
216
    offerings = ReferenceSet(id, 'Offering.semester_id')
217
1080.1.25 by me at id
ivle.database: Add Subject, Semester and Offering.
218
    __init__ = _kwarg_init
219
220
    def __repr__(self):
221
        return "<%s %s/%s>" % (type(self).__name__, self.year, self.semester)
222
1080.1.26 by me at id
ivle.database: Add an Enrolment class, and reference(set)s between all of the
223
class Offering(Storm):
1080.1.25 by me at id
ivle.database: Add Subject, Semester and Offering.
224
    __storm_table__ = "offering"
225
226
    id = Int(primary=True, name="offeringid")
227
    subject_id = Int(name="subject")
228
    subject = Reference(subject_id, Subject.id)
229
    semester_id = Int(name="semesterid")
230
    semester = Reference(semester_id, Semester.id)
231
    groups_student_permissions = Unicode()
232
1080.1.26 by me at id
ivle.database: Add an Enrolment class, and reference(set)s between all of the
233
    enrolments = ReferenceSet(id, 'Enrolment.offering_id')
234
1080.1.25 by me at id
ivle.database: Add Subject, Semester and Offering.
235
    __init__ = _kwarg_init
236
237
    def __repr__(self):
238
        return "<%s %r in %r>" % (type(self).__name__, self.subject,
239
                                  self.semester)
1080.1.26 by me at id
ivle.database: Add an Enrolment class, and reference(set)s between all of the
240
241
class Enrolment(Storm):
242
    __storm_table__ = "enrolment"
243
    __storm_primary__ = "user_id", "offering_id"
244
245
    user_id = Int(name="loginid")
246
    user = Reference(user_id, User.id)
247
    offering_id = Int(name="offeringid")
248
    offering = Reference(offering_id, Offering.id)
249
    notes = Unicode()
250
    active = Bool()
251
252
    __init__ = _kwarg_init
253
254
    def __repr__(self):
255
        return "<%s %r in %r>" % (type(self).__name__, self.user,
256
                                  self.offering)
1080.1.36 by William Grant
ivle.database: Add ProjectSet, Project, ProjectGroup, ProjectGroupMembership
257
1080.1.39 by Matt Giuca
ivle.database: Added __all__ to the top of the file.
258
# PROJECTS #
259
1080.1.36 by William Grant
ivle.database: Add ProjectSet, Project, ProjectGroup, ProjectGroupMembership
260
class ProjectSet(Storm):
261
    __storm_table__ = "project_set"
262
263
    id = Int(name="projectsetid", primary=True)
264
    offering_id = Int(name="offeringid")
265
    offering = Reference(offering_id, Offering.id)
266
    max_students_per_group = Int()
267
268
    __init__ = _kwarg_init
269
270
    def __repr__(self):
271
        return "<%s %d in %r>" % (type(self).__name__, self.id,
272
                                  self.offering)
273
274
class Project(Storm):
275
    __storm_table__ = "project"
276
277
    id = Int(name="projectid", primary=True)
278
    synopsis = Unicode()
279
    url = Unicode()
280
    project_set_id = Int(name="projectsetid")
281
    project_set = Reference(project_set_id, ProjectSet.id)
282
    deadline = DateTime()
283
284
    __init__ = _kwarg_init
285
286
    def __repr__(self):
287
        return "<%s '%s' in %r>" % (type(self).__name__, self.synopsis,
288
                                  self.project_set.offering)
289
290
class ProjectGroup(Storm):
291
    __storm_table__ = "project_group"
292
293
    id = Int(name="groupid", primary=True)
294
    name = Unicode(name="groupnm")
295
    project_set_id = Int(name="projectsetid")
296
    project_set = Reference(project_set_id, ProjectSet.id)
297
    nick = Unicode()
298
    created_by_id = Int(name="createdby")
299
    created_by = Reference(created_by_id, User.id)
300
    epoch = DateTime()
301
302
    __init__ = _kwarg_init
303
304
    def __repr__(self):
305
        return "<%s %s in %r>" % (type(self).__name__, self.name,
306
                                  self.project_set.offering)
307
1080.1.41 by William Grant
ivle.database.ProjectGroup: Add 'members' property, returning a sequence of
308
    @property
309
    def members(self):
310
        return Store.of(self).find(User,
311
            ProjectGroupMembership.project_group_id == self.id,
312
            User.id == ProjectGroupMembership.user_id)
313
1080.1.36 by William Grant
ivle.database: Add ProjectSet, Project, ProjectGroup, ProjectGroupMembership
314
class ProjectGroupMembership(Storm):
315
    __storm_table__ = "group_member"
316
    __storm_primary__ = "user_id", "project_group_id"
317
318
    user_id = Int(name="loginid")
319
    user = Reference(user_id, User.id)
320
    project_group_id = Int(name="groupid")
321
    project_group = Reference(project_group_id, ProjectGroup.id)
322
323
    __init__ = _kwarg_init
324
325
    def __repr__(self):
326
        return "<%s %r in %r>" % (type(self).__name__, self.user,
327
                                  self.project_group)
328
1080.1.40 by Matt Giuca
ivle.database: Added Worksheet and Exercise classes (more to come in this
329
# WORKSHEETS AND EXERCISES #
330
331
class Exercise(Storm):
332
    # Note: Table "problem" is called "Exercise" in the Object layer, since
333
    # it's called that everywhere else.
334
    __storm_table__ = "problem"
335
336
    id = Int(primary=True, name="problemid")
337
    name = Unicode(name="identifier")
338
    spec = Unicode()
339
340
    __init__ = _kwarg_init
341
342
    def __repr__(self):
343
        return "<%s %s>" % (type(self).__name__, self.name)
344
345
class Worksheet(Storm):
346
    __storm_table__ = "worksheet"
347
348
    id = Int(primary=True, name="worksheetid")
349
    # XXX subject is not linked to a Subject object. This is a property of
350
    # the database, and will be refactored.
351
    subject = Unicode()
352
    name = Unicode(name="identifier")
353
    assessable = Bool()
354
    mtime = DateTime()
355
356
    __init__ = _kwarg_init
357
358
    def __repr__(self):
359
        return "<%s %s>" % (type(self).__name__, self.name)
1080.1.47 by Matt Giuca
ivle.database: Added Worksheet.get_by_name method.
360
361
    # XXX Refactor this - make it an instance method of Subject rather than a
362
    # class method of Worksheet. Can't do that now because Subject isn't
363
    # linked referentially to the Worksheet.
364
    @classmethod
365
    def get_by_name(cls, store, subjectname, worksheetname):
366
        """
367
        Get the Worksheet from the db associated with a given store, subject
368
        name and worksheet name.
369
        """
370
        return store.find(cls, cls.subject == unicode(subjectname),
371
            cls.name == unicode(worksheetname)).one()