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

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

Define validation method for email addresses and use as constraint (copied from Plone Entwicklerhandbuch).

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