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

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

Some stuff to start school grade support. Messy, but a beginning. At least manage and edit of applicants provide some school grade selections.

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