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

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

A faster and shorter way to check for applicant image existence.

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