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

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

Adjust copyright statement and svn keyword in applicants.

  • Property svn:keywords set to Id
File size: 15.6 KB
RevLine 
[5638]1## $Id: interfaces.py 7192 2011-11-25 07:15:50Z henrik $
[6076]2##
[6087]3## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
[5638]4## This program is free software; you can redistribute it and/or modify
5## it under the terms of the GNU General Public License as published by
6## the Free Software Foundation; either version 2 of the License, or
7## (at your option) any later version.
[6076]8##
[5638]9## This program is distributed in the hope that it will be useful,
10## but WITHOUT ANY WARRANTY; without even the implied warranty of
11## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12## GNU General Public License for more details.
[6076]13##
[5638]14## You should have received a copy of the GNU General Public License
15## along with this program; if not, write to the Free Software
16## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17##
[6500]18"""Interfaces of the university application package.
[5638]19"""
[5753]20import os
[6343]21import re
[6256]22
[5866]23from grokcore.content.interfaces import IContainer
[6256]24
[5638]25from zope import schema
[7063]26from zope.interface import Interface, Attribute
[6391]27from zope.component import getUtilitiesFor
[5758]28from zope.pluggableauth.interfaces import IPrincipalInfo
29from zope.security.interfaces import IGroupClosureAwarePrincipal as IPrincipal
[6256]30from zc.sourcefactory.basic import BasicSourceFactory
[6915]31from waeup.sirp.interfaces import IWAeUPObject, year_range
[6189]32from waeup.sirp.university.vocabularies import application_categories
[6648]33from waeup.sirp.students.vocabularies import (
[6915]34  lgas_vocab, CertificateSource, GenderSource,
[6648]35  )
[6256]36from waeup.sirp.applicants.vocabularies import (
[6648]37  application_types_vocab, application_pins_vocab,
38  AppCatCertificateSource,
[6256]39  )
[5638]40
[7075]41#: Maximum upload size for applicant passport photographs (in bytes)
[7086]42MAX_UPLOAD_SIZE = 1024 * 20
[7075]43
[6343]44# Define a valiation method for email addresses
45class NotAnEmailAddress(schema.ValidationError):
46    __doc__ = u"Invalid email address"
47
[6594]48check_email = re.compile(
49    r"[a-zA-Z0-9._%-]+@([a-zA-Z0-9-]+.)*[a-zA-Z]{2,4}").match
[6343]50def validate_email(value):
51    if not check_email(value):
52        raise NotAnEmailAddress(value)
53    return True
54
[6069]55class ApplicantContainerProviderSource(BasicSourceFactory):
[6075]56    """A source offering all available applicants container types.
57
58    The values returned by this source are names of utilities that can
59    create :class:`ApplicantContainer` instances. So, if you get a
60    name like ``'myactype'`` from this source, then you can do:
61
62      >>> from zope.component import getUtility
63      >>> p = getUtility(IApplicantsContainerProvider, name=myactype)
64      >>> my_applicants_container = p.factory()
65
66    Or you can access class-attributes like
67
68      >>> my_applicants_container.container_title
69      'Pretty'
[6076]70
[6069]71    """
72    def getValues(self):
[6075]73        """Returns a list of ``(<name>, <provider>)`` tuples.
[5753]74
[6075]75        Here ``<name>`` is the name under which an
76        :class:``ApplicantContainerProvider`` was registered as a
77        utility and ``<provider>`` is the utility itself.
78        """
79        return getUtilitiesFor(IApplicantsContainerProvider)
80
[6069]81    def getToken(self, value):
[6075]82        """Return the name of the ``(<name>, <provider>)`` tuple.
83        """
84        return value[0]
85
[6069]86    def getTitle(self, value):
[6075]87        """Get a 'title - description' string for a container type.
88        """
89        factory = value[1].factory
90        return "%s - %s" % (
91            factory.container_title, factory.container_description)
[6069]92
[5753]93class IResultEntry(IWAeUPObject):
94    subject = schema.TextLine(
95        title = u'Subject',
96        description = u'The subject',
97        required=False,
98        )
99    score = schema.TextLine(
100        title = u'Score',
101        description = u'The score',
102        required=False,
103        )
104
[5866]105class IApplicantsRoot(IWAeUPObject, IContainer):
[5676]106    """A container for university applicants containers.
[5645]107    """
[5866]108    pass
[5638]109
[5676]110class IApplicantsContainer(IWAeUPObject):
111    """An applicants container contains university applicants.
[5645]112
[5638]113    """
[6069]114
[6075]115    container_title = Attribute(
116        u'classattribute: title for type of container')
117    container_description = Attribute(
118        u'classattribute: description for type of container')
119
[6076]120
[6069]121    code = schema.TextLine(
122        title = u'Code',
[6087]123        default = u'-',
[6069]124        required = True,
125        readonly = True,
[6076]126        )
[6096]127
[6087]128    title = schema.TextLine(
129        title = u'Title',
130        required = True,
131        default = u'-',
132        readonly = True,
[6096]133        )
134
[6087]135    prefix = schema.Choice(
136        title = u'Application target',
137        required = True,
138        default = None,
139        source = application_types_vocab,
140        readonly = True,
141        )
[6076]142
[6087]143    year = schema.Choice(
144        title = u'Year of entrance',
145        required = True,
146        default = None,
[6158]147        values = year_range(),
[6087]148        readonly = True,
[6096]149        )
[6087]150
[6069]151    provider = schema.Choice(
[6070]152        title = u'Applicants container type',
[6069]153        required = True,
154        default = None,
155        source = ApplicantContainerProviderSource(),
[6087]156        readonly = True,
[6076]157        )
[6158]158
[6110]159    ac_prefix = schema.Choice(
160        title = u'Access code prefix',
161        required = True,
162        default = None,
[6111]163        source = application_pins_vocab,
[6110]164        )
[6076]165
[6189]166    application_category = schema.Choice(
[6477]167        title = u'Category for the grouping of certificates',
[6189]168        required = True,
169        default = None,
170        source = application_categories,
171        )
172
[5645]173    description = schema.Text(
174        title = u'Human readable description in reST format',
[5638]175        required = False,
[6518]176        default = u'''This text can been seen by anonymous users.
177Here we put information about the study courses provided, the application procedure and deadlines.'''
[5638]178        )
179
180    startdate = schema.Date(
[6509]181        title = u'Application start date',
[5638]182        required = False,
183        default = None,
184        )
185
186    enddate = schema.Date(
[6509]187        title = u'Application closing date',
[5638]188        required = False,
189        default = None,
190        )
191
[5645]192    strict_deadline = schema.Bool(
193        title = u'Forbid additions after deadline (enddate)',
194        required = True,
195        default = True,
196        )
[5638]197
198    def archive(id=None):
[5676]199        """Create on-dist archive of applicants stored in this term.
[5638]200
[5676]201        If id is `None`, all applicants are archived.
[5638]202
203        If id contains a single id string, only the respective
[5676]204        applicants are archived.
[5638]205
206        If id contains a list of id strings all of the respective
[5676]207        applicants types are saved to disk.
[5638]208        """
209
210    def clear(id=None, archive=True):
[5676]211        """Remove applicants of type given by 'id'.
[5638]212
[5676]213        Optionally archive the applicants.
[6076]214
[5676]215        If id is `None`, all applicants are archived.
[5638]216
217        If id contains a single id string, only the respective
[5676]218        applicants are archived.
[5638]219
220        If id contains a list of id strings all of the respective
[5676]221        applicant types are saved to disk.
[5638]222
223        If `archive` is ``False`` none of the archive-handling is done
[5676]224        and respective applicants are simply removed from the
[5638]225        database.
226        """
[6073]227
[6069]228class IApplicantsContainerAdd(IApplicantsContainer):
229    """An applicants container contains university applicants.
230    """
[6087]231    prefix = schema.Choice(
232        title = u'Application target',
[6069]233        required = True,
[6087]234        default = None,
235        source = application_types_vocab,
[6069]236        readonly = False,
[6076]237        )
[6073]238
[6087]239    year = schema.Choice(
240        title = u'Year of entrance',
241        required = True,
242        default = None,
[6158]243        values = year_range(),
[6087]244        readonly = False,
[6096]245        )
[6073]246
[6087]247    provider = schema.Choice(
248        title = u'Applicants container type',
249        required = True,
250        default = None,
251        source = ApplicantContainerProviderSource(),
252        readonly = False,
253        )
254
[6096]255IApplicantsContainerAdd[
256    'prefix'].order =  IApplicantsContainer['prefix'].order
257IApplicantsContainerAdd[
258    'year'].order =  IApplicantsContainer['year'].order
259IApplicantsContainerAdd[
260    'provider'].order =  IApplicantsContainer['provider'].order
[6087]261
[5753]262class IApplicantBaseData(IWAeUPObject):
263    """The data for an applicant.
264
265    This is a base interface with no field (except ``reg_no``)
266    required. For use with importers, forms, etc., please use one of
267    the derived interfaces below, which set more fields to required
268    state, depending on use-case.
269    """
[6339]270    history = Attribute('Object history, a list of messages.')
[6332]271    state = Attribute('Returns the application state of an applicant')
[6476]272    application_date = Attribute('Date of submission, used for export only')
[6304]273
[6476]274    #def getApplicantsRootLogger():
275    #    """Returns the logger from the applicants root object
276    #    """
277
278    def loggerInfo(ob_class, comment):
279        """Adds an INFO message to the log file
[6348]280        """
281
[5753]282    reg_no = schema.TextLine(
283        title = u'JAMB Registration Number',
[6195]284        readonly = True,
[5753]285        )
286    access_code = schema.TextLine(
287        title = u'Access Code',
288        required = False,
[6195]289        readonly = True,
[5753]290        )
[6248]291    course1 = schema.Choice(
[5753]292        title = u'1st Choice Course of Study',
[6248]293        source = AppCatCertificateSource(),
[6352]294        required = True,
[5753]295        )
[6248]296    course2 = schema.Choice(
[5753]297        title = u'2nd Choice Course of Study',
[6248]298        source = AppCatCertificateSource(),
[5753]299        required = False,
300        )
301    firstname = schema.TextLine(
302        title = u'First Name',
[6352]303        required = True,
[5753]304        )
305    middlenames = schema.TextLine(
306        title = u'Middle Names',
307        required = False,
308        )
309    lastname = schema.TextLine(
[6205]310        title = u'Last Name (Surname)',
[6352]311        required = True,
[5753]312        )
313    date_of_birth = schema.Date(
314        title = u'Date of Birth',
[6352]315        required = True,
[5753]316        )
[6249]317    lga = schema.Choice(
318        source = lgas_vocab,
[6205]319        title = u'State/LGA',
[6254]320        default = 'foreigner',
321        required = True,
[5753]322        )
323    sex = schema.Choice(
324        title = u'Sex',
325        source = GenderSource(),
326        default = u'm',
[6352]327        required = True,
[5753]328        )
[6341]329    email = schema.ASCIILine(
[5753]330        title = u'Email',
331        required = False,
[6343]332        constraint=validate_email,
[5753]333        )
[6341]334    phone = schema.Int(
[5753]335        title = u'Phone',
[6341]336        description = u'Enter phone number with country code and without spaces.',
[5753]337        required = False,
338        )
[7063]339    #passport = ImageFile(
340    #    title = u'Passport Photograph',
341    #    #default = DEFAULT_PASSPORT_IMAGE_MALE,
342    #    defaultFactory = default_passport_image,
343    #    description = u'Maximun file size is 20 kB.',
344    #    required = True,
345    #    max_size = 20480,
346    #    )
[6322]347
[5753]348    #
[6195]349    # Process Data
[5753]350    #
[6255]351    screening_score = schema.Int(
[5753]352        title = u'Screening Score',
353        required = False,
354        )
355    screening_venue = schema.TextLine(
356        title = u'Screening Venue',
357        required = False,
358        )
[6248]359    course_admitted = schema.Choice(
[5753]360        title = u'Admitted Course of Study',
[6248]361        source = CertificateSource(),
[6254]362        default = None,
[5753]363        required = False,
364        )
365    notice = schema.Text(
366        title = u'Notice',
367        required = False,
368        )
369    student_id = schema.TextLine(
370        title = u'Student ID',
371        required = False,
[6195]372        readonly = True,
[5753]373        )
[6302]374    locked = schema.Bool(
375        title = u'Form locked',
376        default = False,
377        )
[5753]378
379class IApplicant(IApplicantBaseData):
380    """An applicant.
381
382    This is basically the applicant base data. Here we repeat the
[6195]383    fields from base data if we have to set the `required` attribute
384    to True (which is the default).
[5753]385    """
386
[6195]387class IApplicantEdit(IApplicantBaseData):
388    """An applicant.
[5753]389
[6339]390    Here we can repeat the fields from base data and set the
391    `required` and `readonly` attributes to True to further restrict
392    the data access. We cannot omit fields. This has to be done in the
393    respective form page.
[6195]394    """
[6255]395    screening_score = schema.Int(
[5753]396        title = u'Screening Score',
[6195]397        required = False,
[5941]398        readonly = True,
399        )
[6195]400    screening_venue = schema.TextLine(
401        title = u'Screening Venue',
[5753]402        required = False,
403        readonly = True,
404        )
[6301]405    course_admitted = schema.Choice(
[6195]406        title = u'Admitted Course of Study',
[6301]407        source = CertificateSource(),
408        default = None,
[5753]409        required = False,
[6195]410        readonly = True,
[5753]411        )
[6195]412    notice = schema.Text(
413        title = u'Notice',
[5753]414        required = False,
415        readonly = True,
416        )
[5758]417
418class IApplicantPrincipalInfo(IPrincipalInfo):
419    """Infos about principals that are applicants.
420    """
421    access_code = Attribute("The Access Code the user purchased")
422
423class IApplicantPrincipal(IPrincipal):
424    """A principal that is an applicant.
425
426    This interface extends zope.security.interfaces.IPrincipal and
427    requires also an `id` and other attributes defined there.
428    """
429    access_code = schema.TextLine(
430        title = u'Access Code',
431        description = u'The access code purchased by the user.',
432        required = True,
433        readonly = True)
434
435class IApplicantsFormChallenger(Interface):
436    """A challenger that uses a browser form to collect applicant
437       credentials.
438    """
439    loginpagename = schema.TextLine(
440        title = u'Loginpagename',
441        description = u"""Name of the login form used by challenger.
442
443        The form must provide an ``access_code`` input field.
444        """)
445
446    accesscode_field = schema.TextLine(
447        title = u'Access code field',
448        description = u'''Field of the login page which is looked up for
449                          access_code''',
450        default = u'access_code',
451        )
452
453
454class IApplicantSessionCredentials(Interface):
455    """Interface for storing and accessing applicant credentials in a
456       session.
457    """
458
459    def __init__(access_code):
460        """Create applicant session credentials."""
461
462    def getAccessCode():
463        """Return the access code."""
464
465
[5846]466class IApplicantsContainerProvider(Interface):
[5820]467    """A provider for applicants containers.
468
469    Applicants container providers are meant to be looked up as
470    utilities. This way we can find all applicant container types
471    defined somewhere.
472
473    Each applicants container provider registered as utility provides
474    one container type and one should be able to call the `factory`
475    attribute to create an instance of the requested container type.
476
477    .. THE FOLLOWING SHOULD GO INTO SPHINX DOCS (and be tested)
[6076]478
[6500]479    Samples:
[6076]480
[5820]481    Given, you had an IApplicantsContainer implementation somewhere
482    and you would like to make it findable on request, then you would
483    normally create an appropriate provider utility like this::
484
485      import grok
[5846]486      from waeup.sirp.applicants.interfaces import IApplicantsContainerProvider
[5820]487
488      class MyContainerProvider(grok.GlobalUtility):
[5846]489          grok.implements(IApplicantsContainerProvider)
[5820]490          grok.name('MyContainerProvider') # Must be unique
491          factory = MyContainer # A class implementing IApplicantsContainer
492                                # or derivations thereof.
493
494    This utility would be registered on startup and could then be used
495    like this:
496
497      >>> from zope.component import getAllUtilitiesRegisteredFor
498      >>> from waeup.sirp.applicants.interfaces import (
[5846]499      ...     IApplicantsContainerProvider)
[5820]500      >>> all_providers = getAllUtilitiesRegisteredFor(
[5846]501      ...     IApplicantsContainerProvider)
[5820]502      >>> all_providers
503      [<MyContainerProvider object at 0x...>]
504
505    You could look up this specific provider by name:
506
507      >>> from zope.component import getUtility
[5846]508      >>> p = getUtility(IApplicantsContainerProvider, name='MyProvider')
[5820]509      >>> p
510      <MyContainerProvider object at 0x...>
[6076]511
[5820]512    An applicants container would then be created like this:
513
514      >>> provider = all_providers[0]
515      >>> container = provider.factory()
516      >>> container
517      <MyContainer object at 0x...>
518
519    """
520    factory = Attribute("A class that can create instances of the "
521                        "requested container type")
Note: See TracBrowser for help on using the repository browser.