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

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

We need to customize the attributes to be copied. Therefore it's easier to define these attributes in a list.

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