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

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

Remove unused imports.

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