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

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

Use ChangePasswordRequestPage? also for applicants.

Change password requests require a registration number.

  • Property svn:keywords set to Id
File size: 12.1 KB
Line 
1## $Id: applicant.py 8039 2012-04-04 11:25:41Z 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.students.vocabularies import RegNumNotInSource
35from waeup.kofa.utils.helpers import attrs_to_fields
36from waeup.kofa.applicants.interfaces import IApplicant, IApplicantEdit
37from waeup.kofa.applicants.workflow import application_states_dict
38
39class Applicant(grok.Container):
40    grok.implements(IApplicant,IApplicantEdit)
41    grok.provides(IApplicant)
42
43    def __init__(self):
44        super(Applicant, self).__init__()
45        self.password = None
46        IWorkflowInfo(self).fireTransition('init')
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 translated_state(self):
61        return application_states_dict[self.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        kofa_utils = getUtility(IKofaUtils)
79        return kofa_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        student['studycourse'].current_level = self.__parent__.entry_level
117        self._copyPassportImage(student)
118        # Update the catalog
119        notify(grok.ObjectModifiedEvent(student))
120
121        # Save application slip
122        self._createApplicationPDF(student, view=view)
123
124        return True, 'Student %s created' % student.student_id
125
126    def _createApplicationPDF(self, student, view=None):
127        """Create an application slip as PDF and store it in student folder.
128        """
129        file_store = getUtility(IExtFileStore)
130        applicant_slip = getAdapter(self, IPDF, name='application_slip')(
131            view=view)
132        file_id = IFileStoreNameChooser(student).chooseName(
133            attr="application_slip.pdf")
134        file_store.createFile(file_id, StringIO(applicant_slip))
135        return
136
137    def _copyPassportImage(self, student):
138        """Copy any passport image over to student location.
139        """
140        file_store = getUtility(IExtFileStore)
141        appl_file = file_store.getFileByContext(self)
142        if appl_file is None:
143            return
144        stud_file_id = IFileStoreNameChooser(student).chooseName(
145            attr="passport.jpg")
146        file_store.createFile(stud_file_id, appl_file)
147        return
148
149# Set all attributes of Applicant required in IApplicant as field
150# properties. Doing this, we do not have to set initial attributes
151# ourselves and as a bonus we get free validation when an attribute is
152# set.
153Applicant = attrs_to_fields(Applicant)
154
155class ApplicantCatalog(grok.Indexes):
156    """A catalog indexing :class:`Applicant` instances in the ZODB.
157    """
158    grok.site(University)
159    grok.name('applicants_catalog')
160    grok.context(IApplicant)
161
162    #access_code = index.Field(attribute='access_code')
163    applicant_id = index.Field(attribute='applicant_id')
164    reg_number = index.Field(attribute='reg_number')
165    email = index.Field(attribute='email')
166
167class ApplicantFactory(grok.GlobalUtility):
168    """A factory for applicants.
169    """
170    grok.implements(IFactory)
171    grok.name(u'waeup.Applicant')
172    title = u"Create a new applicant.",
173    description = u"This factory instantiates new applicant instances."
174
175    def __call__(self, *args, **kw):
176        return Applicant()
177
178    def getInterfaces(self):
179        return implementedBy(Applicant)
180
181
182#: The file id marker for applicant passport images
183APPLICANT_IMAGE_STORE_NAME = 'img-applicant'
184
185class ApplicantImageNameChooser(grok.Adapter):
186    """A file id chooser for :class:`Applicant` objects.
187
188    `context` is an :class:`Applicant` instance.
189
190    The :class:`ApplicantImageNameChooser` can build/check file ids
191    for :class:`Applicant` objects suitable for use with
192    :class:`ExtFileStore` instances. The delivered file_id contains
193    the file id marker for :class:`Applicant` object and the
194    registration number or access code of the context applicant. Also
195    the name of the connected applicant container will be part of the
196    generated file id.
197
198    This chooser is registered as an adapter providing
199    :class:`waeup.kofa.interfaces.IFileStoreNameChooser`.
200
201    File store name choosers like this one are only convenience
202    components to ease the task of creating file ids for applicant
203    objects. You are nevertheless encouraged to use them instead of
204    manually setting up filenames for applicants.
205
206    .. seealso:: :mod:`waeup.kofa.imagestorage`
207
208    """
209    grok.context(IApplicant)
210    grok.implements(IFileStoreNameChooser)
211
212    def checkName(self, name=None, attr=None):
213        """Check whether the given name is a valid file id for the context.
214
215        Returns ``True`` only if `name` equals the result of
216        :meth:`chooseName`.
217
218        The `attr` parameter is not taken into account for
219        :class:`Applicant` context as the single passport image is the
220        only file we store for applicants.
221        """
222        return name == self.chooseName()
223
224    def chooseName(self, name=None, attr=None):
225        """Get a valid file id for applicant context.
226
227        *Example:*
228
229        For an applicant with applicant_id. ``'app2001_1234'``
230        and stored in an applicants container called
231        ``'mycontainer'``, this chooser would create:
232
233          ``'__img-applicant__mycontainer/app2001_1234.jpg'``
234
235        meaning that the passport image of this applicant would be
236        stored in the site-wide file storage in path:
237
238          ``mycontainer/app2001_1234.jpg``
239
240        If the context applicant has no parent, ``'_default'`` is used
241        as parent name.
242
243        The `attr` parameter is not taken into account for
244        :class:`Applicant` context as the single passport image is the
245        only file we store for applicants.
246
247        """
248        parent_name = getattr(
249            getattr(self.context, '__parent__', None),
250            '__name__', '_default')
251        marked_filename = '__%s__%s/%s.jpg' % (
252            APPLICANT_IMAGE_STORE_NAME,
253            parent_name, self.context.applicant_id)
254        return marked_filename
255
256
257class ApplicantImageStoreHandler(DefaultFileStoreHandler, grok.GlobalUtility):
258    """Applicant specific image handling.
259
260    This handler knows in which path in a filestore to store applicant
261    images and how to turn this kind of data into some (browsable)
262    file object.
263
264    It is called from the global file storage, when it wants to
265    get/store a file with a file id starting with
266    ``__img-applicant__`` (the marker string for applicant images).
267
268    Like each other file store handler it does not handle the files
269    really (this is done by the global file store) but only computes
270    paths and things like this.
271    """
272    grok.implements(IFileStoreHandler)
273    grok.name(APPLICANT_IMAGE_STORE_NAME)
274
275    def pathFromFileID(self, store, root, file_id):
276        """All applicants images are filed in directory ``applicants``.
277        """
278        marker, filename, basename, ext = store.extractMarker(file_id)
279        sub_root = os.path.join(root, 'applicants')
280        return super(ApplicantImageStoreHandler, self).pathFromFileID(
281            store, sub_root, basename)
282
283    def createFile(self, store, root, filename, file_id, file):
284        """Create a browsable file-like object.
285        """
286        ext = os.path.splitext(filename)[1].lower()
287        if ext not in ['.jpg', '.png']:
288            raise ValueError('Only .jpg and .png allowed')
289        # call super method to ensure that any old files with
290        # different filename extension are deleted.
291        file, path, file_obj =  super(
292            ApplicantImageStoreHandler, self).createFile(
293            store, root,  filename, file_id, file)
294        return file, path, KofaImageFile(
295            file_obj.filename, file_obj.data)
296
297@grok.subscribe(IApplicant, grok.IObjectAddedEvent)
298def handle_applicant_added(applicant, event):
299    """If an applicant is added local and site roles are assigned.
300    """
301    role_manager = IPrincipalRoleManager(applicant)
302    role_manager.assignRoleToPrincipal(
303        'waeup.local.ApplicationOwner', applicant.applicant_id)
304    # Assign current principal the global Applicant role
305    role_manager = IPrincipalRoleManager(grok.getSite())
306    role_manager.assignRoleToPrincipal(
307        'waeup.Applicant', applicant.applicant_id)
308
309    # Assign global applicant role for new applicant (alternative way)
310    #account = IUserAccount(applicant)
311    #account.roles = ['waeup.Applicant']
312
313    return
314
315@grok.subscribe(IApplicant, grok.IObjectRemovedEvent)
316def handle_applicant_removed(applicant, event):
317    """If an applicant is removed a message is logged.
318    """
319    comment = 'Applicant record removed'
320    target = applicant.applicant_id
321    try:
322        grok.getSite()['applicants'].logger.info('%s - %s' % (
323            target, comment))
324    except KeyError:
325        # If we delete an entire university instance there won't be
326        # an applicants subcontainer
327        return
328    return
Note: See TracBrowser for help on using the repository browser.