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

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

Export also container_code. This eases re-importing data.

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