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

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

Explain the description field.

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 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    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 certificates',
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'''This text can been seen by anonymous users.
203Here we put information about the study courses provided, the application procedure and deadlines.'''
204        )
205
206    startdate = schema.Date(
207        title = u'Application start date',
208        required = False,
209        default = None,
210        )
211
212    enddate = schema.Date(
213        title = u'Application closing date',
214        required = False,
215        default = None,
216        )
217
218    strict_deadline = schema.Bool(
219        title = u'Forbid additions after deadline (enddate)',
220        required = True,
221        default = True,
222        )
223
224    def archive(id=None):
225        """Create on-dist archive of applicants stored in this term.
226
227        If id is `None`, all applicants are archived.
228
229        If id contains a single id string, only the respective
230        applicants are archived.
231
232        If id contains a list of id strings all of the respective
233        applicants types are saved to disk.
234        """
235
236    def clear(id=None, archive=True):
237        """Remove applicants of type given by 'id'.
238
239        Optionally archive the applicants.
240
241        If id is `None`, all applicants are archived.
242
243        If id contains a single id string, only the respective
244        applicants are archived.
245
246        If id contains a list of id strings all of the respective
247        applicant types are saved to disk.
248
249        If `archive` is ``False`` none of the archive-handling is done
250        and respective applicants are simply removed from the
251        database.
252        """
253
254class IApplicantsContainerAdd(IApplicantsContainer):
255    """An applicants container contains university applicants.
256    """
257    prefix = schema.Choice(
258        title = u'Application target',
259        required = True,
260        default = None,
261        source = application_types_vocab,
262        readonly = False,
263        )
264
265    year = schema.Choice(
266        title = u'Year of entrance',
267        required = True,
268        default = None,
269        values = year_range(),
270        readonly = False,
271        )
272
273    provider = schema.Choice(
274        title = u'Applicants container type',
275        required = True,
276        default = None,
277        source = ApplicantContainerProviderSource(),
278        readonly = False,
279        )
280
281IApplicantsContainerAdd[
282    'prefix'].order =  IApplicantsContainer['prefix'].order
283IApplicantsContainerAdd[
284    'year'].order =  IApplicantsContainer['year'].order
285IApplicantsContainerAdd[
286    'provider'].order =  IApplicantsContainer['provider'].order
287
288class IApplicantBaseData(IWAeUPObject):
289    """The data for an applicant.
290
291    This is a base interface with no field (except ``reg_no``)
292    required. For use with importers, forms, etc., please use one of
293    the derived interfaces below, which set more fields to required
294    state, depending on use-case.
295    """
296    history = Attribute('Object history, a list of messages.')
297    state = Attribute('Returns the application state of an applicant')
298    application_date = Attribute('Date of submission, used for export only')
299
300    #def getApplicantsRootLogger():
301    #    """Returns the logger from the applicants root object
302    #    """
303
304    def loggerInfo(ob_class, comment):
305        """Adds an INFO message to the log file
306        """
307
308    reg_no = schema.TextLine(
309        title = u'JAMB Registration Number',
310        readonly = True,
311        )
312    access_code = schema.TextLine(
313        title = u'Access Code',
314        required = False,
315        readonly = True,
316        )
317    course1 = schema.Choice(
318        title = u'1st Choice Course of Study',
319        source = AppCatCertificateSource(),
320        required = True,
321        )
322    course2 = schema.Choice(
323        title = u'2nd Choice Course of Study',
324        source = AppCatCertificateSource(),
325        required = False,
326        )
327    firstname = schema.TextLine(
328        title = u'First Name',
329        required = True,
330        )
331    middlenames = schema.TextLine(
332        title = u'Middle Names',
333        required = False,
334        )
335    lastname = schema.TextLine(
336        title = u'Last Name (Surname)',
337        required = True,
338        )
339    date_of_birth = schema.Date(
340        title = u'Date of Birth',
341        required = True,
342        )
343    lga = schema.Choice(
344        source = lgas_vocab,
345        title = u'State/LGA',
346        default = 'foreigner',
347        required = True,
348        )
349    sex = schema.Choice(
350        title = u'Sex',
351        source = GenderSource(),
352        default = u'm',
353        required = True,
354        )
355    email = schema.ASCIILine(
356        title = u'Email',
357        required = False,
358        constraint=validate_email,
359        )
360    phone = schema.Int(
361        title = u'Phone',
362        description = u'Enter phone number with country code and without spaces.',
363        required = False,
364        )
365    passport = ImageFile(
366        title = u'Passport Photograph',
367        #default = DEFAULT_PASSPORT_IMAGE_MALE,
368        defaultFactory = default_passport_image,
369        description = u'Maximun file size is 20 kB.',
370        required = True,
371        max_size = 20480,
372        )
373
374    #
375    # Process Data
376    #
377    screening_score = schema.Int(
378        title = u'Screening Score',
379        required = False,
380        )
381    screening_venue = schema.TextLine(
382        title = u'Screening Venue',
383        required = False,
384        )
385    course_admitted = schema.Choice(
386        title = u'Admitted Course of Study',
387        source = CertificateSource(),
388        default = None,
389        required = False,
390        )
391    notice = schema.Text(
392        title = u'Notice',
393        required = False,
394        )
395    student_id = schema.TextLine(
396        title = u'Student ID',
397        required = False,
398        readonly = True,
399        )
400    locked = schema.Bool(
401        title = u'Form locked',
402        default = False,
403        )
404
405class IApplicant(IApplicantBaseData):
406    """An applicant.
407
408    This is basically the applicant base data. Here we repeat the
409    fields from base data if we have to set the `required` attribute
410    to True (which is the default).
411    """
412
413class IApplicantEdit(IApplicantBaseData):
414    """An applicant.
415
416    Here we can repeat the fields from base data and set the
417    `required` and `readonly` attributes to True to further restrict
418    the data access. We cannot omit fields. This has to be done in the
419    respective form page.
420    """
421    screening_score = schema.Int(
422        title = u'Screening Score',
423        required = False,
424        readonly = True,
425        )
426    screening_venue = schema.TextLine(
427        title = u'Screening Venue',
428        required = False,
429        readonly = True,
430        )
431    course_admitted = schema.Choice(
432        title = u'Admitted Course of Study',
433        source = CertificateSource(),
434        default = None,
435        required = False,
436        readonly = True,
437        )
438    # entry_session is inherited from the container
439    #entry_session = schema.Choice(
440    #    source = entry_session_vocab,
441    #    title = u'Entry Session',
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.