source: main/waeup.sirp/trunk/src/waeup/sirp/applicants/applicant.py @ 7402

Last change on this file since 7402 was 7391, checked in by uli, 13 years ago

Store application slip when creating students.

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