source: main/waeup.sirp/trunk/src/waeup/sirp/applicants/applicant.py @ 10687

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

Enable translation of application_state.

  • Property svn:keywords set to Id
File size: 12.0 KB
RevLine 
[7192]1## $Id: applicant.py 7686 2012-02-23 09:34:11Z 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
[6115]28from waeup.sirp.app import University
[7321]29from waeup.sirp.image import SIRPImageFile
[7063]30from waeup.sirp.imagestorage import DefaultFileStoreHandler
31from waeup.sirp.interfaces import (
[7388]32    IObjectHistory, IFileStoreHandler, IFileStoreNameChooser, ISIRPUtils,
[7409]33    IExtFileStore, IPDF, IUserAccount)
[7391]34from waeup.sirp.students.vocabularies import RegNumNotInSource
[7652]35from waeup.sirp.utils.helpers import attrs_to_fields
[7438]36from waeup.sirp.applicants.interfaces import IApplicant, IApplicantEdit
[7686]37from waeup.sirp.applicants.workflow import application_states_dict
[5272]38
[7250]39class Applicant(grok.Container):
[6196]40    grok.implements(IApplicant,IApplicantEdit)
[5483]41    grok.provides(IApplicant)
[5272]42
[7260]43    def __init__(self):
[6296]44        super(Applicant, self).__init__()
[7240]45        self.password = None
[6301]46        IWorkflowInfo(self).fireTransition('init')
[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
[6332]56    def state(self):
[7686]57        return IWorkflowState(self).getState()
[6324]58
[6339]59    @property
[7686]60    def translated_state(self):
61        return application_states_dict[self.state]
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)
[7359]78        sirp_utils = getUtility(ISIRPUtils)
79        return sirp_utils.fullname(self.firstname, self.lastname, middlename)
[7240]80
[7391]81    def createStudent(self, view=None):
[7344]82        """Create a student, fill with base data, create a StudentApplication
83        object and copy applicant data.
[7338]84        """
[7344]85        # Is applicant in the correct state?
86        if self.state != 'admitted':
87            return False, "Applicant has not yet been admitted."
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:
93            return False, 'Registration Number exists.'
[7344]94        # Has the course_admitted field been properly filled?
[7347]95        if self.course_admitted is None:
[7341]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
[7436]116        student['studycourse'].current_level = self.__parent__.entry_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
[7344]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')
[6115]165
[5318]166class ApplicantFactory(grok.GlobalUtility):
[6620]167    """A factory for applicants.
[5318]168    """
169    grok.implements(IFactory)
170    grok.name(u'waeup.Applicant')
171    title = u"Create a new applicant.",
172    description = u"This factory instantiates new applicant instances."
173
174    def __call__(self, *args, **kw):
[7260]175        return Applicant()
[5318]176
177    def getInterfaces(self):
[5479]178        return implementedBy(Applicant)
[7063]179
180
181#: The file id marker for applicant passport images
182APPLICANT_IMAGE_STORE_NAME = 'img-applicant'
183
184class ApplicantImageNameChooser(grok.Adapter):
185    """A file id chooser for :class:`Applicant` objects.
186
187    `context` is an :class:`Applicant` instance.
188
189    The :class:`ApplicantImageNameChooser` can build/check file ids
190    for :class:`Applicant` objects suitable for use with
191    :class:`ExtFileStore` instances. The delivered file_id contains
192    the file id marker for :class:`Applicant` object and the
193    registration number or access code of the context applicant. Also
194    the name of the connected applicant container will be part of the
195    generated file id.
196
197    This chooser is registered as an adapter providing
198    :class:`waeup.sirp.interfaces.IFileStoreNameChooser`.
199
200    File store name choosers like this one are only convenience
201    components to ease the task of creating file ids for applicant
202    objects. You are nevertheless encouraged to use them instead of
203    manually setting up filenames for applicants.
204
205    .. seealso:: :mod:`waeup.sirp.imagestorage`
206
207    """
208    grok.context(IApplicant)
209    grok.implements(IFileStoreNameChooser)
210
[7067]211    def checkName(self, name=None, attr=None):
[7063]212        """Check whether the given name is a valid file id for the context.
213
214        Returns ``True`` only if `name` equals the result of
215        :meth:`chooseName`.
[7067]216
217        The `attr` parameter is not taken into account for
218        :class:`Applicant` context as the single passport image is the
219        only file we store for applicants.
[7063]220        """
221        return name == self.chooseName()
222
[7105]223    def chooseName(self, name=None, attr=None):
[7063]224        """Get a valid file id for applicant context.
225
226        *Example:*
227
[7240]228        For an applicant with applicant_id. ``'app2001_1234'``
[7063]229        and stored in an applicants container called
230        ``'mycontainer'``, this chooser would create:
231
[7240]232          ``'__img-applicant__mycontainer/app2001_1234.jpg'``
[7063]233
234        meaning that the passport image of this applicant would be
235        stored in the site-wide file storage in path:
236
[7240]237          ``mycontainer/app2001_1234.jpg``
[7063]238
239        If the context applicant has no parent, ``'_default'`` is used
240        as parent name.
[7067]241
242        The `attr` parameter is not taken into account for
243        :class:`Applicant` context as the single passport image is the
244        only file we store for applicants.
245
[7063]246        """
247        parent_name = getattr(
248            getattr(self.context, '__parent__', None),
249            '__name__', '_default')
250        marked_filename = '__%s__%s/%s.jpg' % (
251            APPLICANT_IMAGE_STORE_NAME,
[7240]252            parent_name, self.context.applicant_id)
[7063]253        return marked_filename
254
255
256class ApplicantImageStoreHandler(DefaultFileStoreHandler, grok.GlobalUtility):
257    """Applicant specific image handling.
258
259    This handler knows in which path in a filestore to store applicant
260    images 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    ``__img-applicant__`` (the marker string for applicant images).
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(APPLICANT_IMAGE_STORE_NAME)
273
274    def pathFromFileID(self, store, root, file_id):
275        """All applicants images are filed in directory ``applicants``.
276        """
277        marker, filename, basename, ext = store.extractMarker(file_id)
[7121]278        sub_root = os.path.join(root, 'applicants')
279        return super(ApplicantImageStoreHandler, self).pathFromFileID(
280            store, sub_root, basename)
[7063]281
282    def createFile(self, store, root, filename, file_id, file):
283        """Create a browsable file-like object.
284        """
[7121]285        ext = os.path.splitext(filename)[1].lower()
286        if ext not in ['.jpg', '.png']:
287            raise ValueError('Only .jpg and .png allowed')
288        # call super method to ensure that any old files with
289        # different filename extension are deleted.
290        file, path, file_obj =  super(
291            ApplicantImageStoreHandler, self).createFile(
292            store, root,  filename, file_id, file)
[7321]293        return file, path, SIRPImageFile(
[7121]294            file_obj.filename, file_obj.data)
[7240]295
296@grok.subscribe(IApplicant, grok.IObjectAddedEvent)
297def handle_applicant_added(applicant, event):
298    """If an applicant is added local and site roles are assigned.
299    """
300    role_manager = IPrincipalRoleManager(applicant)
301    role_manager.assignRoleToPrincipal(
302        'waeup.local.ApplicationOwner', applicant.applicant_id)
303    # Assign current principal the global Applicant role
304    role_manager = IPrincipalRoleManager(grok.getSite())
305    role_manager.assignRoleToPrincipal(
306        'waeup.Applicant', applicant.applicant_id)
307
308    # Assign global applicant role for new applicant (alternative way)
309    #account = IUserAccount(applicant)
310    #account.roles = ['waeup.Applicant']
311
312    return
313
314@grok.subscribe(IApplicant, grok.IObjectRemovedEvent)
315def handle_applicant_removed(applicant, event):
316    """If an applicant is removed a message is logged.
317    """
318    comment = 'Applicant record removed'
319    target = applicant.applicant_id
320    try:
[7652]321        grok.getSite()['applicants'].logger.info('%s - %s' % (
322            target, comment))
[7240]323    except KeyError:
324        # If we delete an entire university instance there won't be
325        # an applicants subcontainer
326        return
327    return
Note: See TracBrowser for help on using the repository browser.