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

Last change on this file since 9187 was 9115, checked in by Henrik Bettermann, 12 years ago

The year_range function was only used in applicants. Extend academic session range properly.

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