source: main/waeup.kofa/trunk/src/waeup/kofa/applicants/applicant.py @ 8331

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

Remove entry_level attribute. The entry level is given by the start level of the study course admitted.

  • Property svn:keywords set to Id
File size: 12.2 KB
RevLine 
[7192]1## $Id: applicant.py 8311 2012-04-29 19:47:24Z henrik $
[5272]2##
[7192]3## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
[5272]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.
[7192]8##
[5272]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.
[7192]13##
[5272]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##
[7063]18import os
[5272]19import grok
[7391]20from cStringIO import StringIO
[6115]21from grok import index
[7438]22from zope.component import getUtility, createObject, getAdapter
[5318]23from zope.component.interfaces import IFactory
[7421]24from zope.event import notify
[7240]25from zope.securitypolicy.interfaces import IPrincipalRoleManager
[5479]26from zope.interface import implementedBy
[6296]27from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState
[7811]28from waeup.kofa.app import University
[7819]29from waeup.kofa.image import KofaImageFile
[7811]30from waeup.kofa.imagestorage import DefaultFileStoreHandler
31from waeup.kofa.interfaces import (
[7819]32    IObjectHistory, IFileStoreHandler, IFileStoreNameChooser, IKofaUtils,
[7409]33    IExtFileStore, IPDF, IUserAccount)
[8311]34from waeup.kofa.interfaces import MessageFactory as _
[7811]35from waeup.kofa.students.vocabularies import RegNumNotInSource
36from waeup.kofa.utils.helpers import attrs_to_fields
37from waeup.kofa.applicants.interfaces import IApplicant, IApplicantEdit
38from waeup.kofa.applicants.workflow import application_states_dict
[5272]39
[7250]40class Applicant(grok.Container):
[6196]41    grok.implements(IApplicant,IApplicantEdit)
[5483]42    grok.provides(IApplicant)
[5272]43
[7260]44    def __init__(self):
[6296]45        super(Applicant, self).__init__()
[7240]46        self.password = None
[6301]47        IWorkflowInfo(self).fireTransition('init')
[6476]48        self.application_date = None
[7260]49        self.applicant_id = None
[6331]50        return
[6296]51
[6475]52    def loggerInfo(self, ob_class, comment=None):
[7244]53        target = self.applicant_id
[6475]54        return grok.getSite()['applicants'].logger_info(ob_class,target,comment)
55
[6324]56    @property
[8286]57    def state(self):
[7686]58        return IWorkflowState(self).getState()
[6324]59
[6339]60    @property
[7686]61    def translated_state(self):
[8286]62        return application_states_dict[self.state]
[7686]63
64    @property
[6339]65    def history(self):
66        history = IObjectHistory(self)
67        return history
68
[7240]69    @property
70    def application_number(self):
[7260]71        try:
72            return self.applicant_id.split('_')[1]
73        except AttributeError:
74            return None
[7240]75
76    @property
[7364]77    def display_fullname(self):
[7356]78        middlename = getattr(self, 'middlename', None)
[7819]79        kofa_utils = getUtility(IKofaUtils)
[7811]80        return kofa_utils.fullname(self.firstname, self.lastname, middlename)
[7240]81
[7391]82    def createStudent(self, view=None):
[8311]83        """Create a student, fill with base data, create an application slip
84        and copy applicant data.
[7338]85        """
[7344]86        # Is applicant in the correct state?
[8286]87        if self.state != 'admitted':
[8311]88            return False, _('Applicant has not yet been admitted.')
[7344]89        # Does registration number exist?
[7338]90        student = createObject(u'waeup.Student')
[7341]91        try:
92            student.reg_number = self.reg_number
93        except RegNumNotInSource:
[8311]94            return False, _('Registration Number exists.')
[7344]95        # Has the course_admitted field been properly filled?
[7347]96        if self.course_admitted is None:
[8311]97            return False, _('No course admitted provided.')
[7344]98        # Add student object
[7356]99        student.firstname = self.firstname
100        student.lastname = self.lastname
[7341]101        student.sex = self.sex
102        student.date_of_birth = self.date_of_birth
103        student.email = self.email
104        student.phone = self.phone
[7406]105        site = grok.getSite()
106        site['students'].addStudent(student)
107        self.student_id = student.student_id
108        IWorkflowInfo(self).fireTransition('create')
[7391]109
[7406]110        # Set password
[7409]111        IUserAccount(student).setPassword(self.application_number)
[7406]112
[7420]113        # Save the certificate and set session attributes
[7347]114        student['studycourse'].certificate = self.course_admitted
[7420]115        student['studycourse'].entry_session = self.__parent__.year
116        student['studycourse'].current_session = self.__parent__.year
[8311]117        student['studycourse'].current_level = self.course_admitted.start_level
[7388]118        self._copyPassportImage(student)
[7421]119        # Update the catalog
120        notify(grok.ObjectModifiedEvent(student))
[7355]121
[7391]122        # Save application slip
123        self._createApplicationPDF(student, view=view)
[7355]124
[8311]125        return True, _('Student %s created') % student.student_id
[7338]126
[7391]127    def _createApplicationPDF(self, student, view=None):
128        """Create an application slip as PDF and store it in student folder.
129        """
130        file_store = getUtility(IExtFileStore)
131        applicant_slip = getAdapter(self, IPDF, name='application_slip')(
132            view=view)
133        file_id = IFileStoreNameChooser(student).chooseName(
134            attr="application_slip.pdf")
135        file_store.createFile(file_id, StringIO(applicant_slip))
136        return
137
[7388]138    def _copyPassportImage(self, student):
139        """Copy any passport image over to student location.
140        """
141        file_store = getUtility(IExtFileStore)
142        appl_file = file_store.getFileByContext(self)
[7417]143        if appl_file is None:
144            return
[7388]145        stud_file_id = IFileStoreNameChooser(student).chooseName(
146            attr="passport.jpg")
[7417]147        file_store.createFile(stud_file_id, appl_file)
[7388]148        return
149
[5983]150# Set all attributes of Applicant required in IApplicant as field
151# properties. Doing this, we do not have to set initial attributes
152# ourselves and as a bonus we get free validation when an attribute is
153# set.
[6115]154Applicant = attrs_to_fields(Applicant)
[5272]155
[6115]156class ApplicantCatalog(grok.Indexes):
157    """A catalog indexing :class:`Applicant` instances in the ZODB.
158    """
159    grok.site(University)
160    grok.name('applicants_catalog')
161    grok.context(IApplicant)
162
[7376]163    #access_code = index.Field(attribute='access_code')
[7240]164    applicant_id = index.Field(attribute='applicant_id')
[7270]165    reg_number = index.Field(attribute='reg_number')
[8039]166    email = index.Field(attribute='email')
[6115]167
[5318]168class ApplicantFactory(grok.GlobalUtility):
[6620]169    """A factory for applicants.
[5318]170    """
171    grok.implements(IFactory)
172    grok.name(u'waeup.Applicant')
173    title = u"Create a new applicant.",
174    description = u"This factory instantiates new applicant instances."
175
176    def __call__(self, *args, **kw):
[7260]177        return Applicant()
[5318]178
179    def getInterfaces(self):
[5479]180        return implementedBy(Applicant)
[7063]181
182
183#: The file id marker for applicant passport images
184APPLICANT_IMAGE_STORE_NAME = 'img-applicant'
185
186class ApplicantImageNameChooser(grok.Adapter):
187    """A file id chooser for :class:`Applicant` objects.
188
189    `context` is an :class:`Applicant` instance.
190
191    The :class:`ApplicantImageNameChooser` can build/check file ids
192    for :class:`Applicant` objects suitable for use with
193    :class:`ExtFileStore` instances. The delivered file_id contains
194    the file id marker for :class:`Applicant` object and the
195    registration number or access code of the context applicant. Also
196    the name of the connected applicant container will be part of the
197    generated file id.
198
199    This chooser is registered as an adapter providing
[7811]200    :class:`waeup.kofa.interfaces.IFileStoreNameChooser`.
[7063]201
202    File store name choosers like this one are only convenience
203    components to ease the task of creating file ids for applicant
204    objects. You are nevertheless encouraged to use them instead of
205    manually setting up filenames for applicants.
206
[7811]207    .. seealso:: :mod:`waeup.kofa.imagestorage`
[7063]208
209    """
210    grok.context(IApplicant)
211    grok.implements(IFileStoreNameChooser)
212
[7067]213    def checkName(self, name=None, attr=None):
[7063]214        """Check whether the given name is a valid file id for the context.
215
216        Returns ``True`` only if `name` equals the result of
217        :meth:`chooseName`.
[7067]218
219        The `attr` parameter is not taken into account for
220        :class:`Applicant` context as the single passport image is the
221        only file we store for applicants.
[7063]222        """
223        return name == self.chooseName()
224
[7105]225    def chooseName(self, name=None, attr=None):
[7063]226        """Get a valid file id for applicant context.
227
228        *Example:*
229
[7240]230        For an applicant with applicant_id. ``'app2001_1234'``
[7063]231        and stored in an applicants container called
232        ``'mycontainer'``, this chooser would create:
233
[7240]234          ``'__img-applicant__mycontainer/app2001_1234.jpg'``
[7063]235
236        meaning that the passport image of this applicant would be
237        stored in the site-wide file storage in path:
238
[7240]239          ``mycontainer/app2001_1234.jpg``
[7063]240
241        If the context applicant has no parent, ``'_default'`` is used
242        as parent name.
[7067]243
244        The `attr` parameter is not taken into account for
245        :class:`Applicant` context as the single passport image is the
246        only file we store for applicants.
247
[7063]248        """
249        parent_name = getattr(
250            getattr(self.context, '__parent__', None),
251            '__name__', '_default')
252        marked_filename = '__%s__%s/%s.jpg' % (
253            APPLICANT_IMAGE_STORE_NAME,
[7240]254            parent_name, self.context.applicant_id)
[7063]255        return marked_filename
256
257
258class ApplicantImageStoreHandler(DefaultFileStoreHandler, grok.GlobalUtility):
259    """Applicant specific image handling.
260
261    This handler knows in which path in a filestore to store applicant
262    images and how to turn this kind of data into some (browsable)
263    file object.
264
265    It is called from the global file storage, when it wants to
266    get/store a file with a file id starting with
267    ``__img-applicant__`` (the marker string for applicant images).
268
269    Like each other file store handler it does not handle the files
270    really (this is done by the global file store) but only computes
271    paths and things like this.
272    """
273    grok.implements(IFileStoreHandler)
274    grok.name(APPLICANT_IMAGE_STORE_NAME)
275
276    def pathFromFileID(self, store, root, file_id):
277        """All applicants images are filed in directory ``applicants``.
278        """
279        marker, filename, basename, ext = store.extractMarker(file_id)
[7121]280        sub_root = os.path.join(root, 'applicants')
281        return super(ApplicantImageStoreHandler, self).pathFromFileID(
282            store, sub_root, basename)
[7063]283
284    def createFile(self, store, root, filename, file_id, file):
285        """Create a browsable file-like object.
286        """
[7121]287        ext = os.path.splitext(filename)[1].lower()
288        if ext not in ['.jpg', '.png']:
289            raise ValueError('Only .jpg and .png allowed')
290        # call super method to ensure that any old files with
291        # different filename extension are deleted.
292        file, path, file_obj =  super(
293            ApplicantImageStoreHandler, self).createFile(
294            store, root,  filename, file_id, file)
[7819]295        return file, path, KofaImageFile(
[7121]296            file_obj.filename, file_obj.data)
[7240]297
298@grok.subscribe(IApplicant, grok.IObjectAddedEvent)
299def handle_applicant_added(applicant, event):
300    """If an applicant is added local and site roles are assigned.
301    """
302    role_manager = IPrincipalRoleManager(applicant)
303    role_manager.assignRoleToPrincipal(
304        'waeup.local.ApplicationOwner', applicant.applicant_id)
305    # Assign current principal the global Applicant role
306    role_manager = IPrincipalRoleManager(grok.getSite())
307    role_manager.assignRoleToPrincipal(
308        'waeup.Applicant', applicant.applicant_id)
309
310    # Assign global applicant role for new applicant (alternative way)
311    #account = IUserAccount(applicant)
312    #account.roles = ['waeup.Applicant']
313
314    return
315
316@grok.subscribe(IApplicant, grok.IObjectRemovedEvent)
317def handle_applicant_removed(applicant, event):
318    """If an applicant is removed a message is logged.
319    """
320    comment = 'Applicant record removed'
321    target = applicant.applicant_id
322    try:
[7652]323        grok.getSite()['applicants'].logger.info('%s - %s' % (
324            target, comment))
[7240]325    except KeyError:
326        # If we delete an entire university instance there won't be
327        # an applicants subcontainer
328        return
[8285]329    # Remove also any passport image.
330    file_store = getUtility(IExtFileStore)
331    file_store.deleteFileByContext(applicant)
[7240]332    return
Note: See TracBrowser for help on using the repository browser.