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

Last change on this file since 11727 was 11599, checked in by Henrik Bettermann, 11 years ago

Special application procedure modified. Special applicants must submit
their form before they can download payment slips. Thus the regular
workflow is now applied but, in contrast to regular applications,
special applicants can create and pay more than one payment ticket.
Also application slips are hidden.

  • Property svn:keywords set to Id
File size: 19.3 KB
RevLine 
[5638]1## $Id: interfaces.py 11599 2014-04-24 06:17:10Z 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    """
151
[10184]152    APP_TYPES_DICT = Attribute('dict of application types')
[7844]153
[10184]154    def setPaymentDetails(container, payment):
155        """Set the payment data of an applicant.
156        """
157
158    def getApplicantsStatistics(container):
159        """Count applicants in containers.
160        """
161
[10187]162    def filterCertificates(context, resultset):
[10184]163        """Filter and sort certificates for AppCatCertificateSource.
164        """
165
[10186]166    def getCertTitle(context, value):
167        """Compose the titles in AppCatCertificateSource.
168        """
169
[7819]170class IApplicantsRoot(IKofaObject, IContainer):
[5676]171    """A container for university applicants containers.
[5645]172    """
[5638]173
[8388]174    description = schema.Text(
175        title = _(u'Human readable description in HTML format'),
176        required = False,
177        default = u'''This text can been seen by anonymous users.
178Here we put multi-lingual general information about the application procedure.
179>>de<<
180Dieser Text kann von anonymen Benutzern gelesen werden.
181Hier koennen mehrsprachige Informationen fuer Antragsteller hinterlegt werden.'''
182        )
183
184    description_dict = Attribute(
185        """Content as language dictionary with values in HTML format.""")
186
[7819]187class IApplicantsContainer(IKofaObject):
[5676]188    """An applicants container contains university applicants.
[5645]189
[5638]190    """
[6069]191
192    code = schema.TextLine(
[7708]193        title = _(u'Code'),
[6069]194        required = True,
195        readonly = True,
[6076]196        )
[6096]197
[6087]198    title = schema.TextLine(
[7708]199        title = _(u'Title'),
[6087]200        required = True,
[8562]201        readonly = False,
[6096]202        )
203
[6087]204    prefix = schema.Choice(
[7708]205        title = _(u'Application Target'),
[6087]206        required = True,
[7683]207        source = ApplicationTypeSource(),
[6087]208        readonly = True,
209        )
[6076]210
[6087]211    year = schema.Choice(
[7708]212        title = _(u'Year of Entrance'),
[6087]213        required = True,
[6158]214        values = year_range(),
[6087]215        readonly = True,
[6096]216        )
[6087]217
[8033]218    mode = schema.Choice(
219        title = _(u'Application Mode'),
220        vocabulary = application_modes_vocab,
221        required = True,
222        )
223
[8773]224    # Maybe FUTMinna still needs this ...
[7376]225    #ac_prefix = schema.Choice(
226    #    title = u'Activation code prefix',
227    #    required = True,
228    #    default = None,
[7683]229    #    source = ApplicationPinSource(),
[7376]230    #    )
[6076]231
[6189]232    application_category = schema.Choice(
[7708]233        title = _(u'Category for the grouping of certificates'),
[6189]234        required = True,
[7681]235        source = AppCatSource(),
[6189]236        )
237
[5645]238    description = schema.Text(
[8365]239        title = _(u'Human readable description in HTML format'),
[5638]240        required = False,
[6518]241        default = u'''This text can been seen by anonymous users.
[8033]242Here we put multi-lingual information about the study courses provided, the application procedure and deadlines.
[7708]243>>de<<
244Dieser Text kann von anonymen Benutzern gelesen werden.
245Hier koennen mehrsprachige Informationen fuer Antragsteller hinterlegt werden.'''
[5638]246        )
247
[7903]248    description_dict = Attribute(
249        """Content as language dictionary with values in HTML format.""")
[7708]250
[8200]251    startdate = schema.Datetime(
[7708]252        title = _(u'Application Start Date'),
[5638]253        required = False,
[8203]254        description = _('Example:') + u'2011-12-01 18:30:00+01:00',
[5638]255        )
256
[8200]257    enddate = schema.Datetime(
[7708]258        title = _(u'Application Closing Date'),
[5638]259        required = False,
[8203]260        description = _('Example:') + u'2011-12-31 23:59:59+01:00',
[5638]261        )
262
[5645]263    strict_deadline = schema.Bool(
[7708]264        title = _(u'Forbid additions after deadline (enddate)'),
[7984]265        required = False,
[5645]266        default = True,
267        )
[5638]268
[8525]269    application_fee = schema.Float(
270        title = _(u'Application Fee'),
271        default = 0.0,
272        required = False,
273        )
274
[10097]275    hidden= schema.Bool(
276        title = _(u'Hide container'),
277        required = False,
[10098]278        default = False,
[10097]279        )
280
[5638]281    def archive(id=None):
[5676]282        """Create on-dist archive of applicants stored in this term.
[5638]283
[5676]284        If id is `None`, all applicants are archived.
[5638]285
286        If id contains a single id string, only the respective
[5676]287        applicants are archived.
[5638]288
289        If id contains a list of id strings all of the respective
[5676]290        applicants types are saved to disk.
[5638]291        """
292
293    def clear(id=None, archive=True):
[5676]294        """Remove applicants of type given by 'id'.
[5638]295
[5676]296        Optionally archive the applicants.
[6076]297
[5676]298        If id is `None`, all applicants are archived.
[5638]299
300        If id contains a single id string, only the respective
[5676]301        applicants are archived.
[5638]302
303        If id contains a list of id strings all of the respective
[5676]304        applicant types are saved to disk.
[5638]305
306        If `archive` is ``False`` none of the archive-handling is done
[5676]307        and respective applicants are simply removed from the
[5638]308        database.
309        """
[6073]310
[9531]311    def writeLogMessage(view, comment):
312        """Adds an INFO message to the log file
313        """
314
[6069]315class IApplicantsContainerAdd(IApplicantsContainer):
316    """An applicants container contains university applicants.
317    """
[6087]318    prefix = schema.Choice(
[7708]319        title = _(u'Application Target'),
[6069]320        required = True,
[7683]321        source = ApplicationTypeSource(),
[6069]322        readonly = False,
[6076]323        )
[6073]324
[6087]325    year = schema.Choice(
[7708]326        title = _(u'Year of Entrance'),
[6087]327        required = True,
[6158]328        values = year_range(),
[6087]329        readonly = False,
[6096]330        )
[6073]331
[6096]332IApplicantsContainerAdd[
333    'prefix'].order =  IApplicantsContainer['prefix'].order
334IApplicantsContainerAdd[
335    'year'].order =  IApplicantsContainer['year'].order
[6087]336
[9217]337class IApplicantBaseData(IApplicantBase):
[5753]338    """The data for an applicant.
339
[7240]340    This is a base interface with no field
[7933]341    required. For use with processors, forms, etc., please use one of
[5753]342    the derived interfaces below, which set more fields to required
343    state, depending on use-case.
344    """
[8052]345
346    history = Attribute('Object history, a list of messages')
[8286]347    state = Attribute('The application state of an applicant')
[8052]348    display_fullname = Attribute('The fullname of an applicant')
[8589]349    application_date = Attribute('UTC datetime of submission, used for export only')
[8052]350    password = Attribute('Encrypted password of a applicant')
351    application_number = Attribute('The key under which the record is stored')
352
[8983]353    suspended = schema.Bool(
354        title = _(u'Account suspended'),
355        default = False,
[9035]356        required = False,
[8983]357        )
358
[7240]359    applicant_id = schema.TextLine(
[7708]360        title = _(u'Applicant Id'),
[7240]361        required = False,
[7260]362        readonly = False,
[7240]363        )
[7270]364    reg_number = TextLineChoice(
[8033]365        title = _(u'Registration Number'),
[7263]366        readonly = False,
367        required = True,
368        source = contextual_reg_num_source,
[5753]369        )
[7376]370    #access_code = schema.TextLine(
371    #    title = u'Activation Code',
372    #    required = False,
373    #    readonly = True,
374    #    )
[5753]375    firstname = schema.TextLine(
[7708]376        title = _(u'First Name'),
[6352]377        required = True,
[5753]378        )
[7356]379    middlename = schema.TextLine(
[7708]380        title = _(u'Middle Name'),
[5753]381        required = False,
382        )
383    lastname = schema.TextLine(
[7708]384        title = _(u'Last Name (Surname)'),
[6352]385        required = True,
[5753]386        )
[8149]387    date_of_birth = FormattedDate(
[7708]388        title = _(u'Date of Birth'),
[8540]389        required = False,
[8154]390        #date_format = u'%d/%m/%Y', # Use grok-instance-wide default
[8149]391        show_year = True,
[5753]392        )
393    sex = schema.Choice(
[7708]394        title = _(u'Sex'),
[5753]395        source = GenderSource(),
[6352]396        required = True,
[5753]397        )
[6341]398    email = schema.ASCIILine(
[7708]399        title = _(u'Email Address'),
[8033]400        required = False,
[6343]401        constraint=validate_email,
[5753]402        )
[8176]403    phone = PhoneNumber(
[7708]404        title = _(u'Phone'),
[7331]405        description = u'',
[5753]406        required = False,
407        )
[7262]408    course1 = schema.Choice(
[7708]409        title = _(u'1st Choice Course of Study'),
[8518]410        source = AppCatCertificateSource(),
[7262]411        required = True,
412        )
413    course2 = schema.Choice(
[7708]414        title = _(u'2nd Choice Course of Study'),
[8518]415        source = AppCatCertificateSource(),
[7262]416        required = False,
417        )
[8044]418    #school_grades = schema.List(
419    #    title = _(u'School Grades'),
420    #    value_type = ResultEntryField(),
421    #    required = False,
422    #    default = [],
423    #    )
[6322]424
[8052]425    notice = schema.Text(
426        title = _(u'Notice'),
[5753]427        required = False,
428        )
[8533]429    student_id = schema.TextLine(
430        title = _(u'Student Id'),
431        required = False,
432        readonly = False,
433        )
[6248]434    course_admitted = schema.Choice(
[7708]435        title = _(u'Admitted Course of Study'),
[7347]436        source = CertificateSource(),
[5753]437        required = False,
438        )
[6302]439    locked = schema.Bool(
[7708]440        title = _(u'Form locked'),
[6302]441        default = False,
[10384]442        required = False,
[6302]443        )
[5753]444
[10831]445    special_application = schema.Choice(
446        title = _(u'Special Application'),
447        source = SpecialApplicationSource(),
448        required = False,
449        )
450
[8052]451class IApplicant(IApplicantBaseData):
[5753]452    """An applicant.
453
454    This is basically the applicant base data. Here we repeat the
[6195]455    fields from base data if we have to set the `required` attribute
456    to True (which is the default).
[5753]457    """
458
[8742]459    def writeLogMessage(view, comment):
[8052]460        """Adds an INFO message to the log file
461        """
462
[8014]463    def createStudent():
464        """Create a student object from applicatnt data
465        and copy applicant object.
466        """
467
[10845]468class ISpecialApplicant(IApplicantBase):
469    """A special applicant.
470
471    This reduced interface is for former students or students who are not
472    users of the portal but have to pay supplementary fees.
473
474    This interface is used in browser components only. Thus we can't add
475    fields here to the regular IApplicant interface here. We can
476    only 'customize' fields.
477    """
478
479    history = Attribute('Object history, a list of messages')
480    state = Attribute('The application state of an applicant')
481    display_fullname = Attribute('The fullname of an applicant')
482    application_date = Attribute('UTC datetime of submission, used for export only')
483    password = Attribute('Encrypted password of a applicant')
484    application_number = Attribute('The key under which the record is stored')
485
486    suspended = schema.Bool(
487        title = _(u'Account suspended'),
488        default = False,
489        required = False,
490        )
491
[11599]492    locked = schema.Bool(
493        title = _(u'Form locked'),
494        default = False,
495        required = False,
496        )
497
[10845]498    applicant_id = schema.TextLine(
499        title = _(u'Applicant Id'),
500        required = False,
501        readonly = False,
502        )
503
504    firstname = schema.TextLine(
505        title = _(u'First Name'),
506        required = True,
507        )
508
509    middlename = schema.TextLine(
510        title = _(u'Middle Name'),
511        required = False,
512        )
513
514    lastname = schema.TextLine(
515        title = _(u'Last Name (Surname)'),
516        required = True,
517        )
518
519    reg_number = TextLineChoice(
[10846]520        title = _(u'Identification Number'),
521        description = u'Enter either registration or matriculation number.',
[10845]522        readonly = False,
523        required = True,
524        source = contextual_reg_num_source,
525        )
526
527    date_of_birth = FormattedDate(
528        title = _(u'Date of Birth'),
529        required = False,
530        #date_format = u'%d/%m/%Y', # Use grok-instance-wide default
531        show_year = True,
532        )
533
534    email = schema.ASCIILine(
535        title = _(u'Email Address'),
536        required = True,
537        constraint=validate_email,
538        )
539
540    phone = PhoneNumber(
541        title = _(u'Phone'),
542        description = u'',
543        required = False,
544        )
545
546    special_application = schema.Choice(
547        title = _(u'Special Application'),
548        source = SpecialApplicationSource(),
549        required = True,
550        )
551
[8016]552class IApplicantEdit(IApplicant):
[7867]553    """An applicant interface for editing.
[5753]554
[6339]555    Here we can repeat the fields from base data and set the
556    `required` and `readonly` attributes to True to further restrict
[7262]557    the data access. Or we can allow only certain certificates to be
558    selected by choosing the appropriate source.
559
560    We cannot omit fields here. This has to be done in the
[6339]561    respective form page.
[6195]562    """
[7262]563
[8097]564    email = schema.ASCIILine(
565        title = _(u'Email Address'),
566        required = True,
567        constraint=validate_email,
568        )
[7262]569    course1 = schema.Choice(
[7708]570        title = _(u'1st Choice Course of Study'),
[7347]571        source = AppCatCertificateSource(),
[7262]572        required = True,
573        )
574    course2 = schema.Choice(
[7708]575        title = _(u'2nd Choice Course of Study'),
[7347]576        source = AppCatCertificateSource(),
[7262]577        required = False,
578        )
[6301]579    course_admitted = schema.Choice(
[7708]580        title = _(u'Admitted Course of Study'),
[7347]581        source = CertificateSource(),
[5753]582        required = False,
[6195]583        readonly = True,
[5753]584        )
[6195]585    notice = schema.Text(
[7708]586        title = _(u'Notice'),
[5753]587        required = False,
588        readonly = True,
589        )
[5758]590
[8097]591IApplicantEdit['email'].order = IApplicantEdit[
592    'sex'].order
593
[7268]594class IApplicantUpdateByRegNo(IApplicant):
595    """Representation of an applicant.
596
[7270]597    Skip regular reg_number validation if reg_number is used for finding
[7268]598    the applicant object.
599    """
[7270]600    reg_number = schema.TextLine(
[7268]601        title = u'Registration Number',
602        required = False,
603        )
604
[8037]605class IApplicantRegisterUpdate(IApplicant):
606    """Representation of an applicant for first-time registration.
607
[8778]608    This interface is used when applicants use the registration page to
[8037]609    update their records.
610    """
611    reg_number = schema.TextLine(
612        title = u'Registration Number',
613        required = True,
614        )
615
616    firstname = schema.TextLine(
617        title = _(u'First Name'),
618        required = True,
619        )
620
621    email = schema.ASCIILine(
622        title = _(u'Email Address'),
623        required = True,
624        constraint=validate_email,
625        )
626
[7250]627class IApplicantOnlinePayment(IOnlinePayment):
628    """An applicant payment via payment gateways.
629
630    """
[8422]631
632    def doAfterApplicantPayment():
633        """Process applicant after payment was made.
634
635        """
636
[8453]637    def doAfterApplicantPaymentApproval():
638        """Process applicant after payment was approved.
639
640        """
641
[8422]642    def approveApplicantPayment():
643        """Approve payment and process applicant.
644
645        """
Note: See TracBrowser for help on using the repository browser.