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

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

Adjust field titles.

File size: 17.2 KB
RevLine 
[5638]1##
2## interfaces.py
3## Login : <uli@pu.smp.net>
4## Started on  Sun Jan 16 15:30:01 2011 Uli Fouquet
5## $Id$
[6076]6##
[6087]7## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
[5638]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.
[6076]12##
[5638]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.
[6076]17##
[5638]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##
[5676]22"""Interfaces regarding student applicants and related components.
[5638]23"""
[5753]24import os
25import waeup.sirp.browser
[6098]26from datetime import datetime
[5866]27from grokcore.content.interfaces import IContainer
[5758]28from zc.sourcefactory.basic import BasicSourceFactory
[5638]29from zope import schema
[6075]30from zope.component import getUtility, getUtilitiesFor
[5758]31from zope.interface import Interface, Attribute
32from zope.pluggableauth.interfaces import IPrincipalInfo
33from zope.security.interfaces import IGroupClosureAwarePrincipal as IPrincipal
[5753]34from waeup.sirp.image.schema import ImageFile
[5980]35from waeup.sirp.image.image import WAeUPImageFile
[5681]36from waeup.sirp.interfaces import IWAeUPObject, SimpleWAeUPVocabulary
[6189]37from waeup.sirp.university.vocabularies import application_categories
[5638]38
[6075]39
[5753]40IMAGE_PATH = os.path.join(
41    os.path.dirname(waeup.sirp.browser.__file__),
42    'static'
43    )
[5980]44DEFAULT_PASSPORT_IMAGE_MALE = WAeUPImageFile(
[5753]45    'passport.jpg',
46    open(os.path.join(IMAGE_PATH, 'placeholder_m.jpg')).read(),
47    )
[5980]48DEFAULT_PASSPORT_IMAGE_FEMALE = WAeUPImageFile(
[5753]49    'passport.jpg',
50    open(os.path.join(IMAGE_PATH, 'placeholder_f.jpg')).read(),
51    )
52
[6087]53#: Types of applications we support.
54APPLICATION_TYPES = (
[6111]55    ('General Studies', 'app','APP'),
56    ('Pre-NCE Programme', 'prence','PRE'),
57    ('Post UME Screening Test', 'pume','PUME'),
58    ('Post UDE Screening', 'pude','PUDE'),
59    ('Part Time Degree in Education', 'sandwich','SAND'),
60    ('Part-Time Degree Programmes', 'pt','PTP'),
61    ('Diploma Programmes', 'dp','DPP'),
62    ('PCE Screening', 'pce','PCE'),
63    ('Certificate Programmes', 'ct','CTP'),
64    ('Common Entry Screening Test', 'cest','CEST'),
[5681]65    )
66
67#: A :class:`waeup.sirp.interfaces.SimpleWAeUPVocabulary` of supported
[6087]68#: application or screening types.
[6158]69application_types_vocab = SimpleWAeUPVocabulary(
70    *[(x[0],x[1]) for x in APPLICATION_TYPES])
71application_pins_vocab = SimpleWAeUPVocabulary(
72    *[(u"%s (%s)" % (x[2],x[0]),x[2]) for x in APPLICATION_TYPES])
[5681]73
[6158]74def year_range():
[6109]75    curr_year = datetime.now().year
76    return range(curr_year - 2, curr_year + 5)
[6097]77
[5753]78class GenderSource(BasicSourceFactory):
79    """A gender source delivers basically a mapping
80       ``{'m': 'male', 'f': 'female'}``
81
82       Using a source, we make sure that the tokens (which are
83       stored/expected for instance from CSV files) are something one
84       can expect and not cryptic IntIDs.
85    """
86    def getValues(self):
87        return ['m', 'f']
88
89    def getToken(self, value):
90        return value[0].lower()
[6076]91
[5753]92    def getTitle(self, value):
93        if value == 'm':
94            return 'male'
95        if value == 'f':
96            return 'female'
[6076]97
[6069]98class ApplicantContainerProviderSource(BasicSourceFactory):
[6075]99    """A source offering all available applicants container types.
100
101    The values returned by this source are names of utilities that can
102    create :class:`ApplicantContainer` instances. So, if you get a
103    name like ``'myactype'`` from this source, then you can do:
104
105      >>> from zope.component import getUtility
106      >>> p = getUtility(IApplicantsContainerProvider, name=myactype)
107      >>> my_applicants_container = p.factory()
108
109    Or you can access class-attributes like
110
111      >>> my_applicants_container.container_title
112      'Pretty'
[6076]113
[6069]114    """
115    def getValues(self):
[6075]116        """Returns a list of ``(<name>, <provider>)`` tuples.
[5753]117
[6075]118        Here ``<name>`` is the name under which an
119        :class:``ApplicantContainerProvider`` was registered as a
120        utility and ``<provider>`` is the utility itself.
121        """
122        return getUtilitiesFor(IApplicantsContainerProvider)
123
[6069]124    def getToken(self, value):
[6075]125        """Return the name of the ``(<name>, <provider>)`` tuple.
126        """
127        return value[0]
128
[6069]129    def getTitle(self, value):
[6075]130        """Get a 'title - description' string for a container type.
131        """
132        factory = value[1].factory
133        return "%s - %s" % (
134            factory.container_title, factory.container_description)
[6069]135
[5753]136class IResultEntry(IWAeUPObject):
137    subject = schema.TextLine(
138        title = u'Subject',
139        description = u'The subject',
140        required=False,
141        )
142    score = schema.TextLine(
143        title = u'Score',
144        description = u'The score',
145        required=False,
146        )
147
[5866]148class IApplicantsRoot(IWAeUPObject, IContainer):
[5676]149    """A container for university applicants containers.
[5645]150    """
[5866]151    pass
[5638]152
[6076]153
[5676]154class IApplicantsContainer(IWAeUPObject):
155    """An applicants container contains university applicants.
[5645]156
[5638]157    """
[6069]158
[6075]159    container_title = Attribute(
160        u'classattribute: title for type of container')
161    container_description = Attribute(
162        u'classattribute: description for type of container')
163
[6076]164
[6069]165    code = schema.TextLine(
166        title = u'Code',
[6087]167        default = u'-',
[6069]168        required = True,
169        readonly = True,
[6076]170        )
[6096]171
[6087]172    title = schema.TextLine(
173        title = u'Title',
174        required = True,
175        default = u'-',
176        readonly = True,
[6096]177        )
178
[6087]179    prefix = schema.Choice(
180        title = u'Application target',
181        required = True,
182        default = None,
183        source = application_types_vocab,
184        readonly = True,
185        )
[6076]186
[6087]187    year = schema.Choice(
188        title = u'Year of entrance',
189        required = True,
190        default = None,
[6158]191        values = year_range(),
[6087]192        readonly = True,
[6096]193        )
[6087]194
[6069]195    provider = schema.Choice(
[6070]196        title = u'Applicants container type',
[6069]197        required = True,
198        default = None,
199        source = ApplicantContainerProviderSource(),
[6087]200        readonly = True,
[6076]201        )
[6158]202
[6110]203    ac_prefix = schema.Choice(
204        title = u'Access code prefix',
205        required = True,
206        default = None,
[6111]207        source = application_pins_vocab,
[6110]208        )
[6076]209
[6189]210    application_category = schema.Choice(
211        title = u'Category for the grouping of study courses',
212        required = True,
213        default = None,
214        source = application_categories,
215        )
216
[5645]217    description = schema.Text(
218        title = u'Human readable description in reST format',
[5638]219        required = False,
[6070]220        default = u'No description yet.'
[5638]221        )
222
223    startdate = schema.Date(
[5645]224        title = u'Date when the application period starts',
[5638]225        required = False,
226        default = None,
227        )
228
229    enddate = schema.Date(
[5645]230        title = u'Date when the application period ends',
[5638]231        required = False,
232        default = None,
233        )
234
[5645]235    strict_deadline = schema.Bool(
236        title = u'Forbid additions after deadline (enddate)',
237        required = True,
238        default = True,
239        )
[5638]240
241    def archive(id=None):
[5676]242        """Create on-dist archive of applicants stored in this term.
[5638]243
[5676]244        If id is `None`, all applicants are archived.
[5638]245
246        If id contains a single id string, only the respective
[5676]247        applicants are archived.
[5638]248
249        If id contains a list of id strings all of the respective
[5676]250        applicants types are saved to disk.
[5638]251        """
252
253    def clear(id=None, archive=True):
[5676]254        """Remove applicants of type given by 'id'.
[5638]255
[5676]256        Optionally archive the applicants.
[6076]257
[5676]258        If id is `None`, all applicants are archived.
[5638]259
260        If id contains a single id string, only the respective
[5676]261        applicants are archived.
[5638]262
263        If id contains a list of id strings all of the respective
[5676]264        applicant types are saved to disk.
[5638]265
266        If `archive` is ``False`` none of the archive-handling is done
[5676]267        and respective applicants are simply removed from the
[5638]268        database.
269        """
[6073]270
[6069]271class IApplicantsContainerAdd(IApplicantsContainer):
272    """An applicants container contains university applicants.
273    """
[6087]274    prefix = schema.Choice(
275        title = u'Application target',
[6069]276        required = True,
[6087]277        default = None,
278        source = application_types_vocab,
[6069]279        readonly = False,
[6076]280        )
[6073]281
[6087]282    year = schema.Choice(
283        title = u'Year of entrance',
284        required = True,
285        default = None,
[6158]286        values = year_range(),
[6087]287        readonly = False,
[6096]288        )
[6073]289
[6087]290    provider = schema.Choice(
291        title = u'Applicants container type',
292        required = True,
293        default = None,
294        source = ApplicantContainerProviderSource(),
295        readonly = False,
296        )
297
[6096]298IApplicantsContainerAdd[
299    'prefix'].order =  IApplicantsContainer['prefix'].order
300IApplicantsContainerAdd[
301    'year'].order =  IApplicantsContainer['year'].order
302IApplicantsContainerAdd[
303    'provider'].order =  IApplicantsContainer['provider'].order
[6087]304
[5753]305class IApplicantBaseData(IWAeUPObject):
306    """The data for an applicant.
307
308    This is a base interface with no field (except ``reg_no``)
309    required. For use with importers, forms, etc., please use one of
310    the derived interfaces below, which set more fields to required
311    state, depending on use-case.
312    """
[6195]313    locked = schema.Bool(
314        title = u'Form locked',
315        default = False,
316        #readonly = True,
317        )
[5753]318    reg_no = schema.TextLine(
319        title = u'JAMB Registration Number',
[6195]320        readonly = True,
[5753]321        )
322    access_code = schema.TextLine(
323        title = u'Access Code',
324        required = False,
[6195]325        readonly = True,
[5753]326        )
327    course1 = schema.TextLine(
[6195]328        # XXX: should be choice
[5753]329        title = u'1st Choice Course of Study',
330        required = False,
331        )
332    course2 = schema.TextLine(
[6195]333        # XXX: should be choice
[5753]334        title = u'2nd Choice Course of Study',
335        required = False,
336        )
337    firstname = schema.TextLine(
338        title = u'First Name',
339        required = False,
340        )
341    middlenames = schema.TextLine(
342        title = u'Middle Names',
343        required = False,
344        )
345    lastname = schema.TextLine(
[6205]346        title = u'Last Name (Surname)',
[5753]347        required = False,
348        )
349    date_of_birth = schema.Date(
350        title = u'Date of Birth',
351        required = False,
352        )
353    lga = schema.TextLine(
354        # XXX: should be choice
[6205]355        title = u'State/LGA',
[5753]356        required = False,
357        )
358    sex = schema.Choice(
359        title = u'Sex',
360        source = GenderSource(),
361        default = u'm',
362        required = False,
363        )
364    email = schema.TextLine(
365        title = u'Email',
366        required = False,
367        )
368    phone = schema.TextLine(
369        title = u'Phone',
370        required = False,
371        )
372    passport = ImageFile(
373        title = u'Passport Photograph',
374        default = DEFAULT_PASSPORT_IMAGE_MALE,
375        required = True,
[6184]376        #max_size = 20480,
[5753]377        )
[6195]378    confirm_passport = schema.Bool(
[6205]379        title = u"Passport picture confirmed",
[6195]380        default = False,
381        required = True,
[5753]382        )
383    #
[6195]384    # Process Data
[5753]385    #
386    application_date = schema.Date(
387        title = u'Application Date',
388        required = False,
[6195]389        readonly = True,
[5753]390        )
391    status = schema.TextLine(
392        # XXX: should be 'status' type
393        title = u'Application Status',
394        required = False,
[6195]395        readonly = True,
[5753]396        )
397    screening_score = schema.TextLine(
398        title = u'Screening Score',
399        required = False,
400        )
401    screening_venue = schema.TextLine(
402        title = u'Screening Venue',
403        required = False,
404        )
405    course_admitted = schema.TextLine(
406        # XXX: should be choice
407        title = u'Admitted Course of Study',
408        required = False,
409        )
410    entry_session = schema.TextLine(
[6195]411        # XXX: should be choice
[5753]412        title = u'Entry Session',
413        required = False,
414        )
415    notice = schema.Text(
416        title = u'Notice',
417        required = False,
418        )
419    student_id = schema.TextLine(
420        title = u'Student ID',
421        required = False,
[6195]422        readonly = True,
[5753]423        )
424
[6076]425
[5753]426class IApplicant(IApplicantBaseData):
427    """An applicant.
428
429    This is basically the applicant base data. Here we repeat the
[6195]430    fields from base data if we have to set the `required` attribute
431    to True (which is the default).
[5753]432    """
433
[6195]434class IApplicantEdit(IApplicantBaseData):
435    """An applicant.
[5753]436
[6195]437    Here we can repeat the fields from base data and set the `required` and
438    `readonly` attributes to True to further restrict the data access. We cannot
439    omit fields. This has to be done in the respective form page.
440    """
[5753]441    screening_score = schema.TextLine(
442        title = u'Screening Score',
[6195]443        required = False,
[5941]444        readonly = True,
445        )
[6195]446    screening_venue = schema.TextLine(
447        title = u'Screening Venue',
[5753]448        required = False,
449        readonly = True,
450        )
[6195]451    course_admitted = schema.TextLine(
[5753]452        # XXX: should be choice
[6195]453        title = u'Admitted Course of Study',
[5753]454        required = False,
[6195]455        readonly = True,
[5753]456        )
[6195]457    entry_session = schema.TextLine(
[5753]458        # XXX: should be choice
[6195]459        title = u'Entry Session',
[5753]460        required = False,
461        readonly = True,
462        )
[6195]463    notice = schema.Text(
464        title = u'Notice',
[5753]465        required = False,
466        readonly = True,
467        )
[5952]468    confirm_passport = schema.Bool(
[6195]469        title = u"I confirm that the Passport Photograph uploaded on this form is a true picture of me.",
[5952]470        default = False,
471        required = True,
472        )
[5758]473
474class IApplicantPrincipalInfo(IPrincipalInfo):
475    """Infos about principals that are applicants.
476    """
477    access_code = Attribute("The Access Code the user purchased")
478
479class IApplicantPrincipal(IPrincipal):
480    """A principal that is an applicant.
481
482    This interface extends zope.security.interfaces.IPrincipal and
483    requires also an `id` and other attributes defined there.
484    """
485    access_code = schema.TextLine(
486        title = u'Access Code',
487        description = u'The access code purchased by the user.',
488        required = True,
489        readonly = True)
490
491class IApplicantsFormChallenger(Interface):
492    """A challenger that uses a browser form to collect applicant
493       credentials.
494    """
495    loginpagename = schema.TextLine(
496        title = u'Loginpagename',
497        description = u"""Name of the login form used by challenger.
498
499        The form must provide an ``access_code`` input field.
500        """)
501
502    accesscode_field = schema.TextLine(
503        title = u'Access code field',
504        description = u'''Field of the login page which is looked up for
505                          access_code''',
506        default = u'access_code',
507        )
508
509
510class IApplicantSessionCredentials(Interface):
511    """Interface for storing and accessing applicant credentials in a
512       session.
513    """
514
515    def __init__(access_code):
516        """Create applicant session credentials."""
517
518    def getAccessCode():
519        """Return the access code."""
520
521
[5846]522class IApplicantsContainerProvider(Interface):
[5820]523    """A provider for applicants containers.
524
525    Applicants container providers are meant to be looked up as
526    utilities. This way we can find all applicant container types
527    defined somewhere.
528
529    Each applicants container provider registered as utility provides
530    one container type and one should be able to call the `factory`
531    attribute to create an instance of the requested container type.
532
533    .. THE FOLLOWING SHOULD GO INTO SPHINX DOCS (and be tested)
[6076]534
[5820]535    Samples
536    *******
[6076]537
[5820]538    Given, you had an IApplicantsContainer implementation somewhere
539    and you would like to make it findable on request, then you would
540    normally create an appropriate provider utility like this::
541
542      import grok
[5846]543      from waeup.sirp.applicants.interfaces import IApplicantsContainerProvider
[5820]544
545      class MyContainerProvider(grok.GlobalUtility):
[5846]546          grok.implements(IApplicantsContainerProvider)
[5820]547          grok.name('MyContainerProvider') # Must be unique
548          factory = MyContainer # A class implementing IApplicantsContainer
549                                # or derivations thereof.
550
551    This utility would be registered on startup and could then be used
552    like this:
553
554      >>> from zope.component import getAllUtilitiesRegisteredFor
555      >>> from waeup.sirp.applicants.interfaces import (
[5846]556      ...     IApplicantsContainerProvider)
[5820]557      >>> all_providers = getAllUtilitiesRegisteredFor(
[5846]558      ...     IApplicantsContainerProvider)
[5820]559      >>> all_providers
560      [<MyContainerProvider object at 0x...>]
561
562    You could look up this specific provider by name:
563
564      >>> from zope.component import getUtility
[5846]565      >>> p = getUtility(IApplicantsContainerProvider, name='MyProvider')
[5820]566      >>> p
567      <MyContainerProvider object at 0x...>
[6076]568
[5820]569    An applicants container would then be created like this:
570
571      >>> provider = all_providers[0]
572      >>> container = provider.factory()
573      >>> container
574      <MyContainer object at 0x...>
575
576    """
577    factory = Attribute("A class that can create instances of the "
578                        "requested container type")
Note: See TracBrowser for help on using the repository browser.