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

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

Catch RequiredMissing? error when creating students from applicants.

Add state to ApplicantsCatalog?.

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