source: main/waeup.sirp/trunk/src/waeup/sirp/students/student.py @ 7386

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

Define two different (convenience?) methods: fullname and display_fullname. The first one is a hyphen-separated string of all name parts and is meant for indexing only. The second one uses the SIRPUtils fullname method and is meant for displaying the fullname in the UI. It can be easily customized according to the requirements of the school.

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