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

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

Show hint (field description) between lines in pdf slips like in regular display view.

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