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

Last change on this file since 7083 was 7075, checked in by uli, 13 years ago

Define maximum upload size for applicant photographs.

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