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

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

Do not fire init transition when state has been imported.

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