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

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

Implement fullname constructor as a global utility method which can easily be subject to customization. I think it's better to leave this here rather than selecting the constructor method via the customization object.

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