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

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

Merge IApplicantProcessData and IApplicantBaseData.

All attributes and fields of an applicant must be exportable.

  • Property svn:keywords set to Id
File size: 15.8 KB
RevLine 
[5638]1## $Id: interfaces.py 8052 2012-04-06 15:27:12Z 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 (
[8033]31    IKofaObject, year_range, validate_email, academic_sessions_vocab,
32    SimpleKofaVocabulary)
[7811]33from waeup.kofa.interfaces import MessageFactory as _
34from waeup.kofa.payments.interfaces import IOnlinePayment
35from waeup.kofa.schoolgrades import ResultEntryField
36from waeup.kofa.students.vocabularies import (
[7915]37    lgas_vocab, GenderSource)
[7811]38from waeup.kofa.university.vocabularies import (
[7915]39    course_levels, AppCatSource, CertificateSource)
[5638]40
[7075]41#: Maximum upload size for applicant passport photographs (in bytes)
[7086]42MAX_UPLOAD_SIZE = 1024 * 20
[7075]43
[7263]44class RegNumInSource(ValidationError):
45    """Registration number exists already
46    """
47    # The docstring of ValidationErrors is used as error description
48    # by zope.formlib.
49    pass
50
51class RegNumberSource(object):
52    implements(ISource)
53    cat_name = 'applicants_catalog'
[7270]54    field_name = 'reg_number'
[7263]55    validation_error = RegNumInSource
56    def __init__(self, context):
57        self.context = context
58        return
59
60    def __contains__(self, value):
61        cat = queryUtility(ICatalog, self.cat_name)
62        if cat is None:
63            return True
64        kw = {self.field_name: (value, value)}
65        results = cat.searchResults(**kw)
66        for entry in results:
67            if entry.applicant_id != self.context.applicant_id:
68                # XXX: sources should simply return False.
69                #      But then we get some stupid error message in forms
70                #      when validation fails.
71                raise self.validation_error(value)
72                #return False
73        return True
74
75def contextual_reg_num_source(context):
76    source = RegNumberSource(context)
77    return source
78directlyProvides(contextual_reg_num_source, IContextSourceBinder)
79
[7795]80
[7683]81class AppCatCertificateSource(CertificateSource):
82    """An application certificate source delivers all courses which belong to
83    a certain application_category.
84    """
85    def getValues(self, context):
86        # appliction category not available when certificate was deleted.
87        # shouldn't that info be part of applicant info instead?
88        # when we cannot determine the appcat, we will display all courses.
89        appcat = getattr(getattr(context, '__parent__', None),
90                         'application_category', None)
91        catalog = getUtility(ICatalog, name='certificates_catalog')
92        result = catalog.searchResults(
93            application_category=(appcat,appcat))
94        result = sorted(result, key=lambda value: value.code)
95        curr_course = context.course1
96        if curr_course is not None and curr_course not in result:
97            # display also current course even if it is not catalogued
98            # (any more)
99            result = [curr_course,] + result
100        return result
101
102class ApplicationTypeSource(BasicContextualSourceFactory):
103    """An application type source delivers screening types defined in the
104    portal.
105    """
106    def getValues(self, context):
[7688]107        appcats_dict = getUtility(
[7844]108            IApplicantsUtils).APP_TYPES_DICT
[7688]109        return sorted(appcats_dict.keys())
[7683]110
111    def getToken(self, context, value):
112        return value
113
114    def getTitle(self, context, value):
[7688]115        appcats_dict = getUtility(
[7844]116            IApplicantsUtils).APP_TYPES_DICT
[7688]117        return appcats_dict[value][0]
[7683]118
119# Maybe Uniben still needs this ...
120#class ApplicationPinSource(BasicContextualSourceFactory):
121#    """An application pin source delivers PIN prefixes for application
122#    defined in the portal.
123#    """
124#    def getValues(self, context):
[7688]125#        apppins_dict = getUtility(
[7844]126#            IApplicantsUtils).APP_TYPES_DICT
[7688]127#        return sorted(appcats_dict.keys())
[7683]128#
129#    def getToken(self, context, value):
130#        return value
131#
132#    def getTitle(self, context, value):
[7688]133#        apppins_dict = getUtility(
[7844]134#            IApplicantsUtils).APP_TYPES_DICT
[7683]135#        return u"%s (%s)" % (
[7688]136#            apppins_dict[value][1],self.apppins_dict[value][0])
[7683]137
[8033]138application_modes_vocab = SimpleKofaVocabulary(
139    (_('Create Application Records'), 'create'),
140    (_('Update Application Records'), 'update'),
141    )
[6075]142
[7682]143class IApplicantsUtils(Interface):
144    """A collection of methods which are subject to customization.
145    """
146
[7844]147    APP_TYPES_DICT = Attribute(' dict of application types')
148
[7819]149class IApplicantsRoot(IKofaObject, IContainer):
[5676]150    """A container for university applicants containers.
[5645]151    """
[5866]152    pass
[5638]153
[7819]154class IApplicantsContainer(IKofaObject):
[5676]155    """An applicants container contains university applicants.
[5645]156
[5638]157    """
[6069]158
159    code = schema.TextLine(
[7708]160        title = _(u'Code'),
[6069]161        required = True,
162        readonly = True,
[6076]163        )
[6096]164
[6087]165    title = schema.TextLine(
[7708]166        title = _(u'Title'),
[6087]167        required = True,
168        readonly = True,
[6096]169        )
170
[6087]171    prefix = schema.Choice(
[7708]172        title = _(u'Application Target'),
[6087]173        required = True,
[7683]174        source = ApplicationTypeSource(),
[6087]175        readonly = True,
176        )
[6076]177
[6087]178    year = schema.Choice(
[7708]179        title = _(u'Year of Entrance'),
[6087]180        required = True,
[6158]181        values = year_range(),
[6087]182        readonly = True,
[6096]183        )
[6087]184
[8033]185    mode = schema.Choice(
186        title = _(u'Application Mode'),
187        vocabulary = application_modes_vocab,
188        required = True,
189        )
190
[8008]191    entry_level = schema.Choice(
192        title = _(u'Entry Level'),
193        vocabulary = course_levels,
[6069]194        required = True,
[6076]195        )
[6158]196
[7683]197    # Maybe Uniben still needs this ...
[7376]198    #ac_prefix = schema.Choice(
199    #    title = u'Activation code prefix',
200    #    required = True,
201    #    default = None,
[7683]202    #    source = ApplicationPinSource(),
[7376]203    #    )
[6076]204
[6189]205    application_category = schema.Choice(
[7708]206        title = _(u'Category for the grouping of certificates'),
[6189]207        required = True,
[7681]208        source = AppCatSource(),
[6189]209        )
210
[5645]211    description = schema.Text(
[7708]212        title = _(u'Human readable description in reST format'),
[5638]213        required = False,
[6518]214        default = u'''This text can been seen by anonymous users.
[8033]215Here we put multi-lingual information about the study courses provided, the application procedure and deadlines.
[7708]216>>de<<
217Dieser Text kann von anonymen Benutzern gelesen werden.
218Hier koennen mehrsprachige Informationen fuer Antragsteller hinterlegt werden.'''
[5638]219        )
220
[7903]221    description_dict = Attribute(
222        """Content as language dictionary with values in HTML format.""")
[7708]223
[5638]224    startdate = schema.Date(
[7708]225        title = _(u'Application Start Date'),
[5638]226        required = False,
227        )
228
229    enddate = schema.Date(
[7708]230        title = _(u'Application Closing Date'),
[5638]231        required = False,
232        )
233
[5645]234    strict_deadline = schema.Bool(
[7708]235        title = _(u'Forbid additions after deadline (enddate)'),
[7984]236        required = False,
[5645]237        default = True,
238        )
[5638]239
240    def archive(id=None):
[5676]241        """Create on-dist archive of applicants stored in this term.
[5638]242
[5676]243        If id is `None`, all applicants are archived.
[5638]244
245        If id contains a single id string, only the respective
[5676]246        applicants are archived.
[5638]247
248        If id contains a list of id strings all of the respective
[5676]249        applicants types are saved to disk.
[5638]250        """
251
252    def clear(id=None, archive=True):
[5676]253        """Remove applicants of type given by 'id'.
[5638]254
[5676]255        Optionally archive the applicants.
[6076]256
[5676]257        If id is `None`, all applicants are archived.
[5638]258
259        If id contains a single id string, only the respective
[5676]260        applicants are archived.
[5638]261
262        If id contains a list of id strings all of the respective
[5676]263        applicant types are saved to disk.
[5638]264
265        If `archive` is ``False`` none of the archive-handling is done
[5676]266        and respective applicants are simply removed from the
[5638]267        database.
268        """
[6073]269
[6069]270class IApplicantsContainerAdd(IApplicantsContainer):
271    """An applicants container contains university applicants.
272    """
[6087]273    prefix = schema.Choice(
[7708]274        title = _(u'Application Target'),
[6069]275        required = True,
[7683]276        source = ApplicationTypeSource(),
[6069]277        readonly = False,
[6076]278        )
[6073]279
[6087]280    year = schema.Choice(
[7708]281        title = _(u'Year of Entrance'),
[6087]282        required = True,
[6158]283        values = year_range(),
[6087]284        readonly = False,
[6096]285        )
[6073]286
[6096]287IApplicantsContainerAdd[
288    'prefix'].order =  IApplicantsContainer['prefix'].order
289IApplicantsContainerAdd[
290    'year'].order =  IApplicantsContainer['year'].order
[6087]291
[7819]292class IApplicantBaseData(IKofaObject):
[5753]293    """The data for an applicant.
294
[7240]295    This is a base interface with no field
[7933]296    required. For use with processors, forms, etc., please use one of
[5753]297    the derived interfaces below, which set more fields to required
298    state, depending on use-case.
[7338]299
[7795]300    This base interface is also implemented by the
[7811]301    :class:`waeup.kofa.students.StudentApplication` class in the
[7795]302    students package. Thus, these are the data which are saved after
303    admission.
[5753]304    """
[8052]305
306    history = Attribute('Object history, a list of messages')
307    state = Attribute('The application state of an applicant')
308    display_fullname = Attribute('The fullname of an applicant')
309    application_date = Attribute('Date of submission, used for export only')
310    password = Attribute('Encrypted password of a applicant')
311    application_number = Attribute('The key under which the record is stored')
312
[7240]313    applicant_id = schema.TextLine(
[7708]314        title = _(u'Applicant Id'),
[7240]315        required = False,
[7260]316        readonly = False,
[7240]317        )
[7270]318    reg_number = TextLineChoice(
[8033]319        title = _(u'Registration Number'),
[7263]320        readonly = False,
321        required = True,
322        source = contextual_reg_num_source,
[5753]323        )
[7376]324    #access_code = schema.TextLine(
325    #    title = u'Activation Code',
326    #    required = False,
327    #    readonly = True,
328    #    )
[5753]329    firstname = schema.TextLine(
[7708]330        title = _(u'First Name'),
[6352]331        required = True,
[5753]332        )
[7356]333    middlename = schema.TextLine(
[7708]334        title = _(u'Middle Name'),
[5753]335        required = False,
336        )
337    lastname = schema.TextLine(
[7708]338        title = _(u'Last Name (Surname)'),
[6352]339        required = True,
[5753]340        )
341    date_of_birth = schema.Date(
[7708]342        title = _(u'Date of Birth'),
[6352]343        required = True,
[5753]344        )
[6249]345    lga = schema.Choice(
346        source = lgas_vocab,
[7708]347        title = _(u'State/LGA'),
[6254]348        default = 'foreigner',
[7984]349        required = False,
[5753]350        )
351    sex = schema.Choice(
[7708]352        title = _(u'Sex'),
[5753]353        source = GenderSource(),
[6352]354        required = True,
[5753]355        )
[6341]356    email = schema.ASCIILine(
[7708]357        title = _(u'Email Address'),
[8033]358        required = False,
[6343]359        constraint=validate_email,
[5753]360        )
[7324]361    phone = schema.TextLine(
[7708]362        title = _(u'Phone'),
[7331]363        description = u'',
[5753]364        required = False,
365        )
[7262]366    course1 = schema.Choice(
[7708]367        title = _(u'1st Choice Course of Study'),
[7347]368        source = CertificateSource(),
[7262]369        required = True,
370        )
371    course2 = schema.Choice(
[7708]372        title = _(u'2nd Choice Course of Study'),
[7347]373        source = CertificateSource(),
[7262]374        required = False,
375        )
[8044]376    #school_grades = schema.List(
377    #    title = _(u'School Grades'),
378    #    value_type = ResultEntryField(),
379    #    required = False,
380    #    default = [],
381    #    )
[6322]382
[8052]383    notice = schema.Text(
384        title = _(u'Notice'),
[5753]385        required = False,
386        )
387    screening_venue = schema.TextLine(
[7708]388        title = _(u'Screening Venue'),
[5753]389        required = False,
390        )
[8052]391    screening_score = schema.Int(
392        title = _(u'Screening Score'),
393        required = False,
394        )
[6248]395    course_admitted = schema.Choice(
[7708]396        title = _(u'Admitted Course of Study'),
[7347]397        source = CertificateSource(),
[5753]398        required = False,
399        )
400    student_id = schema.TextLine(
[7708]401        title = _(u'Student Id'),
[5753]402        required = False,
[7351]403        readonly = False,
[5753]404        )
[6302]405    locked = schema.Bool(
[7708]406        title = _(u'Form locked'),
[6302]407        default = False,
408        )
[5753]409
[8052]410class IApplicant(IApplicantBaseData):
[5753]411    """An applicant.
412
413    This is basically the applicant base data. Here we repeat the
[6195]414    fields from base data if we have to set the `required` attribute
415    to True (which is the default).
[5753]416    """
417
[8052]418    def loggerInfo(ob_class, comment):
419        """Adds an INFO message to the log file
420        """
421
[8014]422    def createStudent():
423        """Create a student object from applicatnt data
424        and copy applicant object.
425        """
426
[8016]427class IApplicantEdit(IApplicant):
[7867]428    """An applicant interface for editing.
[5753]429
[6339]430    Here we can repeat the fields from base data and set the
431    `required` and `readonly` attributes to True to further restrict
[7262]432    the data access. Or we can allow only certain certificates to be
433    selected by choosing the appropriate source.
434
435    We cannot omit fields here. This has to be done in the
[6339]436    respective form page.
[6195]437    """
[7262]438
439    course1 = schema.Choice(
[7708]440        title = _(u'1st Choice Course of Study'),
[7347]441        source = AppCatCertificateSource(),
[7262]442        required = True,
443        )
444    course2 = schema.Choice(
[7708]445        title = _(u'2nd Choice Course of Study'),
[7347]446        source = AppCatCertificateSource(),
[7262]447        required = False,
448        )
[6255]449    screening_score = schema.Int(
[7708]450        title = _(u'Screening Score'),
[6195]451        required = False,
[5941]452        readonly = True,
453        )
[6195]454    screening_venue = schema.TextLine(
[7708]455        title = _(u'Screening Venue'),
[5753]456        required = False,
457        readonly = True,
458        )
[6301]459    course_admitted = schema.Choice(
[7708]460        title = _(u'Admitted Course of Study'),
[7347]461        source = CertificateSource(),
[5753]462        required = False,
[6195]463        readonly = True,
[5753]464        )
[6195]465    notice = schema.Text(
[7708]466        title = _(u'Notice'),
[5753]467        required = False,
468        readonly = True,
469        )
[5758]470
[7268]471class IApplicantUpdateByRegNo(IApplicant):
472    """Representation of an applicant.
473
[7270]474    Skip regular reg_number validation if reg_number is used for finding
[7268]475    the applicant object.
476    """
[7270]477    reg_number = schema.TextLine(
[7268]478        title = u'Registration Number',
479        required = False,
480        )
481
[8037]482class IApplicantRegisterUpdate(IApplicant):
483    """Representation of an applicant for first-time registration.
484
485    This interface is used when apllicants use the registration page to
486    update their records.
487    """
488    reg_number = schema.TextLine(
489        title = u'Registration Number',
490        required = True,
491        )
492
493    firstname = schema.TextLine(
494        title = _(u'First Name'),
495        required = True,
496        )
497
498    email = schema.ASCIILine(
499        title = _(u'Email Address'),
500        required = True,
501        constraint=validate_email,
502        )
503
[7250]504class IApplicantOnlinePayment(IOnlinePayment):
505    """An applicant payment via payment gateways.
506
507    """
508    p_year = schema.Choice(
[7708]509        title = _(u'Payment Session'),
[7250]510        source = academic_sessions_vocab,
511        required = False,
512        )
513
514IApplicantOnlinePayment['p_year'].order = IApplicantOnlinePayment[
515    'p_year'].order
516
Note: See TracBrowser for help on using the repository browser.