source: main/waeup.sirp/branches/accesscodes-with-workflow/src/waeup/sirp/applicants/interfaces.py @ 6401

Last change on this file since 6401 was 6391, checked in by uli, 13 years ago

* empty log message *

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 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
298    def getApplicantsRootLogger():
299        """Returns the logger from the applicants root object
300        """
301
302    reg_no = schema.TextLine(
303        title = u'JAMB Registration Number',
304        readonly = True,
305        )
306    access_code = schema.TextLine(
307        title = u'Access Code',
308        required = False,
309        readonly = True,
310        )
311    course1 = schema.Choice(
312        title = u'1st Choice Course of Study',
313        source = AppCatCertificateSource(),
314        required = True,
315        )
316    course2 = schema.Choice(
317        title = u'2nd Choice Course of Study',
318        source = AppCatCertificateSource(),
319        required = False,
320        )
321    firstname = schema.TextLine(
322        title = u'First Name',
323        required = True,
324        )
325    middlenames = schema.TextLine(
326        title = u'Middle Names',
327        required = False,
328        )
329    lastname = schema.TextLine(
330        title = u'Last Name (Surname)',
331        required = True,
332        )
333    date_of_birth = schema.Date(
334        title = u'Date of Birth',
335        required = True,
336        )
337    lga = schema.Choice(
338        source = lgas_vocab,
339        title = u'State/LGA',
340        default = 'foreigner',
341        required = True,
342        )
343    sex = schema.Choice(
344        title = u'Sex',
345        source = GenderSource(),
346        default = u'm',
347        required = True,
348        )
349    email = schema.ASCIILine(
350        title = u'Email',
351        required = False,
352        constraint=validate_email,
353        )
354    phone = schema.Int(
355        title = u'Phone',
356        description = u'Enter phone number with country code and without spaces.',
357        required = False,
358        )
359    passport = ImageFile(
360        title = u'Passport Photograph',
361        #default = DEFAULT_PASSPORT_IMAGE_MALE,
362        defaultFactory = default_passport_image,
363        description = u'Maximun file size is 20 kB.',
364        required = True,
365        max_size = 20480,
366        )
367
368    #
369    # Process Data
370    #
371    application_date = schema.Date(
372        title = u'Application Date',
373        required = False,
374        readonly = True,
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    # entry_session is inherited from the container
391    #entry_session = schema.Choice(
392    #    source = entry_session_vocab,
393    #    title = u'Entry Session',
394    #    required = False,
395    #    )
396    notice = schema.Text(
397        title = u'Notice',
398        required = False,
399        )
400    student_id = schema.TextLine(
401        title = u'Student ID',
402        required = False,
403        readonly = True,
404        )
405    locked = schema.Bool(
406        title = u'Form locked',
407        default = False,
408        )
409
410class IApplicant(IApplicantBaseData):
411    """An applicant.
412
413    This is basically the applicant base data. Here we repeat the
414    fields from base data if we have to set the `required` attribute
415    to True (which is the default).
416    """
417
418class IApplicantEdit(IApplicantBaseData):
419    """An applicant.
420
421    Here we can repeat the fields from base data and set the
422    `required` and `readonly` attributes to True to further restrict
423    the data access. We cannot omit fields. This has to be done in the
424    respective form page.
425    """
426    screening_score = schema.Int(
427        title = u'Screening Score',
428        required = False,
429        readonly = True,
430        )
431    screening_venue = schema.TextLine(
432        title = u'Screening Venue',
433        required = False,
434        readonly = True,
435        )
436    course_admitted = schema.Choice(
437        title = u'Admitted Course of Study',
438        source = CertificateSource(),
439        default = None,
440        required = False,
441        readonly = True,
442        )
443    # entry_session is inherited from the container
444    #entry_session = schema.Choice(
445    #    source = entry_session_vocab,
446    #    title = u'Entry Session',
447    #    required = False,
448    #    readonly = True
449    #    )
450    notice = schema.Text(
451        title = u'Notice',
452        required = False,
453        readonly = True,
454        )
455
456class IApplicantPrincipalInfo(IPrincipalInfo):
457    """Infos about principals that are applicants.
458    """
459    access_code = Attribute("The Access Code the user purchased")
460
461class IApplicantPrincipal(IPrincipal):
462    """A principal that is an applicant.
463
464    This interface extends zope.security.interfaces.IPrincipal and
465    requires also an `id` and other attributes defined there.
466    """
467    access_code = schema.TextLine(
468        title = u'Access Code',
469        description = u'The access code purchased by the user.',
470        required = True,
471        readonly = True)
472
473class IApplicantsFormChallenger(Interface):
474    """A challenger that uses a browser form to collect applicant
475       credentials.
476    """
477    loginpagename = schema.TextLine(
478        title = u'Loginpagename',
479        description = u"""Name of the login form used by challenger.
480
481        The form must provide an ``access_code`` input field.
482        """)
483
484    accesscode_field = schema.TextLine(
485        title = u'Access code field',
486        description = u'''Field of the login page which is looked up for
487                          access_code''',
488        default = u'access_code',
489        )
490
491
492class IApplicantSessionCredentials(Interface):
493    """Interface for storing and accessing applicant credentials in a
494       session.
495    """
496
497    def __init__(access_code):
498        """Create applicant session credentials."""
499
500    def getAccessCode():
501        """Return the access code."""
502
503
504class IApplicantsContainerProvider(Interface):
505    """A provider for applicants containers.
506
507    Applicants container providers are meant to be looked up as
508    utilities. This way we can find all applicant container types
509    defined somewhere.
510
511    Each applicants container provider registered as utility provides
512    one container type and one should be able to call the `factory`
513    attribute to create an instance of the requested container type.
514
515    .. THE FOLLOWING SHOULD GO INTO SPHINX DOCS (and be tested)
516
517    Samples
518    *******
519
520    Given, you had an IApplicantsContainer implementation somewhere
521    and you would like to make it findable on request, then you would
522    normally create an appropriate provider utility like this::
523
524      import grok
525      from waeup.sirp.applicants.interfaces import IApplicantsContainerProvider
526
527      class MyContainerProvider(grok.GlobalUtility):
528          grok.implements(IApplicantsContainerProvider)
529          grok.name('MyContainerProvider') # Must be unique
530          factory = MyContainer # A class implementing IApplicantsContainer
531                                # or derivations thereof.
532
533    This utility would be registered on startup and could then be used
534    like this:
535
536      >>> from zope.component import getAllUtilitiesRegisteredFor
537      >>> from waeup.sirp.applicants.interfaces import (
538      ...     IApplicantsContainerProvider)
539      >>> all_providers = getAllUtilitiesRegisteredFor(
540      ...     IApplicantsContainerProvider)
541      >>> all_providers
542      [<MyContainerProvider object at 0x...>]
543
544    You could look up this specific provider by name:
545
546      >>> from zope.component import getUtility
547      >>> p = getUtility(IApplicantsContainerProvider, name='MyProvider')
548      >>> p
549      <MyContainerProvider object at 0x...>
550
551    An applicants container would then be created like this:
552
553      >>> provider = all_providers[0]
554      >>> container = provider.factory()
555      >>> container
556      <MyContainer object at 0x...>
557
558    """
559    factory = Attribute("A class that can create instances of the "
560                        "requested container type")
Note: See TracBrowser for help on using the repository browser.