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

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

Remove application_date from schema. This date should not be displayed on forms and slips since we have the same information in the object history. It's needed in export files for sorting and filtering.

File size: 16.8 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 regarding student applicants and related components.
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    return WAeUPImageFile(
79        'placeholder_m.jpg', DEFAULT_PASSPORT_IMAGE_MALE)
80
81class ApplicantContainerProviderSource(BasicSourceFactory):
82    """A source offering all available applicants container types.
83
84    The values returned by this source are names of utilities that can
85    create :class:`ApplicantContainer` instances. So, if you get a
86    name like ``'myactype'`` from this source, then you can do:
87
88      >>> from zope.component import getUtility
89      >>> p = getUtility(IApplicantsContainerProvider, name=myactype)
90      >>> my_applicants_container = p.factory()
91
92    Or you can access class-attributes like
93
94      >>> my_applicants_container.container_title
95      'Pretty'
96
97    """
98    def getValues(self):
99        """Returns a list of ``(<name>, <provider>)`` tuples.
100
101        Here ``<name>`` is the name under which an
102        :class:``ApplicantContainerProvider`` was registered as a
103        utility and ``<provider>`` is the utility itself.
104        """
105        return getUtilitiesFor(IApplicantsContainerProvider)
106
107    def getToken(self, value):
108        """Return the name of the ``(<name>, <provider>)`` tuple.
109        """
110        return value[0]
111
112    def getTitle(self, value):
113        """Get a 'title - description' string for a container type.
114        """
115        factory = value[1].factory
116        return "%s - %s" % (
117            factory.container_title, factory.container_description)
118
119class IResultEntry(IWAeUPObject):
120    subject = schema.TextLine(
121        title = u'Subject',
122        description = u'The subject',
123        required=False,
124        )
125    score = schema.TextLine(
126        title = u'Score',
127        description = u'The score',
128        required=False,
129        )
130
131class IApplicantsRoot(IWAeUPObject, IContainer):
132    """A container for university applicants containers.
133    """
134    pass
135
136class IApplicantsContainer(IWAeUPObject):
137    """An applicants container contains university applicants.
138
139    """
140
141    container_title = Attribute(
142        u'classattribute: title for type of container')
143    container_description = Attribute(
144        u'classattribute: description for type of container')
145
146
147    code = schema.TextLine(
148        title = u'Code',
149        default = u'-',
150        required = True,
151        readonly = True,
152        )
153
154    title = schema.TextLine(
155        title = u'Title',
156        required = True,
157        default = u'-',
158        readonly = True,
159        )
160
161    prefix = schema.Choice(
162        title = u'Application target',
163        required = True,
164        default = None,
165        source = application_types_vocab,
166        readonly = True,
167        )
168
169    year = schema.Choice(
170        title = u'Year of entrance',
171        required = True,
172        default = None,
173        values = year_range(),
174        readonly = True,
175        )
176
177    provider = schema.Choice(
178        title = u'Applicants container type',
179        required = True,
180        default = None,
181        source = ApplicantContainerProviderSource(),
182        readonly = True,
183        )
184
185    ac_prefix = schema.Choice(
186        title = u'Access code prefix',
187        required = True,
188        default = None,
189        source = application_pins_vocab,
190        )
191
192    application_category = schema.Choice(
193        title = u'Category for the grouping of study courses',
194        required = True,
195        default = None,
196        source = application_categories,
197        )
198
199    description = schema.Text(
200        title = u'Human readable description in reST format',
201        required = False,
202        default = u'No description yet.'
203        )
204
205    startdate = schema.Date(
206        title = u'Date when the application period starts',
207        required = False,
208        default = None,
209        )
210
211    enddate = schema.Date(
212        title = u'Date when the application period ends',
213        required = False,
214        default = None,
215        )
216
217    strict_deadline = schema.Bool(
218        title = u'Forbid additions after deadline (enddate)',
219        required = True,
220        default = True,
221        )
222
223    def archive(id=None):
224        """Create on-dist archive of applicants stored in this term.
225
226        If id is `None`, all applicants are archived.
227
228        If id contains a single id string, only the respective
229        applicants are archived.
230
231        If id contains a list of id strings all of the respective
232        applicants types are saved to disk.
233        """
234
235    def clear(id=None, archive=True):
236        """Remove applicants of type given by 'id'.
237
238        Optionally archive the applicants.
239
240        If id is `None`, all applicants are archived.
241
242        If id contains a single id string, only the respective
243        applicants are archived.
244
245        If id contains a list of id strings all of the respective
246        applicant types are saved to disk.
247
248        If `archive` is ``False`` none of the archive-handling is done
249        and respective applicants are simply removed from the
250        database.
251        """
252
253class IApplicantsContainerAdd(IApplicantsContainer):
254    """An applicants container contains university applicants.
255    """
256    prefix = schema.Choice(
257        title = u'Application target',
258        required = True,
259        default = None,
260        source = application_types_vocab,
261        readonly = False,
262        )
263
264    year = schema.Choice(
265        title = u'Year of entrance',
266        required = True,
267        default = None,
268        values = year_range(),
269        readonly = False,
270        )
271
272    provider = schema.Choice(
273        title = u'Applicants container type',
274        required = True,
275        default = None,
276        source = ApplicantContainerProviderSource(),
277        readonly = False,
278        )
279
280IApplicantsContainerAdd[
281    'prefix'].order =  IApplicantsContainer['prefix'].order
282IApplicantsContainerAdd[
283    'year'].order =  IApplicantsContainer['year'].order
284IApplicantsContainerAdd[
285    'provider'].order =  IApplicantsContainer['provider'].order
286
287class IApplicantBaseData(IWAeUPObject):
288    """The data for an applicant.
289
290    This is a base interface with no field (except ``reg_no``)
291    required. For use with importers, forms, etc., please use one of
292    the derived interfaces below, which set more fields to required
293    state, depending on use-case.
294    """
295    history = Attribute('Object history, a list of messages.')
296    state = Attribute('Returns the application state of an applicant')
297    application_date = Attribute('Date of submission, used for export only')
298
299    #def getApplicantsRootLogger():
300    #    """Returns the logger from the applicants root object
301    #    """
302
303    def loggerInfo(ob_class, comment):
304        """Adds an INFO message to the log file
305        """
306
307    reg_no = schema.TextLine(
308        title = u'JAMB Registration Number',
309        readonly = True,
310        )
311    access_code = schema.TextLine(
312        title = u'Access Code',
313        required = False,
314        readonly = True,
315        )
316    course1 = schema.Choice(
317        title = u'1st Choice Course of Study',
318        source = AppCatCertificateSource(),
319        required = True,
320        )
321    course2 = schema.Choice(
322        title = u'2nd Choice Course of Study',
323        source = AppCatCertificateSource(),
324        required = False,
325        )
326    firstname = schema.TextLine(
327        title = u'First Name',
328        required = True,
329        )
330    middlenames = schema.TextLine(
331        title = u'Middle Names',
332        required = False,
333        )
334    lastname = schema.TextLine(
335        title = u'Last Name (Surname)',
336        required = True,
337        )
338    date_of_birth = schema.Date(
339        title = u'Date of Birth',
340        required = True,
341        )
342    lga = schema.Choice(
343        source = lgas_vocab,
344        title = u'State/LGA',
345        default = 'foreigner',
346        required = True,
347        )
348    sex = schema.Choice(
349        title = u'Sex',
350        source = GenderSource(),
351        default = u'm',
352        required = True,
353        )
354    email = schema.ASCIILine(
355        title = u'Email',
356        required = False,
357        constraint=validate_email,
358        )
359    phone = schema.Int(
360        title = u'Phone',
361        description = u'Enter phone number with country code and without spaces.',
362        required = False,
363        )
364    passport = ImageFile(
365        title = u'Passport Photograph',
366        #default = DEFAULT_PASSPORT_IMAGE_MALE,
367        defaultFactory = default_passport_image,
368        description = u'Maximun file size is 20 kB.',
369        required = True,
370        max_size = 20480,
371        )
372
373    #
374    # Process Data
375    #
376    screening_score = schema.Int(
377        title = u'Screening Score',
378        required = False,
379        )
380    screening_venue = schema.TextLine(
381        title = u'Screening Venue',
382        required = False,
383        )
384    course_admitted = schema.Choice(
385        title = u'Admitted Course of Study',
386        source = CertificateSource(),
387        default = None,
388        required = False,
389        )
390    notice = schema.Text(
391        title = u'Notice',
392        required = False,
393        )
394    student_id = schema.TextLine(
395        title = u'Student ID',
396        required = False,
397        readonly = True,
398        )
399    locked = schema.Bool(
400        title = u'Form locked',
401        default = False,
402        )
403
404class IApplicant(IApplicantBaseData):
405    """An applicant.
406
407    This is basically the applicant base data. Here we repeat the
408    fields from base data if we have to set the `required` attribute
409    to True (which is the default).
410    """
411
412class IApplicantEdit(IApplicantBaseData):
413    """An applicant.
414
415    Here we can repeat the fields from base data and set the
416    `required` and `readonly` attributes to True to further restrict
417    the data access. We cannot omit fields. This has to be done in the
418    respective form page.
419    """
420    screening_score = schema.Int(
421        title = u'Screening Score',
422        required = False,
423        readonly = True,
424        )
425    screening_venue = schema.TextLine(
426        title = u'Screening Venue',
427        required = False,
428        readonly = True,
429        )
430    course_admitted = schema.Choice(
431        title = u'Admitted Course of Study',
432        source = CertificateSource(),
433        default = None,
434        required = False,
435        readonly = True,
436        )
437    # entry_session is inherited from the container
438    #entry_session = schema.Choice(
439    #    source = entry_session_vocab,
440    #    title = u'Entry Session',
441    #    required = False,
442    #    readonly = True
443    #    )
444    notice = schema.Text(
445        title = u'Notice',
446        required = False,
447        readonly = True,
448        )
449
450class IApplicantPrincipalInfo(IPrincipalInfo):
451    """Infos about principals that are applicants.
452    """
453    access_code = Attribute("The Access Code the user purchased")
454
455class IApplicantPrincipal(IPrincipal):
456    """A principal that is an applicant.
457
458    This interface extends zope.security.interfaces.IPrincipal and
459    requires also an `id` and other attributes defined there.
460    """
461    access_code = schema.TextLine(
462        title = u'Access Code',
463        description = u'The access code purchased by the user.',
464        required = True,
465        readonly = True)
466
467class IApplicantsFormChallenger(Interface):
468    """A challenger that uses a browser form to collect applicant
469       credentials.
470    """
471    loginpagename = schema.TextLine(
472        title = u'Loginpagename',
473        description = u"""Name of the login form used by challenger.
474
475        The form must provide an ``access_code`` input field.
476        """)
477
478    accesscode_field = schema.TextLine(
479        title = u'Access code field',
480        description = u'''Field of the login page which is looked up for
481                          access_code''',
482        default = u'access_code',
483        )
484
485
486class IApplicantSessionCredentials(Interface):
487    """Interface for storing and accessing applicant credentials in a
488       session.
489    """
490
491    def __init__(access_code):
492        """Create applicant session credentials."""
493
494    def getAccessCode():
495        """Return the access code."""
496
497
498class IApplicantsContainerProvider(Interface):
499    """A provider for applicants containers.
500
501    Applicants container providers are meant to be looked up as
502    utilities. This way we can find all applicant container types
503    defined somewhere.
504
505    Each applicants container provider registered as utility provides
506    one container type and one should be able to call the `factory`
507    attribute to create an instance of the requested container type.
508
509    .. THE FOLLOWING SHOULD GO INTO SPHINX DOCS (and be tested)
510
511    Samples
512    *******
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.