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

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

Set student 'admitted' after copying.

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