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

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

Move year_range and academic_sessions_vocab to waeup.sirp.interfaces.

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