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

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

Remove entry_level attribute. The entry level is given by the start level of the study course admitted.

  • Property svn:keywords set to Id
File size: 15.6 KB
RevLine 
[5638]1## $Id: interfaces.py 8311 2012-04-29 19:47:24Z 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)
[8149]29from waeup.kofa.schema import TextLineChoice, FormattedDate
[7811]30from waeup.kofa.interfaces import (
[8245]31    IKofaObject, year_range, validate_email,
[8033]32    SimpleKofaVocabulary)
[7811]33from waeup.kofa.interfaces import MessageFactory as _
34from waeup.kofa.payments.interfaces import IOnlinePayment
[8176]35from waeup.kofa.schema import PhoneNumber
[8069]36from waeup.kofa.students.vocabularies import GenderSource
[7811]37from waeup.kofa.university.vocabularies import (
[7915]38    course_levels, AppCatSource, CertificateSource)
[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
[8033]137application_modes_vocab = SimpleKofaVocabulary(
138    (_('Create Application Records'), 'create'),
139    (_('Update Application Records'), 'update'),
140    )
[6075]141
[7682]142class IApplicantsUtils(Interface):
143    """A collection of methods which are subject to customization.
144    """
145
[7844]146    APP_TYPES_DICT = Attribute(' dict of application types')
147
[7819]148class IApplicantsRoot(IKofaObject, IContainer):
[5676]149    """A container for university applicants containers.
[5645]150    """
[5866]151    pass
[5638]152
[7819]153class IApplicantsContainer(IKofaObject):
[5676]154    """An applicants container contains university applicants.
[5645]155
[5638]156    """
[6069]157
158    code = schema.TextLine(
[7708]159        title = _(u'Code'),
[6069]160        required = True,
161        readonly = True,
[6076]162        )
[6096]163
[6087]164    title = schema.TextLine(
[7708]165        title = _(u'Title'),
[6087]166        required = True,
167        readonly = True,
[6096]168        )
169
[6087]170    prefix = schema.Choice(
[7708]171        title = _(u'Application Target'),
[6087]172        required = True,
[7683]173        source = ApplicationTypeSource(),
[6087]174        readonly = True,
175        )
[6076]176
[6087]177    year = schema.Choice(
[7708]178        title = _(u'Year of Entrance'),
[6087]179        required = True,
[6158]180        values = year_range(),
[6087]181        readonly = True,
[6096]182        )
[6087]183
[8033]184    mode = schema.Choice(
185        title = _(u'Application Mode'),
186        vocabulary = application_modes_vocab,
187        required = True,
188        )
189
[7683]190    # Maybe Uniben still needs this ...
[7376]191    #ac_prefix = schema.Choice(
192    #    title = u'Activation code prefix',
193    #    required = True,
194    #    default = None,
[7683]195    #    source = ApplicationPinSource(),
[7376]196    #    )
[6076]197
[6189]198    application_category = schema.Choice(
[7708]199        title = _(u'Category for the grouping of certificates'),
[6189]200        required = True,
[7681]201        source = AppCatSource(),
[6189]202        )
203
[5645]204    description = schema.Text(
[7708]205        title = _(u'Human readable description in reST format'),
[5638]206        required = False,
[6518]207        default = u'''This text can been seen by anonymous users.
[8033]208Here we put multi-lingual information about the study courses provided, the application procedure and deadlines.
[7708]209>>de<<
210Dieser Text kann von anonymen Benutzern gelesen werden.
211Hier koennen mehrsprachige Informationen fuer Antragsteller hinterlegt werden.'''
[5638]212        )
213
[7903]214    description_dict = Attribute(
215        """Content as language dictionary with values in HTML format.""")
[7708]216
[8200]217    startdate = schema.Datetime(
[7708]218        title = _(u'Application Start Date'),
[5638]219        required = False,
[8203]220        description = _('Example:') + u'2011-12-01 18:30:00+01:00',
[5638]221        )
222
[8200]223    enddate = schema.Datetime(
[7708]224        title = _(u'Application Closing Date'),
[5638]225        required = False,
[8203]226        description = _('Example:') + u'2011-12-31 23:59:59+01:00',
[5638]227        )
228
[5645]229    strict_deadline = schema.Bool(
[7708]230        title = _(u'Forbid additions after deadline (enddate)'),
[7984]231        required = False,
[5645]232        default = True,
233        )
[5638]234
235    def archive(id=None):
[5676]236        """Create on-dist archive of applicants stored in this term.
[5638]237
[5676]238        If id is `None`, all applicants are archived.
[5638]239
240        If id contains a single id string, only the respective
[5676]241        applicants are archived.
[5638]242
243        If id contains a list of id strings all of the respective
[5676]244        applicants types are saved to disk.
[5638]245        """
246
247    def clear(id=None, archive=True):
[5676]248        """Remove applicants of type given by 'id'.
[5638]249
[5676]250        Optionally archive the applicants.
[6076]251
[5676]252        If id is `None`, all applicants are archived.
[5638]253
254        If id contains a single id string, only the respective
[5676]255        applicants are archived.
[5638]256
257        If id contains a list of id strings all of the respective
[5676]258        applicant types are saved to disk.
[5638]259
260        If `archive` is ``False`` none of the archive-handling is done
[5676]261        and respective applicants are simply removed from the
[5638]262        database.
263        """
[6073]264
[6069]265class IApplicantsContainerAdd(IApplicantsContainer):
266    """An applicants container contains university applicants.
267    """
[6087]268    prefix = schema.Choice(
[7708]269        title = _(u'Application Target'),
[6069]270        required = True,
[7683]271        source = ApplicationTypeSource(),
[6069]272        readonly = False,
[6076]273        )
[6073]274
[6087]275    year = schema.Choice(
[7708]276        title = _(u'Year of Entrance'),
[6087]277        required = True,
[6158]278        values = year_range(),
[6087]279        readonly = False,
[6096]280        )
[6073]281
[6096]282IApplicantsContainerAdd[
283    'prefix'].order =  IApplicantsContainer['prefix'].order
284IApplicantsContainerAdd[
285    'year'].order =  IApplicantsContainer['year'].order
[6087]286
[7819]287class IApplicantBaseData(IKofaObject):
[5753]288    """The data for an applicant.
289
[7240]290    This is a base interface with no field
[7933]291    required. For use with processors, forms, etc., please use one of
[5753]292    the derived interfaces below, which set more fields to required
293    state, depending on use-case.
[7338]294
[7795]295    This base interface is also implemented by the
[7811]296    :class:`waeup.kofa.students.StudentApplication` class in the
[7795]297    students package. Thus, these are the data which are saved after
298    admission.
[5753]299    """
[8052]300
301    history = Attribute('Object history, a list of messages')
[8286]302    state = Attribute('The application state of an applicant')
[8052]303    display_fullname = Attribute('The fullname of an applicant')
304    application_date = Attribute('Date of submission, used for export only')
305    password = Attribute('Encrypted password of a applicant')
306    application_number = Attribute('The key under which the record is stored')
307
[7240]308    applicant_id = schema.TextLine(
[7708]309        title = _(u'Applicant Id'),
[7240]310        required = False,
[7260]311        readonly = False,
[7240]312        )
[7270]313    reg_number = TextLineChoice(
[8033]314        title = _(u'Registration Number'),
[7263]315        readonly = False,
316        required = True,
317        source = contextual_reg_num_source,
[5753]318        )
[7376]319    #access_code = schema.TextLine(
320    #    title = u'Activation Code',
321    #    required = False,
322    #    readonly = True,
323    #    )
[5753]324    firstname = schema.TextLine(
[7708]325        title = _(u'First Name'),
[6352]326        required = True,
[5753]327        )
[7356]328    middlename = schema.TextLine(
[7708]329        title = _(u'Middle Name'),
[5753]330        required = False,
331        )
332    lastname = schema.TextLine(
[7708]333        title = _(u'Last Name (Surname)'),
[6352]334        required = True,
[5753]335        )
[8149]336    date_of_birth = FormattedDate(
[7708]337        title = _(u'Date of Birth'),
[6352]338        required = True,
[8154]339        #date_format = u'%d/%m/%Y', # Use grok-instance-wide default
[8149]340        show_year = True,
[5753]341        )
342    sex = schema.Choice(
[7708]343        title = _(u'Sex'),
[5753]344        source = GenderSource(),
[6352]345        required = True,
[5753]346        )
[6341]347    email = schema.ASCIILine(
[7708]348        title = _(u'Email Address'),
[8033]349        required = False,
[6343]350        constraint=validate_email,
[5753]351        )
[8176]352    phone = PhoneNumber(
[7708]353        title = _(u'Phone'),
[7331]354        description = u'',
[5753]355        required = False,
356        )
[7262]357    course1 = schema.Choice(
[7708]358        title = _(u'1st Choice Course of Study'),
[7347]359        source = CertificateSource(),
[7262]360        required = True,
361        )
362    course2 = schema.Choice(
[7708]363        title = _(u'2nd Choice Course of Study'),
[7347]364        source = CertificateSource(),
[7262]365        required = False,
366        )
[8044]367    #school_grades = schema.List(
368    #    title = _(u'School Grades'),
369    #    value_type = ResultEntryField(),
370    #    required = False,
371    #    default = [],
372    #    )
[6322]373
[8052]374    notice = schema.Text(
375        title = _(u'Notice'),
[5753]376        required = False,
377        )
378    screening_venue = schema.TextLine(
[7708]379        title = _(u'Screening Venue'),
[5753]380        required = False,
381        )
[8052]382    screening_score = schema.Int(
383        title = _(u'Screening Score'),
384        required = False,
385        )
[6248]386    course_admitted = schema.Choice(
[7708]387        title = _(u'Admitted Course of Study'),
[7347]388        source = CertificateSource(),
[5753]389        required = False,
390        )
391    student_id = schema.TextLine(
[7708]392        title = _(u'Student Id'),
[5753]393        required = False,
[7351]394        readonly = False,
[5753]395        )
[6302]396    locked = schema.Bool(
[7708]397        title = _(u'Form locked'),
[6302]398        default = False,
399        )
[5753]400
[8052]401class IApplicant(IApplicantBaseData):
[5753]402    """An applicant.
403
404    This is basically the applicant base data. Here we repeat the
[6195]405    fields from base data if we have to set the `required` attribute
406    to True (which is the default).
[5753]407    """
408
[8052]409    def loggerInfo(ob_class, comment):
410        """Adds an INFO message to the log file
411        """
412
[8014]413    def createStudent():
414        """Create a student object from applicatnt data
415        and copy applicant object.
416        """
417
[8016]418class IApplicantEdit(IApplicant):
[7867]419    """An applicant interface for editing.
[5753]420
[6339]421    Here we can repeat the fields from base data and set the
422    `required` and `readonly` attributes to True to further restrict
[7262]423    the data access. Or we can allow only certain certificates to be
424    selected by choosing the appropriate source.
425
426    We cannot omit fields here. This has to be done in the
[6339]427    respective form page.
[6195]428    """
[7262]429
[8097]430    email = schema.ASCIILine(
431        title = _(u'Email Address'),
432        required = True,
433        constraint=validate_email,
434        )
[7262]435    course1 = schema.Choice(
[7708]436        title = _(u'1st Choice Course of Study'),
[7347]437        source = AppCatCertificateSource(),
[7262]438        required = True,
439        )
440    course2 = schema.Choice(
[7708]441        title = _(u'2nd Choice Course of Study'),
[7347]442        source = AppCatCertificateSource(),
[7262]443        required = False,
444        )
[6255]445    screening_score = schema.Int(
[7708]446        title = _(u'Screening Score'),
[6195]447        required = False,
[5941]448        readonly = True,
449        )
[6195]450    screening_venue = schema.TextLine(
[7708]451        title = _(u'Screening Venue'),
[5753]452        required = False,
453        readonly = True,
454        )
[6301]455    course_admitted = schema.Choice(
[7708]456        title = _(u'Admitted Course of Study'),
[7347]457        source = CertificateSource(),
[5753]458        required = False,
[6195]459        readonly = True,
[5753]460        )
[6195]461    notice = schema.Text(
[7708]462        title = _(u'Notice'),
[5753]463        required = False,
464        readonly = True,
465        )
[5758]466
[8097]467IApplicantEdit['email'].order = IApplicantEdit[
468    'sex'].order
469
[7268]470class IApplicantUpdateByRegNo(IApplicant):
471    """Representation of an applicant.
472
[7270]473    Skip regular reg_number validation if reg_number is used for finding
[7268]474    the applicant object.
475    """
[7270]476    reg_number = schema.TextLine(
[7268]477        title = u'Registration Number',
478        required = False,
479        )
480
[8037]481class IApplicantRegisterUpdate(IApplicant):
482    """Representation of an applicant for first-time registration.
483
484    This interface is used when apllicants use the registration page to
485    update their records.
486    """
487    reg_number = schema.TextLine(
488        title = u'Registration Number',
489        required = True,
490        )
491
492    firstname = schema.TextLine(
493        title = _(u'First Name'),
494        required = True,
495        )
496
497    email = schema.ASCIILine(
498        title = _(u'Email Address'),
499        required = True,
500        constraint=validate_email,
501        )
502
[7250]503class IApplicantOnlinePayment(IOnlinePayment):
504    """An applicant payment via payment gateways.
505
506    """
Note: See TracBrowser for help on using the repository browser.