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

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

We have to ensure that students are properly indexed after creation from applicants.

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