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

Last change on this file since 8959 was 8855, checked in by Henrik Bettermann, 12 years ago

Do not translate system messages.

Improve pagetemplates for better translation.

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