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

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

Save full applicant_id in log file not only application number.

  • Property svn:keywords set to Id
File size: 9.7 KB
Line 
1## $Id: applicant.py 7244 2011-12-01 10:49:41Z 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
19from random import SystemRandom as r
20import grok
21from grok import index
22from zope.component.interfaces import IFactory
23from zope.securitypolicy.interfaces import IPrincipalRoleManager
24from zope.interface import implementedBy
25from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState
26from waeup.sirp.app import University
27from waeup.sirp.image import WAeUPImageFile
28from waeup.sirp.imagestorage import DefaultFileStoreHandler
29from waeup.sirp.interfaces import (
30    IObjectHistory, IFileStoreHandler, IFileStoreNameChooser)
31from waeup.sirp.utils.helpers import attrs_to_fields, get_current_principal
32from waeup.sirp.applicants.interfaces import (
33    IApplicant, IApplicantEdit,
34    )
35from waeup.sirp.applicants.workflow import INITIALIZED
36
37def generate_applicant_id(container=None):
38    if container is not None:
39        aid = u"%s_%d" % (container.code, r().randint(99999,1000000))
40        while aid in container.keys():
41            aid = u"%s_%d" % (container.code, r().randint(99999,1000000))
42        return aid
43    else:
44        # In some tests we don't use containers
45        return u"xxx_1234"
46
47class Applicant(grok.Model):
48    grok.implements(IApplicant,IApplicantEdit)
49    grok.provides(IApplicant)
50
51    def __init__(self, container=None):
52        super(Applicant, self).__init__()
53        self.applicant_id = generate_applicant_id(container)
54        self.password = None
55        IWorkflowInfo(self).fireTransition('init')
56        self.application_date = None
57        return
58
59    def loggerInfo(self, ob_class, comment=None):
60        target = self.applicant_id
61        return grok.getSite()['applicants'].logger_info(ob_class,target,comment)
62
63    @property
64    def state(self):
65        state = IWorkflowState(self).getState()
66        return state
67
68    @property
69    def history(self):
70        history = IObjectHistory(self)
71        return history
72
73    @property
74    def application_number(self):
75        return self.applicant_id.split('_')[1]
76
77    @property
78    def fullname(self):
79        # We do not necessarily have the middlenames attribute
80        middlenames = getattr(self, 'middlenames', None)
81        if middlenames:
82            return '%s %s %s' % (self.firstname,
83                middlenames, self.lastname)
84        else:
85            return '%s %s' % (self.firstname, self.lastname)
86
87# Set all attributes of Applicant required in IApplicant as field
88# properties. Doing this, we do not have to set initial attributes
89# ourselves and as a bonus we get free validation when an attribute is
90# set.
91Applicant = attrs_to_fields(Applicant)
92
93class ApplicantCatalog(grok.Indexes):
94    """A catalog indexing :class:`Applicant` instances in the ZODB.
95    """
96    grok.site(University)
97    grok.name('applicants_catalog')
98    grok.context(IApplicant)
99
100    access_code = index.Field(attribute='access_code')
101    applicant_id = index.Field(attribute='applicant_id')
102    reg_no = index.Field(attribute='reg_no')
103
104class ApplicantFactory(grok.GlobalUtility):
105    """A factory for applicants.
106    """
107    grok.implements(IFactory)
108    grok.name(u'waeup.Applicant')
109    title = u"Create a new applicant.",
110    description = u"This factory instantiates new applicant instances."
111
112    def __call__(self, *args, **kw):
113        return Applicant(kw['container'])
114
115    def getInterfaces(self):
116        return implementedBy(Applicant)
117
118
119#: The file id marker for applicant passport images
120APPLICANT_IMAGE_STORE_NAME = 'img-applicant'
121
122class ApplicantImageNameChooser(grok.Adapter):
123    """A file id chooser for :class:`Applicant` objects.
124
125    `context` is an :class:`Applicant` instance.
126
127    The :class:`ApplicantImageNameChooser` can build/check file ids
128    for :class:`Applicant` objects suitable for use with
129    :class:`ExtFileStore` instances. The delivered file_id contains
130    the file id marker for :class:`Applicant` object and the
131    registration number or access code of the context applicant. Also
132    the name of the connected applicant container will be part of the
133    generated file id.
134
135    This chooser is registered as an adapter providing
136    :class:`waeup.sirp.interfaces.IFileStoreNameChooser`.
137
138    File store name choosers like this one are only convenience
139    components to ease the task of creating file ids for applicant
140    objects. You are nevertheless encouraged to use them instead of
141    manually setting up filenames for applicants.
142
143    .. seealso:: :mod:`waeup.sirp.imagestorage`
144
145    """
146    grok.context(IApplicant)
147    grok.implements(IFileStoreNameChooser)
148
149    def checkName(self, name=None, attr=None):
150        """Check whether the given name is a valid file id for the context.
151
152        Returns ``True`` only if `name` equals the result of
153        :meth:`chooseName`.
154
155        The `attr` parameter is not taken into account for
156        :class:`Applicant` context as the single passport image is the
157        only file we store for applicants.
158        """
159        return name == self.chooseName()
160
161    def chooseName(self, name=None, attr=None):
162        """Get a valid file id for applicant context.
163
164        *Example:*
165
166        For an applicant with applicant_id. ``'app2001_1234'``
167        and stored in an applicants container called
168        ``'mycontainer'``, this chooser would create:
169
170          ``'__img-applicant__mycontainer/app2001_1234.jpg'``
171
172        meaning that the passport image of this applicant would be
173        stored in the site-wide file storage in path:
174
175          ``mycontainer/app2001_1234.jpg``
176
177        If the context applicant has no parent, ``'_default'`` is used
178        as parent name.
179
180        The `attr` parameter is not taken into account for
181        :class:`Applicant` context as the single passport image is the
182        only file we store for applicants.
183
184        """
185        parent_name = getattr(
186            getattr(self.context, '__parent__', None),
187            '__name__', '_default')
188        marked_filename = '__%s__%s/%s.jpg' % (
189            APPLICANT_IMAGE_STORE_NAME,
190            parent_name, self.context.applicant_id)
191        return marked_filename
192
193
194class ApplicantImageStoreHandler(DefaultFileStoreHandler, grok.GlobalUtility):
195    """Applicant specific image handling.
196
197    This handler knows in which path in a filestore to store applicant
198    images and how to turn this kind of data into some (browsable)
199    file object.
200
201    It is called from the global file storage, when it wants to
202    get/store a file with a file id starting with
203    ``__img-applicant__`` (the marker string for applicant images).
204
205    Like each other file store handler it does not handle the files
206    really (this is done by the global file store) but only computes
207    paths and things like this.
208    """
209    grok.implements(IFileStoreHandler)
210    grok.name(APPLICANT_IMAGE_STORE_NAME)
211
212    def pathFromFileID(self, store, root, file_id):
213        """All applicants images are filed in directory ``applicants``.
214        """
215        marker, filename, basename, ext = store.extractMarker(file_id)
216        sub_root = os.path.join(root, 'applicants')
217        return super(ApplicantImageStoreHandler, self).pathFromFileID(
218            store, sub_root, basename)
219
220    def createFile(self, store, root, filename, file_id, file):
221        """Create a browsable file-like object.
222        """
223        ext = os.path.splitext(filename)[1].lower()
224        if ext not in ['.jpg', '.png']:
225            raise ValueError('Only .jpg and .png allowed')
226        # call super method to ensure that any old files with
227        # different filename extension are deleted.
228        file, path, file_obj =  super(
229            ApplicantImageStoreHandler, self).createFile(
230            store, root,  filename, file_id, file)
231        return file, path, WAeUPImageFile(
232            file_obj.filename, file_obj.data)
233
234@grok.subscribe(IApplicant, grok.IObjectAddedEvent)
235def handle_applicant_added(applicant, event):
236    """If an applicant is added local and site roles are assigned.
237    """
238    role_manager = IPrincipalRoleManager(applicant)
239    role_manager.assignRoleToPrincipal(
240        'waeup.local.ApplicationOwner', applicant.applicant_id)
241    # Assign current principal the global Applicant role
242    role_manager = IPrincipalRoleManager(grok.getSite())
243    role_manager.assignRoleToPrincipal(
244        'waeup.Applicant', applicant.applicant_id)
245
246    # Assign global applicant role for new applicant (alternative way)
247    #account = IUserAccount(applicant)
248    #account.roles = ['waeup.Applicant']
249
250    return
251
252@grok.subscribe(IApplicant, grok.IObjectRemovedEvent)
253def handle_applicant_removed(applicant, event):
254    """If an applicant is removed a message is logged.
255    """
256    comment = 'Applicant record removed'
257    target = applicant.applicant_id
258    # In some tests we don't have a principal
259    try:
260        user = get_current_principal().id
261    except (TypeError, AttributeError):
262        return
263    try:
264        grok.getSite()['applicants'].logger.info('%s - %s - %s' % (
265            user, target, comment))
266    except KeyError:
267        # If we delete an entire university instance there won't be
268        # an applicants subcontainer
269        return
270    return
Note: See TracBrowser for help on using the repository browser.