source: main/waeup.kofa/trunk/src/waeup/kofa/students/student.py @ 8384

Last change on this file since 8384 was 8375, checked in by Henrik Bettermann, 13 years ago

Do not fire init transition when state has been imported.

  • Property svn:keywords set to Id
File size: 10.0 KB
Line 
1## $Id: student.py 8375 2012-05-07 05:16:39Z henrik $
2##
3## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17##
18"""
19Container for the various objects owned by students.
20"""
21import os
22import grok
23from hurry.workflow.interfaces import IWorkflowState, IWorkflowInfo
24from zope.component import getUtility, createObject
25from zope.component.interfaces import IFactory
26from zope.interface import implementedBy
27from zope.securitypolicy.interfaces import IPrincipalRoleManager
28from waeup.kofa.image import KofaImageFile
29from waeup.kofa.imagestorage import DefaultFileStoreHandler
30from waeup.kofa.interfaces import (
31    IObjectHistory, IUserAccount, IFileStoreNameChooser, IFileStoreHandler,
32    IKofaUtils, CLEARANCE, registration_states_vocab)
33from waeup.kofa.students.accommodation import StudentAccommodation
34from waeup.kofa.students.interfaces import IStudent, IStudentNavigation
35from waeup.kofa.students.payments import StudentPaymentsContainer
36from waeup.kofa.students.studycourse import StudentStudyCourse
37from waeup.kofa.students.utils import generate_student_id
38from waeup.kofa.utils.helpers import attrs_to_fields
39
40
41class Student(grok.Container):
42    """This is a student container for the various objects
43    owned by students.
44    """
45    grok.implements(IStudent, IStudentNavigation)
46    grok.provides(IStudent)
47
48    def __init__(self):
49        super(Student, self).__init__()
50        # The site doesn't exist in unit tests
51        try:
52            students = grok.getSite()['students']
53            self.student_id = generate_student_id(students,'?')
54        except TypeError:
55            self.student_id = u'Z654321'
56        self.password = None
57        return
58
59    def loggerInfo(self, ob_class, comment=None):
60        target = self.__name__
61        return grok.getSite()['students'].logger_info(ob_class,target,comment)
62
63    @property
64    def display_fullname(self):
65        middlename = getattr(self, 'middlename', None)
66        kofa_utils = getUtility(IKofaUtils)
67        return kofa_utils.fullname(self.firstname, self.lastname, middlename)
68
69    @property
70    def fullname(self):
71        middlename = getattr(self, 'middlename', None)
72        if middlename:
73            return '%s-%s-%s' % (self.firstname.lower(),
74                middlename.lower(), self.lastname.lower())
75        else:
76            return '%s-%s' % (self.firstname.lower(), self.lastname.lower())
77
78    @property
79    def state(self):
80        state = IWorkflowState(self).getState()
81        return state
82
83    @property
84    def translated_state(self):
85        state = registration_states_vocab.getTermByToken(
86            self.state).title
87        return state
88
89    @property
90    def history(self):
91        history = IObjectHistory(self)
92        return history
93
94    def getStudent(self):
95        return self
96
97    @property
98    def certcode(self):
99        cert = getattr(self.get('studycourse', None), 'certificate', None)
100        if cert is not None:
101            return cert.code
102        return
103
104    @property
105    def faccode(self):
106        cert = getattr(self.get('studycourse', None), 'certificate', None)
107        if cert is not None:
108            return cert.__parent__.__parent__.__parent__.code
109        return
110
111    @property
112    def depcode(self):
113        cert = getattr(self.get('studycourse', None), 'certificate', None)
114        if cert is not None:
115            return cert.__parent__.__parent__.code
116        return
117
118    @property
119    def current_session(self):
120        session = getattr(
121            self.get('studycourse', None), 'current_session', None)
122        return session
123
124    @property
125    def current_mode(self):
126        certificate = getattr(
127            self.get('studycourse', None), 'certificate', None)
128        if certificate is not None:
129            return certificate.study_mode
130        return
131
132# Set all attributes of Student required in IStudent as field
133# properties. Doing this, we do not have to set initial attributes
134# ourselves and as a bonus we get free validation when an attribute is
135# set.
136Student = attrs_to_fields(Student)
137
138class StudentFactory(grok.GlobalUtility):
139    """A factory for students.
140    """
141    grok.implements(IFactory)
142    grok.name(u'waeup.Student')
143    title = u"Create a new student.",
144    description = u"This factory instantiates new student instances."
145
146    def __call__(self, *args, **kw):
147        return Student()
148
149    def getInterfaces(self):
150        return implementedBy(Student)
151
152@grok.subscribe(IStudent, grok.IObjectAddedEvent)
153def handle_student_added(student, event):
154    """If a student is added all subcontainers are automatically added
155    and the transition create is fired. The latter produces a logging
156    message.
157    """
158    if student.state == CLEARANCE:
159        student.clearance_locked = False
160    else:
161        student.clearance_locked = True
162    studycourse = createObject(u'waeup.StudentStudyCourse')
163    student['studycourse'] = studycourse
164    payments = StudentPaymentsContainer()
165    student['payments'] = payments
166    accommodation = StudentAccommodation()
167    student['accommodation'] = accommodation
168    # Assign global student role for new student
169    account = IUserAccount(student)
170    account.roles = ['waeup.Student']
171    # Assign local StudentRecordOwner role
172    role_manager = IPrincipalRoleManager(student)
173    role_manager.assignRoleToPrincipal(
174        'waeup.local.StudentRecordOwner', student.student_id)
175    if student.state is None:
176        IWorkflowInfo(student).fireTransition('create')
177    return
178
179@grok.subscribe(IStudent, grok.IObjectRemovedEvent)
180def handle_student_removed(student, event):
181    """If a student is removed a message is logged.
182    """
183    comment = 'Student record removed'
184    target = student.student_id
185    try:
186        grok.getSite()['students'].logger.info('%s - %s' % (
187            target, comment))
188    except KeyError:
189        # If we delete an entire university instance there won't be
190        # a students subcontainer
191        return
192    return
193
194#: The file id marker for student files
195STUDENT_FILE_STORE_NAME = 'file-student'
196
197class StudentFileNameChooser(grok.Adapter):
198    """A file id chooser for :class:`Student` objects.
199
200    `context` is an :class:`Student` instance.
201
202    The :class:`StudentImageNameChooser` can build/check file ids for
203    :class:`Student` objects suitable for use with
204    :class:`ExtFileStore` instances. The delivered file_id contains
205    the file id marker for :class:`Student` object and the student id
206    of the context student.
207
208    This chooser is registered as an adapter providing
209    :class:`waeup.kofa.interfaces.IFileStoreNameChooser`.
210
211    File store name choosers like this one are only convenience
212    components to ease the task of creating file ids for student
213    objects. You are nevertheless encouraged to use them instead of
214    manually setting up filenames for students.
215
216    .. seealso:: :mod:`waeup.kofa.imagestorage`
217
218    """
219    grok.context(IStudent)
220    grok.implements(IFileStoreNameChooser)
221
222    def checkName(self, name=None, attr=None):
223        """Check whether the given name is a valid file id for the context.
224
225        Returns ``True`` only if `name` equals the result of
226        :meth:`chooseName`.
227
228        """
229        return name == self.chooseName()
230
231    def chooseName(self, attr, name=None):
232        """Get a valid file id for student context.
233
234        *Example:*
235
236        For a student with student id ``'A123456'`` and
237        with attr ``'nice_image.jpeg'`` stored in
238        the students container this chooser would create:
239
240          ``'__file-student__students/A/A123456/nice_image_A123456.jpeg'``
241
242        meaning that the nice image of this applicant would be
243        stored in the site-wide file storage in path:
244
245          ``students/A/A123456/nice_image_A123456.jpeg``
246
247        """
248        basename, ext = os.path.splitext(attr)
249        stud_id = self.context.student_id
250        marked_filename = '__%s__%s/%s/%s_%s%s' % (
251            STUDENT_FILE_STORE_NAME, stud_id[0], stud_id, basename,
252            stud_id, ext)
253        return marked_filename
254
255
256class StudentFileStoreHandler(DefaultFileStoreHandler, grok.GlobalUtility):
257    """Student specific file handling.
258
259    This handler knows in which path in a filestore to store student
260    files and how to turn this kind of data into some (browsable)
261    file object.
262
263    It is called from the global file storage, when it wants to
264    get/store a file with a file id starting with
265    ``__file-student__`` (the marker string for student files).
266
267    Like each other file store handler it does not handle the files
268    really (this is done by the global file store) but only computes
269    paths and things like this.
270    """
271    grok.implements(IFileStoreHandler)
272    grok.name(STUDENT_FILE_STORE_NAME)
273
274    def pathFromFileID(self, store, root, file_id):
275        """All student files are put in directory ``students``.
276        """
277        marker, filename, basename, ext = store.extractMarker(file_id)
278        sub_root = os.path.join(root, 'students')
279        return super(StudentFileStoreHandler, self).pathFromFileID(
280            store, sub_root, basename)
281
282    def createFile(self, store, root, filename, file_id, file):
283        """Create a browsable file-like object.
284        """
285        # call super method to ensure that any old files with
286        # different filename extension are deleted.
287        file, path, file_obj =  super(
288            StudentFileStoreHandler, self).createFile(
289            store, root,  filename, file_id, file)
290        return file, path, KofaImageFile(
291            file_obj.filename, file_obj.data)
Note: See TracBrowser for help on using the repository browser.