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

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

This my recent version of the createStudent method.

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