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

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

Replace middlenames by middlename.

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