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

Last change on this file since 6374 was 6352, checked in by Henrik Bettermann, 14 years ago

Set required attribute correctly.

File size: 16.9 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    def getApplicantsRootLogger():
300        """Returns the logger from the applicants root object
301        """
302
303    reg_no = schema.TextLine(
304        title = u'JAMB Registration Number',
305        readonly = True,
306        )
307    access_code = schema.TextLine(
308        title = u'Access Code',
309        required = False,
310        readonly = True,
311        )
312    course1 = schema.Choice(
313        title = u'1st Choice Course of Study',
314        source = AppCatCertificateSource(),
315        required = True,
316        )
317    course2 = schema.Choice(
318        title = u'2nd Choice Course of Study',
319        source = AppCatCertificateSource(),
320        required = False,
321        )
322    firstname = schema.TextLine(
323        title = u'First Name',
324        required = True,
325        )
326    middlenames = schema.TextLine(
327        title = u'Middle Names',
328        required = False,
329        )
330    lastname = schema.TextLine(
331        title = u'Last Name (Surname)',
332        required = True,
333        )
334    date_of_birth = schema.Date(
335        title = u'Date of Birth',
336        required = True,
337        )
338    lga = schema.Choice(
339        source = lgas_vocab,
340        title = u'State/LGA',
341        default = 'foreigner',
342        required = True,
343        )
344    sex = schema.Choice(
345        title = u'Sex',
346        source = GenderSource(),
347        default = u'm',
348        required = True,
349        )
350    email = schema.ASCIILine(
351        title = u'Email',
352        required = False,
353        constraint=validate_email,
354        )
355    phone = schema.Int(
356        title = u'Phone',
357        description = u'Enter phone number with country code and without spaces.',
358        required = False,
359        )
360    passport = ImageFile(
361        title = u'Passport Photograph',
362        #default = DEFAULT_PASSPORT_IMAGE_MALE,
363        defaultFactory = default_passport_image,
364        description = u'Maximun file size is 20 kB.',
365        required = True,
366        max_size = 20480,
367        )
368
369    #
370    # Process Data
371    #
372    application_date = schema.Date(
373        title = u'Application Date',
374        required = False,
375        readonly = True,
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    # entry_session is inherited from the container
392    #entry_session = schema.Choice(
393    #    source = entry_session_vocab,
394    #    title = u'Entry Session',
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
521    Given, you had an IApplicantsContainer implementation somewhere
522    and you would like to make it findable on request, then you would
523    normally create an appropriate provider utility like this::
524
525      import grok
526      from waeup.sirp.applicants.interfaces import IApplicantsContainerProvider
527
528      class MyContainerProvider(grok.GlobalUtility):
529          grok.implements(IApplicantsContainerProvider)
530          grok.name('MyContainerProvider') # Must be unique
531          factory = MyContainer # A class implementing IApplicantsContainer
532                                # or derivations thereof.
533
534    This utility would be registered on startup and could then be used
535    like this:
536
537      >>> from zope.component import getAllUtilitiesRegisteredFor
538      >>> from waeup.sirp.applicants.interfaces import (
539      ...     IApplicantsContainerProvider)
540      >>> all_providers = getAllUtilitiesRegisteredFor(
541      ...     IApplicantsContainerProvider)
542      >>> all_providers
543      [<MyContainerProvider object at 0x...>]
544
545    You could look up this specific provider by name:
546
547      >>> from zope.component import getUtility
548      >>> p = getUtility(IApplicantsContainerProvider, name='MyProvider')
549      >>> p
550      <MyContainerProvider object at 0x...>
551
552    An applicants container would then be created like this:
553
554      >>> provider = all_providers[0]
555      >>> container = provider.factory()
556      >>> container
557      <MyContainer object at 0x...>
558
559    """
560    factory = Attribute("A class that can create instances of the "
561                        "requested container type")
Note: See TracBrowser for help on using the repository browser.