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

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

Add student after student attributes have been set. Otherwise the students_catalog won't index these attributes.

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