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

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

Copy over passport images when turning applicants into students.

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