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

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

Rollback of r7341 as discussed on the phone. But now we get other problems (see email). A regression test will follow.

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