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

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

KOFA -> Kofa

  • Property svn:keywords set to Id
File size: 12.0 KB
Line 
1## $Id: applicant.py 7819 2012-03-08 22:28:46Z 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
166class ApplicantFactory(grok.GlobalUtility):
167    """A factory for applicants.
168    """
169    grok.implements(IFactory)
170    grok.name(u'waeup.Applicant')
171    title = u"Create a new applicant.",
172    description = u"This factory instantiates new applicant instances."
173
174    def __call__(self, *args, **kw):
175        return Applicant()
176
177    def getInterfaces(self):
178        return implementedBy(Applicant)
179
180
181#: The file id marker for applicant passport images
182APPLICANT_IMAGE_STORE_NAME = 'img-applicant'
183
184class ApplicantImageNameChooser(grok.Adapter):
185    """A file id chooser for :class:`Applicant` objects.
186
187    `context` is an :class:`Applicant` instance.
188
189    The :class:`ApplicantImageNameChooser` can build/check file ids
190    for :class:`Applicant` objects suitable for use with
191    :class:`ExtFileStore` instances. The delivered file_id contains
192    the file id marker for :class:`Applicant` object and the
193    registration number or access code of the context applicant. Also
194    the name of the connected applicant container will be part of the
195    generated file id.
196
197    This chooser is registered as an adapter providing
198    :class:`waeup.kofa.interfaces.IFileStoreNameChooser`.
199
200    File store name choosers like this one are only convenience
201    components to ease the task of creating file ids for applicant
202    objects. You are nevertheless encouraged to use them instead of
203    manually setting up filenames for applicants.
204
205    .. seealso:: :mod:`waeup.kofa.imagestorage`
206
207    """
208    grok.context(IApplicant)
209    grok.implements(IFileStoreNameChooser)
210
211    def checkName(self, name=None, attr=None):
212        """Check whether the given name is a valid file id for the context.
213
214        Returns ``True`` only if `name` equals the result of
215        :meth:`chooseName`.
216
217        The `attr` parameter is not taken into account for
218        :class:`Applicant` context as the single passport image is the
219        only file we store for applicants.
220        """
221        return name == self.chooseName()
222
223    def chooseName(self, name=None, attr=None):
224        """Get a valid file id for applicant context.
225
226        *Example:*
227
228        For an applicant with applicant_id. ``'app2001_1234'``
229        and stored in an applicants container called
230        ``'mycontainer'``, this chooser would create:
231
232          ``'__img-applicant__mycontainer/app2001_1234.jpg'``
233
234        meaning that the passport image of this applicant would be
235        stored in the site-wide file storage in path:
236
237          ``mycontainer/app2001_1234.jpg``
238
239        If the context applicant has no parent, ``'_default'`` is used
240        as parent name.
241
242        The `attr` parameter is not taken into account for
243        :class:`Applicant` context as the single passport image is the
244        only file we store for applicants.
245
246        """
247        parent_name = getattr(
248            getattr(self.context, '__parent__', None),
249            '__name__', '_default')
250        marked_filename = '__%s__%s/%s.jpg' % (
251            APPLICANT_IMAGE_STORE_NAME,
252            parent_name, self.context.applicant_id)
253        return marked_filename
254
255
256class ApplicantImageStoreHandler(DefaultFileStoreHandler, grok.GlobalUtility):
257    """Applicant specific image handling.
258
259    This handler knows in which path in a filestore to store applicant
260    images and how to turn this kind of data into some (browsable)
261    file object.
262
263    It is called from the global file storage, when it wants to
264    get/store a file with a file id starting with
265    ``__img-applicant__`` (the marker string for applicant images).
266
267    Like each other file store handler it does not handle the files
268    really (this is done by the global file store) but only computes
269    paths and things like this.
270    """
271    grok.implements(IFileStoreHandler)
272    grok.name(APPLICANT_IMAGE_STORE_NAME)
273
274    def pathFromFileID(self, store, root, file_id):
275        """All applicants images are filed in directory ``applicants``.
276        """
277        marker, filename, basename, ext = store.extractMarker(file_id)
278        sub_root = os.path.join(root, 'applicants')
279        return super(ApplicantImageStoreHandler, self).pathFromFileID(
280            store, sub_root, basename)
281
282    def createFile(self, store, root, filename, file_id, file):
283        """Create a browsable file-like object.
284        """
285        ext = os.path.splitext(filename)[1].lower()
286        if ext not in ['.jpg', '.png']:
287            raise ValueError('Only .jpg and .png allowed')
288        # call super method to ensure that any old files with
289        # different filename extension are deleted.
290        file, path, file_obj =  super(
291            ApplicantImageStoreHandler, self).createFile(
292            store, root,  filename, file_id, file)
293        return file, path, KofaImageFile(
294            file_obj.filename, file_obj.data)
295
296@grok.subscribe(IApplicant, grok.IObjectAddedEvent)
297def handle_applicant_added(applicant, event):
298    """If an applicant is added local and site roles are assigned.
299    """
300    role_manager = IPrincipalRoleManager(applicant)
301    role_manager.assignRoleToPrincipal(
302        'waeup.local.ApplicationOwner', applicant.applicant_id)
303    # Assign current principal the global Applicant role
304    role_manager = IPrincipalRoleManager(grok.getSite())
305    role_manager.assignRoleToPrincipal(
306        'waeup.Applicant', applicant.applicant_id)
307
308    # Assign global applicant role for new applicant (alternative way)
309    #account = IUserAccount(applicant)
310    #account.roles = ['waeup.Applicant']
311
312    return
313
314@grok.subscribe(IApplicant, grok.IObjectRemovedEvent)
315def handle_applicant_removed(applicant, event):
316    """If an applicant is removed a message is logged.
317    """
318    comment = 'Applicant record removed'
319    target = applicant.applicant_id
320    try:
321        grok.getSite()['applicants'].logger.info('%s - %s' % (
322            target, comment))
323    except KeyError:
324        # If we delete an entire university instance there won't be
325        # an applicants subcontainer
326        return
327    return
Note: See TracBrowser for help on using the repository browser.