source: main/waeup.sirp/branches/henrik-bootstrap/src/waeup/sirp/applicants/applicant.py @ 10009

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

Put viewlets into their own module.

Remove unneeded imports.

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