source: main/waeup.sirp/trunk/src/waeup/sirp/applicants/interfaces.py @ 6583

Last change on this file since 6583 was 6538, checked in by uli, 13 years ago

Create default passport image via IFileRetrieval utilities.

File size: 17.0 KB
Line 
1##
2## interfaces.py
3## Login : <uli@pu.smp.net>
4## Started on  Sun Jan 16 15:30:01 2011 Uli Fouquet
5## $Id$
6##
7## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
8## This program is free software; you can redistribute it and/or modify
9## it under the terms of the GNU General Public License as published by
10## the Free Software Foundation; either version 2 of the License, or
11## (at your option) any later version.
12##
13## This program is distributed in the hope that it will be useful,
14## but WITHOUT ANY WARRANTY; without even the implied warranty of
15## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16## GNU General Public License for more details.
17##
18## You should have received a copy of the GNU General Public License
19## along with this program; if not, write to the Free Software
20## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21##
22"""Interfaces of the university application package.
23"""
24import os
25import re
26import waeup.sirp.browser
27
28from grokcore.content.interfaces import IContainer
29
30from zope import schema
31from zope.interface import Interface, Attribute, provider
32from zope.component import getUtilitiesFor
33from zope.pluggableauth.interfaces import IPrincipalInfo
34from zope.security.interfaces import IGroupClosureAwarePrincipal as IPrincipal
35from zc.sourcefactory.basic import BasicSourceFactory
36from waeup.sirp.image.schema import ImageFile
37from waeup.sirp.image.image import WAeUPImageFile
38from waeup.sirp.interfaces import IWAeUPObject
39from waeup.sirp.university.vocabularies import application_categories
40from waeup.sirp.applicants.vocabularies import (
41  year_range, application_types_vocab, application_pins_vocab,
42  lgas_vocab, CertificateSource, AppCatCertificateSource, GenderSource,
43  )
44
45IMAGE_PATH = os.path.join(
46    os.path.dirname(waeup.sirp.browser.__file__),
47    'static'
48    )
49DEFAULT_PASSPORT_IMAGE_MALE = open(
50    os.path.join(IMAGE_PATH, 'placeholder_m.jpg')).read()
51DEFAULT_PASSPORT_IMAGE_FEMALE = open(
52    os.path.join(IMAGE_PATH, 'placeholder_f.jpg')).read()
53
54# Define a valiation method for email addresses
55class NotAnEmailAddress(schema.ValidationError):
56    __doc__ = u"Invalid email address"
57
58check_email = re.compile(r"[a-zA-Z0-9._%-]+@([a-zA-Z0-9-]+.)*[a-zA-Z]{2,4}").match
59def validate_email(value):
60    if not check_email(value):
61        raise NotAnEmailAddress(value)
62    return True
63
64@provider(schema.interfaces.IContextAwareDefaultFactory)
65def default_passport_image(context):
66    """A default value factory for ImageFile fields.
67
68    Returns some default image as WAeUPImageFile. We cannot set the
69    default directly in ImageFile fields, as, if we want to set
70    max_size or min_size as well, some utility lookups are needed
71    which are not possible during startup.
72
73    Developers which use IContextAwareDefaultFactories like this one
74    always should make sure that the delivered default meets all
75    constraints of the field that makes use of this default value
76    provider.
77    """
78    from waeup.sirp.image import createWAeUPImageFile
79    imagefile = createWAeUPImageFile(
80        'placeholder_m.jpg',
81        open(os.path.join(IMAGE_PATH, 'placeholder_m.jpg'), 'r')
82        )
83    return imagefile
84    return WAeUPImageFile(
85        'placeholder_m.jpg', DEFAULT_PASSPORT_IMAGE_MALE)
86
87class ApplicantContainerProviderSource(BasicSourceFactory):
88    """A source offering all available applicants container types.
89
90    The values returned by this source are names of utilities that can
91    create :class:`ApplicantContainer` instances. So, if you get a
92    name like ``'myactype'`` from this source, then you can do:
93
94      >>> from zope.component import getUtility
95      >>> p = getUtility(IApplicantsContainerProvider, name=myactype)
96      >>> my_applicants_container = p.factory()
97
98    Or you can access class-attributes like
99
100      >>> my_applicants_container.container_title
101      'Pretty'
102
103    """
104    def getValues(self):
105        """Returns a list of ``(<name>, <provider>)`` tuples.
106
107        Here ``<name>`` is the name under which an
108        :class:``ApplicantContainerProvider`` was registered as a
109        utility and ``<provider>`` is the utility itself.
110        """
111        return getUtilitiesFor(IApplicantsContainerProvider)
112
113    def getToken(self, value):
114        """Return the name of the ``(<name>, <provider>)`` tuple.
115        """
116        return value[0]
117
118    def getTitle(self, value):
119        """Get a 'title - description' string for a container type.
120        """
121        factory = value[1].factory
122        return "%s - %s" % (
123            factory.container_title, factory.container_description)
124
125class IResultEntry(IWAeUPObject):
126    subject = schema.TextLine(
127        title = u'Subject',
128        description = u'The subject',
129        required=False,
130        )
131    score = schema.TextLine(
132        title = u'Score',
133        description = u'The score',
134        required=False,
135        )
136
137class IApplicantsRoot(IWAeUPObject, IContainer):
138    """A container for university applicants containers.
139    """
140    pass
141
142class IApplicantsContainer(IWAeUPObject):
143    """An applicants container contains university applicants.
144
145    """
146
147    container_title = Attribute(
148        u'classattribute: title for type of container')
149    container_description = Attribute(
150        u'classattribute: description for type of container')
151
152
153    code = schema.TextLine(
154        title = u'Code',
155        default = u'-',
156        required = True,
157        readonly = True,
158        )
159
160    title = schema.TextLine(
161        title = u'Title',
162        required = True,
163        default = u'-',
164        readonly = True,
165        )
166
167    prefix = schema.Choice(
168        title = u'Application target',
169        required = True,
170        default = None,
171        source = application_types_vocab,
172        readonly = True,
173        )
174
175    year = schema.Choice(
176        title = u'Year of entrance',
177        required = True,
178        default = None,
179        values = year_range(),
180        readonly = True,
181        )
182
183    provider = schema.Choice(
184        title = u'Applicants container type',
185        required = True,
186        default = None,
187        source = ApplicantContainerProviderSource(),
188        readonly = True,
189        )
190
191    ac_prefix = schema.Choice(
192        title = u'Access code prefix',
193        required = True,
194        default = None,
195        source = application_pins_vocab,
196        )
197
198    application_category = schema.Choice(
199        title = u'Category for the grouping of certificates',
200        required = True,
201        default = None,
202        source = application_categories,
203        )
204
205    description = schema.Text(
206        title = u'Human readable description in reST format',
207        required = False,
208        default = u'''This text can been seen by anonymous users.
209Here we put information about the study courses provided, the application procedure and deadlines.'''
210        )
211
212    startdate = schema.Date(
213        title = u'Application start date',
214        required = False,
215        default = None,
216        )
217
218    enddate = schema.Date(
219        title = u'Application closing date',
220        required = False,
221        default = None,
222        )
223
224    strict_deadline = schema.Bool(
225        title = u'Forbid additions after deadline (enddate)',
226        required = True,
227        default = True,
228        )
229
230    def archive(id=None):
231        """Create on-dist archive of applicants stored in this term.
232
233        If id is `None`, all applicants are archived.
234
235        If id contains a single id string, only the respective
236        applicants are archived.
237
238        If id contains a list of id strings all of the respective
239        applicants types are saved to disk.
240        """
241
242    def clear(id=None, archive=True):
243        """Remove applicants of type given by 'id'.
244
245        Optionally archive the applicants.
246
247        If id is `None`, all applicants are archived.
248
249        If id contains a single id string, only the respective
250        applicants are archived.
251
252        If id contains a list of id strings all of the respective
253        applicant types are saved to disk.
254
255        If `archive` is ``False`` none of the archive-handling is done
256        and respective applicants are simply removed from the
257        database.
258        """
259
260class IApplicantsContainerAdd(IApplicantsContainer):
261    """An applicants container contains university applicants.
262    """
263    prefix = schema.Choice(
264        title = u'Application target',
265        required = True,
266        default = None,
267        source = application_types_vocab,
268        readonly = False,
269        )
270
271    year = schema.Choice(
272        title = u'Year of entrance',
273        required = True,
274        default = None,
275        values = year_range(),
276        readonly = False,
277        )
278
279    provider = schema.Choice(
280        title = u'Applicants container type',
281        required = True,
282        default = None,
283        source = ApplicantContainerProviderSource(),
284        readonly = False,
285        )
286
287IApplicantsContainerAdd[
288    'prefix'].order =  IApplicantsContainer['prefix'].order
289IApplicantsContainerAdd[
290    'year'].order =  IApplicantsContainer['year'].order
291IApplicantsContainerAdd[
292    'provider'].order =  IApplicantsContainer['provider'].order
293
294class IApplicantBaseData(IWAeUPObject):
295    """The data for an applicant.
296
297    This is a base interface with no field (except ``reg_no``)
298    required. For use with importers, forms, etc., please use one of
299    the derived interfaces below, which set more fields to required
300    state, depending on use-case.
301    """
302    history = Attribute('Object history, a list of messages.')
303    state = Attribute('Returns the application state of an applicant')
304    application_date = Attribute('Date of submission, used for export only')
305
306    #def getApplicantsRootLogger():
307    #    """Returns the logger from the applicants root object
308    #    """
309
310    def loggerInfo(ob_class, comment):
311        """Adds an INFO message to the log file
312        """
313
314    reg_no = schema.TextLine(
315        title = u'JAMB Registration Number',
316        readonly = True,
317        )
318    access_code = schema.TextLine(
319        title = u'Access Code',
320        required = False,
321        readonly = True,
322        )
323    course1 = schema.Choice(
324        title = u'1st Choice Course of Study',
325        source = AppCatCertificateSource(),
326        required = True,
327        )
328    course2 = schema.Choice(
329        title = u'2nd Choice Course of Study',
330        source = AppCatCertificateSource(),
331        required = False,
332        )
333    firstname = schema.TextLine(
334        title = u'First Name',
335        required = True,
336        )
337    middlenames = schema.TextLine(
338        title = u'Middle Names',
339        required = False,
340        )
341    lastname = schema.TextLine(
342        title = u'Last Name (Surname)',
343        required = True,
344        )
345    date_of_birth = schema.Date(
346        title = u'Date of Birth',
347        required = True,
348        )
349    lga = schema.Choice(
350        source = lgas_vocab,
351        title = u'State/LGA',
352        default = 'foreigner',
353        required = True,
354        )
355    sex = schema.Choice(
356        title = u'Sex',
357        source = GenderSource(),
358        default = u'm',
359        required = True,
360        )
361    email = schema.ASCIILine(
362        title = u'Email',
363        required = False,
364        constraint=validate_email,
365        )
366    phone = schema.Int(
367        title = u'Phone',
368        description = u'Enter phone number with country code and without spaces.',
369        required = False,
370        )
371    passport = ImageFile(
372        title = u'Passport Photograph',
373        #default = DEFAULT_PASSPORT_IMAGE_MALE,
374        defaultFactory = default_passport_image,
375        description = u'Maximun file size is 20 kB.',
376        required = True,
377        max_size = 20480,
378        )
379
380    #
381    # Process Data
382    #
383    screening_score = schema.Int(
384        title = u'Screening Score',
385        required = False,
386        )
387    screening_venue = schema.TextLine(
388        title = u'Screening Venue',
389        required = False,
390        )
391    course_admitted = schema.Choice(
392        title = u'Admitted Course of Study',
393        source = CertificateSource(),
394        default = None,
395        required = False,
396        )
397    notice = schema.Text(
398        title = u'Notice',
399        required = False,
400        )
401    student_id = schema.TextLine(
402        title = u'Student ID',
403        required = False,
404        readonly = True,
405        )
406    locked = schema.Bool(
407        title = u'Form locked',
408        default = False,
409        )
410
411class IApplicant(IApplicantBaseData):
412    """An applicant.
413
414    This is basically the applicant base data. Here we repeat the
415    fields from base data if we have to set the `required` attribute
416    to True (which is the default).
417    """
418
419class IApplicantEdit(IApplicantBaseData):
420    """An applicant.
421
422    Here we can repeat the fields from base data and set the
423    `required` and `readonly` attributes to True to further restrict
424    the data access. We cannot omit fields. This has to be done in the
425    respective form page.
426    """
427    screening_score = schema.Int(
428        title = u'Screening Score',
429        required = False,
430        readonly = True,
431        )
432    screening_venue = schema.TextLine(
433        title = u'Screening Venue',
434        required = False,
435        readonly = True,
436        )
437    course_admitted = schema.Choice(
438        title = u'Admitted Course of Study',
439        source = CertificateSource(),
440        default = None,
441        required = False,
442        readonly = True,
443        )
444    # entry_session is inherited from the container
445    #entry_session = schema.Choice(
446    #    source = entry_session_vocab,
447    #    title = u'Entry Session',
448    #    required = False,
449    #    readonly = True
450    #    )
451    notice = schema.Text(
452        title = u'Notice',
453        required = False,
454        readonly = True,
455        )
456
457class IApplicantPrincipalInfo(IPrincipalInfo):
458    """Infos about principals that are applicants.
459    """
460    access_code = Attribute("The Access Code the user purchased")
461
462class IApplicantPrincipal(IPrincipal):
463    """A principal that is an applicant.
464
465    This interface extends zope.security.interfaces.IPrincipal and
466    requires also an `id` and other attributes defined there.
467    """
468    access_code = schema.TextLine(
469        title = u'Access Code',
470        description = u'The access code purchased by the user.',
471        required = True,
472        readonly = True)
473
474class IApplicantsFormChallenger(Interface):
475    """A challenger that uses a browser form to collect applicant
476       credentials.
477    """
478    loginpagename = schema.TextLine(
479        title = u'Loginpagename',
480        description = u"""Name of the login form used by challenger.
481
482        The form must provide an ``access_code`` input field.
483        """)
484
485    accesscode_field = schema.TextLine(
486        title = u'Access code field',
487        description = u'''Field of the login page which is looked up for
488                          access_code''',
489        default = u'access_code',
490        )
491
492
493class IApplicantSessionCredentials(Interface):
494    """Interface for storing and accessing applicant credentials in a
495       session.
496    """
497
498    def __init__(access_code):
499        """Create applicant session credentials."""
500
501    def getAccessCode():
502        """Return the access code."""
503
504
505class IApplicantsContainerProvider(Interface):
506    """A provider for applicants containers.
507
508    Applicants container providers are meant to be looked up as
509    utilities. This way we can find all applicant container types
510    defined somewhere.
511
512    Each applicants container provider registered as utility provides
513    one container type and one should be able to call the `factory`
514    attribute to create an instance of the requested container type.
515
516    .. THE FOLLOWING SHOULD GO INTO SPHINX DOCS (and be tested)
517
518    Samples:
519
520    Given, you had an IApplicantsContainer implementation somewhere
521    and you would like to make it findable on request, then you would
522    normally create an appropriate provider utility like this::
523
524      import grok
525      from waeup.sirp.applicants.interfaces import IApplicantsContainerProvider
526
527      class MyContainerProvider(grok.GlobalUtility):
528          grok.implements(IApplicantsContainerProvider)
529          grok.name('MyContainerProvider') # Must be unique
530          factory = MyContainer # A class implementing IApplicantsContainer
531                                # or derivations thereof.
532
533    This utility would be registered on startup and could then be used
534    like this:
535
536      >>> from zope.component import getAllUtilitiesRegisteredFor
537      >>> from waeup.sirp.applicants.interfaces import (
538      ...     IApplicantsContainerProvider)
539      >>> all_providers = getAllUtilitiesRegisteredFor(
540      ...     IApplicantsContainerProvider)
541      >>> all_providers
542      [<MyContainerProvider object at 0x...>]
543
544    You could look up this specific provider by name:
545
546      >>> from zope.component import getUtility
547      >>> p = getUtility(IApplicantsContainerProvider, name='MyProvider')
548      >>> p
549      <MyContainerProvider object at 0x...>
550
551    An applicants container would then be created like this:
552
553      >>> provider = all_providers[0]
554      >>> container = provider.factory()
555      >>> container
556      <MyContainer object at 0x...>
557
558    """
559    factory = Attribute("A class that can create instances of the "
560                        "requested container type")
Note: See TracBrowser for help on using the repository browser.