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

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

Implement referee report slip. Show passport picture on referee pages.

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