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

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

In the base system we don't use access codes (activation codes) anymore. I'd like to leave the respective lines as comments in case we need them in customization packages.

  • Property svn:keywords set to Id
File size: 16.3 KB
RevLine 
[5638]1## $Id: interfaces.py 7376 2011-12-18 11:29:58Z 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"""
[6256]20
[5866]21from grokcore.content.interfaces import IContainer
[6256]22
[5638]23from zope import schema
[7263]24from zope.interface import Interface, Attribute, implements, directlyProvides
25from zope.component import getUtilitiesFor, queryUtility
26from zope.catalog.interfaces import ICatalog
[7317]27from zope.schema.interfaces import (
28    ValidationError, ISource, IContextSourceBinder)
[6256]29from zc.sourcefactory.basic import BasicSourceFactory
[7263]30from waeup.sirp.schema import TextLineChoice
[7250]31from waeup.sirp.interfaces import (
[7321]32    ISIRPObject, year_range, validate_email, academic_sessions_vocab)
[6189]33from waeup.sirp.university.vocabularies import application_categories
[7347]34from waeup.sirp.students.vocabularies import (
35  lgas_vocab, CertificateSource, GenderSource,
36  )
[6256]37from waeup.sirp.applicants.vocabularies import (
[6648]38  application_types_vocab, application_pins_vocab,
[7347]39  AppCatCertificateSource,
[6256]40  )
[7250]41from waeup.sirp.payments.interfaces import IOnlinePayment
[5638]42
[7075]43#: Maximum upload size for applicant passport photographs (in bytes)
[7086]44MAX_UPLOAD_SIZE = 1024 * 20
[7075]45
[7263]46class RegNumInSource(ValidationError):
47    """Registration number exists already
48    """
49    # The docstring of ValidationErrors is used as error description
50    # by zope.formlib.
51    pass
52
53class RegNumberSource(object):
54    implements(ISource)
55    cat_name = 'applicants_catalog'
[7270]56    field_name = 'reg_number'
[7263]57    validation_error = RegNumInSource
58    def __init__(self, context):
59        self.context = context
60        return
61
62    def __contains__(self, value):
63        cat = queryUtility(ICatalog, self.cat_name)
64        if cat is None:
65            return True
66        kw = {self.field_name: (value, value)}
67        results = cat.searchResults(**kw)
68        for entry in results:
69            if entry.applicant_id != self.context.applicant_id:
70                # XXX: sources should simply return False.
71                #      But then we get some stupid error message in forms
72                #      when validation fails.
73                raise self.validation_error(value)
74                #return False
75        return True
76
77def contextual_reg_num_source(context):
78    source = RegNumberSource(context)
79    return source
80directlyProvides(contextual_reg_num_source, IContextSourceBinder)
81
[6069]82class ApplicantContainerProviderSource(BasicSourceFactory):
[6075]83    """A source offering all available applicants container types.
84
85    The values returned by this source are names of utilities that can
86    create :class:`ApplicantContainer` instances. So, if you get a
87    name like ``'myactype'`` from this source, then you can do:
88
89      >>> from zope.component import getUtility
90      >>> p = getUtility(IApplicantsContainerProvider, name=myactype)
91      >>> my_applicants_container = p.factory()
92
93    Or you can access class-attributes like
94
95      >>> my_applicants_container.container_title
96      'Pretty'
[6076]97
[6069]98    """
99    def getValues(self):
[6075]100        """Returns a list of ``(<name>, <provider>)`` tuples.
[5753]101
[6075]102        Here ``<name>`` is the name under which an
103        :class:``ApplicantContainerProvider`` was registered as a
104        utility and ``<provider>`` is the utility itself.
105        """
106        return getUtilitiesFor(IApplicantsContainerProvider)
107
[6069]108    def getToken(self, value):
[6075]109        """Return the name of the ``(<name>, <provider>)`` tuple.
110        """
111        return value[0]
112
[6069]113    def getTitle(self, value):
[6075]114        """Get a 'title - description' string for a container type.
115        """
116        factory = value[1].factory
117        return "%s - %s" % (
118            factory.container_title, factory.container_description)
[6069]119
[7321]120class IApplicantsRoot(ISIRPObject, IContainer):
[5676]121    """A container for university applicants containers.
[5645]122    """
[5866]123    pass
[5638]124
[7321]125class IApplicantsContainer(ISIRPObject):
[5676]126    """An applicants container contains university applicants.
[5645]127
[5638]128    """
[6069]129
[6075]130    container_title = Attribute(
131        u'classattribute: title for type of container')
132    container_description = Attribute(
133        u'classattribute: description for type of container')
134
[6076]135
[6069]136    code = schema.TextLine(
137        title = u'Code',
[6087]138        default = u'-',
[6069]139        required = True,
140        readonly = True,
[6076]141        )
[6096]142
[6087]143    title = schema.TextLine(
144        title = u'Title',
145        required = True,
146        default = u'-',
147        readonly = True,
[6096]148        )
149
[6087]150    prefix = schema.Choice(
151        title = u'Application target',
152        required = True,
153        default = None,
154        source = application_types_vocab,
155        readonly = True,
156        )
[6076]157
[6087]158    year = schema.Choice(
159        title = u'Year of entrance',
160        required = True,
161        default = None,
[6158]162        values = year_range(),
[6087]163        readonly = True,
[6096]164        )
[6087]165
[6069]166    provider = schema.Choice(
[6070]167        title = u'Applicants container type',
[6069]168        required = True,
169        default = None,
170        source = ApplicantContainerProviderSource(),
[6087]171        readonly = True,
[6076]172        )
[6158]173
[7376]174    #ac_prefix = schema.Choice(
175    #    title = u'Activation code prefix',
176    #    required = True,
177    #    default = None,
178    #    source = application_pins_vocab,
179    #    )
[6076]180
[6189]181    application_category = schema.Choice(
[6477]182        title = u'Category for the grouping of certificates',
[6189]183        required = True,
184        default = None,
185        source = application_categories,
186        )
187
[5645]188    description = schema.Text(
189        title = u'Human readable description in reST format',
[5638]190        required = False,
[6518]191        default = u'''This text can been seen by anonymous users.
192Here we put information about the study courses provided, the application procedure and deadlines.'''
[5638]193        )
194
195    startdate = schema.Date(
[6509]196        title = u'Application start date',
[5638]197        required = False,
198        default = None,
199        )
200
201    enddate = schema.Date(
[6509]202        title = u'Application closing date',
[5638]203        required = False,
204        default = None,
205        )
206
[5645]207    strict_deadline = schema.Bool(
208        title = u'Forbid additions after deadline (enddate)',
209        required = True,
210        default = True,
211        )
[5638]212
213    def archive(id=None):
[5676]214        """Create on-dist archive of applicants stored in this term.
[5638]215
[5676]216        If id is `None`, all applicants are archived.
[5638]217
218        If id contains a single id string, only the respective
[5676]219        applicants are archived.
[5638]220
221        If id contains a list of id strings all of the respective
[5676]222        applicants types are saved to disk.
[5638]223        """
224
225    def clear(id=None, archive=True):
[5676]226        """Remove applicants of type given by 'id'.
[5638]227
[5676]228        Optionally archive the applicants.
[6076]229
[5676]230        If id is `None`, all applicants are archived.
[5638]231
232        If id contains a single id string, only the respective
[5676]233        applicants are archived.
[5638]234
235        If id contains a list of id strings all of the respective
[5676]236        applicant types are saved to disk.
[5638]237
238        If `archive` is ``False`` none of the archive-handling is done
[5676]239        and respective applicants are simply removed from the
[5638]240        database.
241        """
[6073]242
[6069]243class IApplicantsContainerAdd(IApplicantsContainer):
244    """An applicants container contains university applicants.
245    """
[6087]246    prefix = schema.Choice(
247        title = u'Application target',
[6069]248        required = True,
[6087]249        default = None,
250        source = application_types_vocab,
[6069]251        readonly = False,
[6076]252        )
[6073]253
[6087]254    year = schema.Choice(
255        title = u'Year of entrance',
256        required = True,
257        default = None,
[6158]258        values = year_range(),
[6087]259        readonly = False,
[6096]260        )
[6073]261
[6087]262    provider = schema.Choice(
263        title = u'Applicants container type',
264        required = True,
265        default = None,
266        source = ApplicantContainerProviderSource(),
267        readonly = False,
268        )
269
[6096]270IApplicantsContainerAdd[
271    'prefix'].order =  IApplicantsContainer['prefix'].order
272IApplicantsContainerAdd[
273    'year'].order =  IApplicantsContainer['year'].order
274IApplicantsContainerAdd[
275    'provider'].order =  IApplicantsContainer['provider'].order
[6087]276
[7321]277class IApplicantBaseData(ISIRPObject):
[5753]278    """The data for an applicant.
279
[7240]280    This is a base interface with no field
[5753]281    required. For use with importers, forms, etc., please use one of
282    the derived interfaces below, which set more fields to required
283    state, depending on use-case.
[7338]284
285    This base interface is also implemented by the StudentApplication
286    class in the students package. Thus, these are the data which are saved
287    after admission.
[5753]288    """
[6304]289
[7240]290    applicant_id = schema.TextLine(
291        title = u'Applicant Id',
292        required = False,
[7260]293        readonly = False,
[7240]294        )
[7270]295    reg_number = TextLineChoice(
[5753]296        title = u'JAMB Registration Number',
[7263]297        readonly = False,
298        required = True,
299        default = None,
300        source = contextual_reg_num_source,
[5753]301        )
[7376]302    #access_code = schema.TextLine(
303    #    title = u'Activation Code',
304    #    required = False,
305    #    readonly = True,
306    #    )
[5753]307    firstname = schema.TextLine(
308        title = u'First Name',
[6352]309        required = True,
[5753]310        )
[7356]311    middlename = schema.TextLine(
312        title = u'Middle Name',
[5753]313        required = False,
314        )
315    lastname = schema.TextLine(
[6205]316        title = u'Last Name (Surname)',
[6352]317        required = True,
[5753]318        )
319    date_of_birth = schema.Date(
320        title = u'Date of Birth',
[6352]321        required = True,
[5753]322        )
[6249]323    lga = schema.Choice(
324        source = lgas_vocab,
[6205]325        title = u'State/LGA',
[6254]326        default = 'foreigner',
327        required = True,
[5753]328        )
329    sex = schema.Choice(
330        title = u'Sex',
331        source = GenderSource(),
332        default = u'm',
[6352]333        required = True,
[5753]334        )
[6341]335    email = schema.ASCIILine(
[5753]336        title = u'Email',
[7240]337        required = True,
[6343]338        constraint=validate_email,
[5753]339        )
[7324]340    phone = schema.TextLine(
[5753]341        title = u'Phone',
[7331]342        description = u'',
[5753]343        required = False,
344        )
[7262]345    course1 = schema.Choice(
346        title = u'1st Choice Course of Study',
[7347]347        source = CertificateSource(),
[7262]348        required = True,
349        )
350    course2 = schema.Choice(
351        title = u'2nd Choice Course of Study',
[7347]352        source = CertificateSource(),
[7262]353        required = False,
354        )
[6322]355
[5753]356    #
[7338]357    # Data to be imported after screening
[5753]358    #
[6255]359    screening_score = schema.Int(
[5753]360        title = u'Screening Score',
361        required = False,
362        )
363    screening_venue = schema.TextLine(
364        title = u'Screening Venue',
365        required = False,
366        )
[6248]367    course_admitted = schema.Choice(
[5753]368        title = u'Admitted Course of Study',
[7347]369        source = CertificateSource(),
[6254]370        default = None,
[5753]371        required = False,
372        )
[7347]373    notice = schema.Text(
374        title = u'Notice',
375        required = False,
376        )
[7338]377
378class IApplicantProcessData(IApplicantBaseData):
379    """An applicant.
380
381    Here we add process attributes and methods to the base data.
382    """
383
384    history = Attribute('Object history, a list of messages.')
385    state = Attribute('The application state of an applicant')
[7364]386    display_fullname = Attribute('The fullname of an applicant')
[7338]387    application_date = Attribute('Date of submission, used for export only')
388    password = Attribute('Encrypted password of a applicant')
389    application_number = Attribute('The key under which the record is stored')
390
391    def loggerInfo(ob_class, comment):
392        """Adds an INFO message to the log file
393        """
394
[5753]395    student_id = schema.TextLine(
[7250]396        title = u'Student Id',
[5753]397        required = False,
[7351]398        readonly = False,
[5753]399        )
[6302]400    locked = schema.Bool(
401        title = u'Form locked',
402        default = False,
403        )
[5753]404
[7338]405class IApplicant(IApplicantProcessData):
[5753]406    """An applicant.
407
408    This is basically the applicant base data. Here we repeat the
[6195]409    fields from base data if we have to set the `required` attribute
410    to True (which is the default).
[5753]411    """
412
[7338]413class IApplicantEdit(IApplicantProcessData):
[6195]414    """An applicant.
[5753]415
[6339]416    Here we can repeat the fields from base data and set the
417    `required` and `readonly` attributes to True to further restrict
[7262]418    the data access. Or we can allow only certain certificates to be
419    selected by choosing the appropriate source.
420
421    We cannot omit fields here. This has to be done in the
[6339]422    respective form page.
[6195]423    """
[7262]424
425    course1 = schema.Choice(
426        title = u'1st Choice Course of Study',
[7347]427        source = AppCatCertificateSource(),
[7262]428        required = True,
429        )
430    course2 = schema.Choice(
431        title = u'2nd Choice Course of Study',
[7347]432        source = AppCatCertificateSource(),
[7262]433        required = False,
434        )
[6255]435    screening_score = schema.Int(
[5753]436        title = u'Screening Score',
[6195]437        required = False,
[5941]438        readonly = True,
439        )
[6195]440    screening_venue = schema.TextLine(
441        title = u'Screening Venue',
[5753]442        required = False,
443        readonly = True,
444        )
[6301]445    course_admitted = schema.Choice(
[6195]446        title = u'Admitted Course of Study',
[7347]447        source = CertificateSource(),
[6301]448        default = None,
[5753]449        required = False,
[6195]450        readonly = True,
[5753]451        )
[6195]452    notice = schema.Text(
453        title = u'Notice',
[5753]454        required = False,
455        readonly = True,
456        )
[5758]457
[7338]458    def createStudent():
459        """Create a student object from applicatnt data
460        and copy applicant object.
461        """
462
[7268]463class IApplicantUpdateByRegNo(IApplicant):
464    """Representation of an applicant.
465
[7270]466    Skip regular reg_number validation if reg_number is used for finding
[7268]467    the applicant object.
468    """
[7270]469    reg_number = schema.TextLine(
[7268]470        title = u'Registration Number',
471        default = None,
472        required = False,
473        )
474
[7250]475class IApplicantOnlinePayment(IOnlinePayment):
476    """An applicant payment via payment gateways.
477
478    """
479    p_year = schema.Choice(
480        title = u'Payment Session',
481        source = academic_sessions_vocab,
482        required = False,
483        )
484
485IApplicantOnlinePayment['p_year'].order = IApplicantOnlinePayment[
486    'p_year'].order
487
[5846]488class IApplicantsContainerProvider(Interface):
[5820]489    """A provider for applicants containers.
490
491    Applicants container providers are meant to be looked up as
492    utilities. This way we can find all applicant container types
493    defined somewhere.
494
495    Each applicants container provider registered as utility provides
496    one container type and one should be able to call the `factory`
497    attribute to create an instance of the requested container type.
498
499    .. THE FOLLOWING SHOULD GO INTO SPHINX DOCS (and be tested)
[6076]500
[6500]501    Samples:
[6076]502
[5820]503    Given, you had an IApplicantsContainer implementation somewhere
504    and you would like to make it findable on request, then you would
505    normally create an appropriate provider utility like this::
506
507      import grok
[5846]508      from waeup.sirp.applicants.interfaces import IApplicantsContainerProvider
[5820]509
510      class MyContainerProvider(grok.GlobalUtility):
[5846]511          grok.implements(IApplicantsContainerProvider)
[5820]512          grok.name('MyContainerProvider') # Must be unique
513          factory = MyContainer # A class implementing IApplicantsContainer
514                                # or derivations thereof.
515
516    This utility would be registered on startup and could then be used
517    like this:
518
519      >>> from zope.component import getAllUtilitiesRegisteredFor
520      >>> from waeup.sirp.applicants.interfaces import (
[5846]521      ...     IApplicantsContainerProvider)
[5820]522      >>> all_providers = getAllUtilitiesRegisteredFor(
[5846]523      ...     IApplicantsContainerProvider)
[5820]524      >>> all_providers
525      [<MyContainerProvider object at 0x...>]
526
527    You could look up this specific provider by name:
528
529      >>> from zope.component import getUtility
[5846]530      >>> p = getUtility(IApplicantsContainerProvider, name='MyProvider')
[5820]531      >>> p
532      <MyContainerProvider object at 0x...>
[6076]533
[5820]534    An applicants container would then be created like this:
535
536      >>> provider = all_providers[0]
537      >>> container = provider.factory()
538      >>> container
539      <MyContainer object at 0x...>
540
541    """
542    factory = Attribute("A class that can create instances of the "
543                        "requested container type")
Note: See TracBrowser for help on using the repository browser.