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

Last change on this file since 15642 was 15575, checked in by Henrik Bettermann, 5 years ago

Don't show description.

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