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

Last change on this file since 6626 was 6594, checked in by uli, 13 years ago

Remove unnecessary return. We use createWAeUPImageFile in order to get
an image file with an image id as data and not the raw binary file
data. Creating a WAeUPImageFile directly (WAeUPImageFile(filename,
filedata)) would mean to store the binary image data in the
WAeUPImageFile itself (and therefore in the ZODB).

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