source: main/waeup.sirp/branches/ulif-schoolgrades/src/waeup/sirp/applicants/applicant.py @ 7774

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

Fix imports, clean up.

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