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

Last change on this file since 16250 was 16243, checked in by Henrik Bettermann, 4 years ago

Save email address provided by mandate when referee report
is created. Add RefereeReportManageFormPage (no button available).

  • Property svn:keywords set to Id
File size: 21.2 KB
RevLine 
[5638]1## $Id: interfaces.py 16243 2020-09-23 19:42:07Z henrik $
[6076]2##
[6087]3## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
[5638]4## This program is free software; you can redistribute it and/or modify
5## it under the terms of the GNU General Public License as published by
6## the Free Software Foundation; either version 2 of the License, or
7## (at your option) any later version.
[6076]8##
[5638]9## This program is distributed in the hope that it will be useful,
10## but WITHOUT ANY WARRANTY; without even the implied warranty of
11## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12## GNU General Public License for more details.
[6076]13##
[5638]14## You should have received a copy of the GNU General Public License
15## along with this program; if not, write to the Free Software
16## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17##
[6500]18"""Interfaces of the university application package.
[5638]19"""
[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
[16134]168    def isPictureEditable(container):
169        """False if applicants are not allowed to edit uploaded pictures.
170        """
171
[7819]172class IApplicantsRoot(IKofaObject, IContainer):
[13077]173    """A container for applicants containers.
[5645]174    """
[13077]175    description_dict = Attribute('Language translation dictionary with values in HTML format')
176    local_roles = Attribute('List of local role names')
177    logger_name = Attribute('Name of the logger')
178    logger_filename = Attribute('Name of the logger file')
[5638]179
[8388]180    description = schema.Text(
181        title = _(u'Human readable description in HTML format'),
182        required = False,
[13235]183        constraint=validate_html,
[8388]184        default = u'''This text can been seen by anonymous users.
185Here we put multi-lingual general information about the application procedure.
186>>de<<
187Dieser Text kann von anonymen Benutzern gelesen werden.
188Hier koennen mehrsprachige Informationen fuer Antragsteller hinterlegt werden.'''
189        )
190
[7819]191class IApplicantsContainer(IKofaObject):
[13077]192    """An applicants container contains applicants.
[5638]193    """
[13077]194    statistics = Attribute('Applicant counts')
195    expired = Attribute('True if application has started but not ended')
[6069]196
[13077]197    description_dict = Attribute('Language translation dictionary with values in HTML format')
198    local_roles = Attribute('List of local role names')
[16133]199    picture_editable = Attribute('False if applicants are not allowed to edit uploaded pictures.')
[13077]200
201
[6069]202    code = schema.TextLine(
[7708]203        title = _(u'Code'),
[6069]204        required = True,
205        readonly = True,
[6076]206        )
[6096]207
[6087]208    title = schema.TextLine(
[7708]209        title = _(u'Title'),
[6087]210        required = True,
[8562]211        readonly = False,
[6096]212        )
213
[6087]214    prefix = schema.Choice(
[7708]215        title = _(u'Application Target'),
[6087]216        required = True,
[7683]217        source = ApplicationTypeSource(),
[6087]218        readonly = True,
219        )
[6076]220
[6087]221    year = schema.Choice(
[7708]222        title = _(u'Year of Entrance'),
[16190]223        required = False,
[6158]224        values = year_range(),
[16190]225        readonly = False,
[6096]226        )
[6087]227
[8033]228    mode = schema.Choice(
229        title = _(u'Application Mode'),
230        vocabulary = application_modes_vocab,
231        required = True,
232        )
233
[8773]234    # Maybe FUTMinna still needs this ...
[7376]235    #ac_prefix = schema.Choice(
236    #    title = u'Activation code prefix',
237    #    required = True,
238    #    default = None,
[7683]239    #    source = ApplicationPinSource(),
[7376]240    #    )
[6076]241
[6189]242    application_category = schema.Choice(
[7708]243        title = _(u'Category for the grouping of certificates'),
[6189]244        required = True,
[7681]245        source = AppCatSource(),
[6189]246        )
247
[5645]248    description = schema.Text(
[8365]249        title = _(u'Human readable description in HTML format'),
[5638]250        required = False,
[13235]251        constraint=validate_html,
[6518]252        default = u'''This text can been seen by anonymous users.
[8033]253Here we put multi-lingual information about the study courses provided, the application procedure and deadlines.
[7708]254>>de<<
255Dieser Text kann von anonymen Benutzern gelesen werden.
256Hier koennen mehrsprachige Informationen fuer Antragsteller hinterlegt werden.'''
[5638]257        )
258
[8200]259    startdate = schema.Datetime(
[7708]260        title = _(u'Application Start Date'),
[5638]261        required = False,
[13031]262        description = _('Example: ') + u'2011-12-01 18:30:00+01:00',
[5638]263        )
264
[8200]265    enddate = schema.Datetime(
[7708]266        title = _(u'Application Closing Date'),
[5638]267        required = False,
[13031]268        description = _('Example: ') + u'2011-12-31 23:59:59+01:00',
[5638]269        )
270
[5645]271    strict_deadline = schema.Bool(
[7708]272        title = _(u'Forbid additions after deadline (enddate)'),
[7984]273        required = False,
[5645]274        default = True,
275        )
[5638]276
[8525]277    application_fee = schema.Float(
278        title = _(u'Application Fee'),
279        default = 0.0,
280        required = False,
281        )
282
[11869]283    application_slip_notice = schema.Text(
284        title = _(u'Human readable notice on application slip in HTML format'),
285        required = False,
[13235]286        constraint=validate_html,
[11869]287        )
288
[10097]289    hidden= schema.Bool(
290        title = _(u'Hide container'),
291        required = False,
[10098]292        default = False,
[10097]293        )
294
[15502]295    with_picture= schema.Bool(
296        title = _(u'With passport picture'),
297        required = False,
298        default = True,
299        )
300
[13077]301    def addApplicant(applicant):
302        """Add an applicant.
[5638]303        """
304
[13077]305    def writeLogMessage(view, comment):
[13167]306        """Add an INFO message to applicants.log.
[5638]307        """
[6073]308
[13077]309    def traverse(name):
310        """Deliver appropriate containers.
[9531]311        """
312
[6069]313class IApplicantsContainerAdd(IApplicantsContainer):
314    """An applicants container contains university applicants.
315    """
[6087]316    prefix = schema.Choice(
[7708]317        title = _(u'Application Target'),
[6069]318        required = True,
[7683]319        source = ApplicationTypeSource(),
[6069]320        readonly = False,
[6076]321        )
[6073]322
[6087]323    year = schema.Choice(
[7708]324        title = _(u'Year of Entrance'),
[16190]325        required = False,
[6158]326        values = year_range(),
[6087]327        readonly = False,
[6096]328        )
[6073]329
[15548]330    container_number = schema.Choice(
331        title = _(u'Container Number'),
[15552]332        values = range(1,100),
[15548]333        description = _(u'If set, this number will be added to the container '
334                         'prefix (e.g. app3). If not set, the year of entrance will be '
335                         'used (e.g. app2019).'),
336        required = False,
337        readonly = False,
338        )
339
[6096]340IApplicantsContainerAdd[
341    'prefix'].order =  IApplicantsContainer['prefix'].order
342IApplicantsContainerAdd[
343    'year'].order =  IApplicantsContainer['year'].order
[15548]344IApplicantsContainerAdd[
345    'container_number'].order =  IApplicantsContainer['year'].order
[6087]346
[13089]347class IApplicantBaseData(IKofaObject):
[13077]348    """This is a base interface of an applicant with no field
[7933]349    required. For use with processors, forms, etc., please use one of
[5753]350    the derived interfaces below, which set more fields to required
351    state, depending on use-case.
352    """
[13080]353    state = Attribute('Application state of an applicant')
[8052]354    history = Attribute('Object history, a list of messages')
355    display_fullname = Attribute('The fullname of an applicant')
[13080]356    application_number = Attribute('The key under which the record is stored')
[13216]357    container_code = Attribute('Code of the parent container plus additional information if record is used or not')
[13080]358    translated_state = Attribute('Real name of the application state')
359    special = Attribute('True if special application')
[13971]360    payments = Attribute('List of payment objects stored in the applicant container')
[13080]361
[8589]362    application_date = Attribute('UTC datetime of submission, used for export only')
[13211]363    password = Attribute('Encrypted password of an applicant')
[8052]364
[13080]365
[8983]366    suspended = schema.Bool(
367        title = _(u'Account suspended'),
368        default = False,
[9035]369        required = False,
[8983]370        )
371
[7240]372    applicant_id = schema.TextLine(
[7708]373        title = _(u'Applicant Id'),
[7240]374        required = False,
[7260]375        readonly = False,
[7240]376        )
[13077]377
[7270]378    reg_number = TextLineChoice(
[8033]379        title = _(u'Registration Number'),
[7263]380        readonly = False,
[16186]381        required = False,
[7263]382        source = contextual_reg_num_source,
[5753]383        )
[13077]384
[5753]385    firstname = schema.TextLine(
[7708]386        title = _(u'First Name'),
[6352]387        required = True,
[5753]388        )
[13077]389
[7356]390    middlename = schema.TextLine(
[7708]391        title = _(u'Middle Name'),
[5753]392        required = False,
393        )
[13077]394
[5753]395    lastname = schema.TextLine(
[7708]396        title = _(u'Last Name (Surname)'),
[6352]397        required = True,
[5753]398        )
[13077]399
[8149]400    date_of_birth = FormattedDate(
[7708]401        title = _(u'Date of Birth'),
[8540]402        required = False,
[8149]403        show_year = True,
[5753]404        )
[13077]405
[5753]406    sex = schema.Choice(
[15790]407        title = _(u'Gender'),
[5753]408        source = GenderSource(),
[6352]409        required = True,
[5753]410        )
[13077]411
[6341]412    email = schema.ASCIILine(
[7708]413        title = _(u'Email Address'),
[8033]414        required = False,
[6343]415        constraint=validate_email,
[5753]416        )
[13077]417
[8176]418    phone = PhoneNumber(
[7708]419        title = _(u'Phone'),
[7331]420        description = u'',
[5753]421        required = False,
422        )
[13077]423
[7262]424    course1 = schema.Choice(
[7708]425        title = _(u'1st Choice Course of Study'),
[8518]426        source = AppCatCertificateSource(),
[13882]427        required = False,
[7262]428        )
[13077]429
[7262]430    course2 = schema.Choice(
[7708]431        title = _(u'2nd Choice Course of Study'),
[8518]432        source = AppCatCertificateSource(),
[7262]433        required = False,
434        )
[13077]435
[8052]436    notice = schema.Text(
437        title = _(u'Notice'),
[5753]438        required = False,
439        )
[8533]440    student_id = schema.TextLine(
441        title = _(u'Student Id'),
442        required = False,
443        readonly = False,
444        )
[6248]445    course_admitted = schema.Choice(
[7708]446        title = _(u'Admitted Course of Study'),
[7347]447        source = CertificateSource(),
[5753]448        required = False,
449        )
[6302]450    locked = schema.Bool(
[7708]451        title = _(u'Form locked'),
[6302]452        default = False,
[10384]453        required = False,
[6302]454        )
[5753]455
[10831]456    special_application = schema.Choice(
457        title = _(u'Special Application'),
458        source = SpecialApplicationSource(),
459        required = False,
460        )
461
[14011]462class IApplicantTestData(IKofaObject):
[14040]463    """This interface is for demonstration and testing only.
[14011]464    It can be omitted in customized versions of Kofa.
465    """
466
467    school_grades = schema.List(
468        title = _(u'School Grades'),
469        value_type = ResultEntryField(),
470        required = False,
471        defaultFactory=list,
472        )
473
474    referees = schema.List(
475        title = _(u'Referees'),
476        value_type = RefereeEntryField(),
477        required = False,
478        defaultFactory=list,
479        )
480
481IApplicantTestData['school_grades'].order = IApplicantBaseData['course2'].order
482
483class IApplicant(IApplicantBaseData, IApplicantTestData):
[13077]484    """This is basically the applicant base data. Here we repeat the
[6195]485    fields from base data if we have to set the `required` attribute
486    to True (which is the default).
[5753]487    """
488
[8742]489    def writeLogMessage(view, comment):
[13167]490        """Add an INFO message to applicants.log.
[8052]491        """
492
[16228]493    def createStudent(view, graduated):
[13104]494        """Create a student object from applicant data and copy
[16228]495        passport image and application slip. If graduated is set True,
496        a graduated student is being created. This method is supposed
497        to be used for transcript applicants. It is tested but not
498        used in the base package.
[8014]499        """
[13089]500class ISpecialApplicant(IKofaObject):
[13077]501    """This reduced interface is for former students or students who are not
[13089]502    users of the portal but have to pay supplementary fees.
[10845]503    This interface is used in browser components only. Thus we can't add
504    fields here to the regular IApplicant interface here. We can
505    only 'customize' fields.
506    """
507
508    suspended = schema.Bool(
509        title = _(u'Account suspended'),
510        default = False,
511        required = False,
512        )
513
[11599]514    locked = schema.Bool(
515        title = _(u'Form locked'),
516        default = False,
517        required = False,
518        )
519
[10845]520    applicant_id = schema.TextLine(
521        title = _(u'Applicant Id'),
522        required = False,
523        readonly = False,
524        )
525
526    firstname = schema.TextLine(
527        title = _(u'First Name'),
528        required = True,
529        )
530
531    middlename = schema.TextLine(
532        title = _(u'Middle Name'),
533        required = False,
534        )
535
536    lastname = schema.TextLine(
537        title = _(u'Last Name (Surname)'),
538        required = True,
539        )
540
541    reg_number = TextLineChoice(
[10846]542        title = _(u'Identification Number'),
[15575]543        #description = _(u'Enter either registration or matriculation number.'),
[10845]544        readonly = False,
545        required = True,
546        source = contextual_reg_num_source,
547        )
548
549    date_of_birth = FormattedDate(
550        title = _(u'Date of Birth'),
551        required = False,
552        #date_format = u'%d/%m/%Y', # Use grok-instance-wide default
553        show_year = True,
554        )
555
556    email = schema.ASCIILine(
557        title = _(u'Email Address'),
558        required = True,
559        constraint=validate_email,
560        )
561
562    phone = PhoneNumber(
563        title = _(u'Phone'),
564        description = u'',
565        required = False,
566        )
567
568    special_application = schema.Choice(
569        title = _(u'Special Application'),
570        source = SpecialApplicationSource(),
571        required = True,
572        )
573
[8016]574class IApplicantEdit(IApplicant):
[13077]575    """This is an applicant interface for editing.
[5753]576
[6339]577    Here we can repeat the fields from base data and set the
578    `required` and `readonly` attributes to True to further restrict
[7262]579    the data access. Or we can allow only certain certificates to be
580    selected by choosing the appropriate source.
581
582    We cannot omit fields here. This has to be done in the
[6339]583    respective form page.
[6195]584    """
[7262]585
[8097]586    email = schema.ASCIILine(
587        title = _(u'Email Address'),
588        required = True,
589        constraint=validate_email,
590        )
[13077]591
[7262]592    course1 = schema.Choice(
[7708]593        title = _(u'1st Choice Course of Study'),
[7347]594        source = AppCatCertificateSource(),
[7262]595        required = True,
596        )
[13077]597
[7262]598    course2 = schema.Choice(
[7708]599        title = _(u'2nd Choice Course of Study'),
[7347]600        source = AppCatCertificateSource(),
[7262]601        required = False,
602        )
[13077]603
[6301]604    course_admitted = schema.Choice(
[7708]605        title = _(u'Admitted Course of Study'),
[7347]606        source = CertificateSource(),
[5753]607        required = False,
[6195]608        readonly = True,
[5753]609        )
[13077]610
[6195]611    notice = schema.Text(
[7708]612        title = _(u'Notice'),
[5753]613        required = False,
614        readonly = True,
615        )
[5758]616
[13089]617IApplicantEdit['email'].order = IApplicantEdit['sex'].order
[8097]618
[7268]619class IApplicantUpdateByRegNo(IApplicant):
[13077]620    """Skip regular reg_number validation if reg_number is used for finding
[7268]621    the applicant object.
622    """
[7270]623    reg_number = schema.TextLine(
[7268]624        title = u'Registration Number',
625        required = False,
626        )
627
[8037]628class IApplicantRegisterUpdate(IApplicant):
[13077]629    """This is a representation of an applicant for first-time registration.
[8778]630    This interface is used when applicants use the registration page to
[8037]631    update their records.
632    """
633    reg_number = schema.TextLine(
634        title = u'Registration Number',
635        required = True,
636        )
637
[11738]638    #firstname = schema.TextLine(
639    #    title = _(u'First Name'),
640    #    required = True,
641    #    )
642
643    lastname = schema.TextLine(
644        title = _(u'Last Name (Surname)'),
[8037]645        required = True,
646        )
647
648    email = schema.ASCIILine(
649        title = _(u'Email Address'),
650        required = True,
651        constraint=validate_email,
652        )
653
[7250]654class IApplicantOnlinePayment(IOnlinePayment):
655    """An applicant payment via payment gateways.
656    """
[8422]657
658    def doAfterApplicantPayment():
659        """Process applicant after payment was made.
660        """
661
[8453]662    def doAfterApplicantPaymentApproval():
663        """Process applicant after payment was approved.
664        """
665
[8422]666    def approveApplicantPayment():
667        """Approve payment and process applicant.
668        """
[13972]669
670class IApplicantRefereeReport(IKofaObject):
671    """A referee report.
672    """
673
674    r_id = Attribute('Report identifier')
[16243]675    email = Attribute('Referee email address taken from the mandate')
[13972]676
[13976]677    creation_date = schema.Datetime(
[16058]678        title = _(u'Report Creation Date'),
[13976]679        readonly = False,
680        required = False,
681        )
682
[13972]683    name = schema.TextLine(
[16058]684        title = _(u'Referee Name'),
[13972]685        required = True,
686        )
687
[16243]688    email_pref = schema.ASCIILine(
689        title = _(u'Preferred Email Address'),
690        required = False,
691        constraint=validate_email,
692        readonly = False,
[13972]693        )
694
695    phone = PhoneNumber(
[16058]696        title = _(u'Referee Phone'),
[13972]697        description = u'',
698        required = False,
699        )
700
701    report = schema.Text(
702        title = _(u'Report'),
703        required = False,
[13975]704        )
Note: See TracBrowser for help on using the repository browser.