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

Last change on this file since 8197 was 8176, checked in by uli, 13 years ago

Use PhoneNumber?.

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