source: main/waeup.kofa/trunk/src/waeup/kofa/applicants/interfaces.py @ 7878

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

Add test to verify classes after import (see previous revision in w.custom).

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