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

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

Edit docstring and change order of fields.

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