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

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

Add test for applicant copier. The test does also check if the passport image has been copied and renamed and if the application slip pdf file has been created in the student's file system folder.

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