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

Last change on this file since 13079 was 13077, checked in by Henrik Bettermann, 10 years ago

More adjustments for a proper documentation.

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