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

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

Ease customization.

  • Property svn:keywords set to Id
File size: 20.9 KB
RevLine 
[5638]1## $Id: interfaces.py 16134 2020-06-28 18:01:52Z 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'),
[6087]223        required = True,
[6158]224        values = year_range(),
[6087]225        readonly = True,
[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'),
[6087]325        required = True,
[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,
381        required = True,
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
[8014]493    def createStudent():
[13104]494        """Create a student object from applicant data and copy
495        passport image and application slip.
[8014]496        """
497
[13089]498class ISpecialApplicant(IKofaObject):
[13077]499    """This reduced interface is for former students or students who are not
[13089]500    users of the portal but have to pay supplementary fees.
[10845]501    This interface is used in browser components only. Thus we can't add
502    fields here to the regular IApplicant interface here. We can
503    only 'customize' fields.
504    """
505
506    suspended = schema.Bool(
507        title = _(u'Account suspended'),
508        default = False,
509        required = False,
510        )
511
[11599]512    locked = schema.Bool(
513        title = _(u'Form locked'),
514        default = False,
515        required = False,
516        )
517
[10845]518    applicant_id = schema.TextLine(
519        title = _(u'Applicant Id'),
520        required = False,
521        readonly = False,
522        )
523
524    firstname = schema.TextLine(
525        title = _(u'First Name'),
526        required = True,
527        )
528
529    middlename = schema.TextLine(
530        title = _(u'Middle Name'),
531        required = False,
532        )
533
534    lastname = schema.TextLine(
535        title = _(u'Last Name (Surname)'),
536        required = True,
537        )
538
539    reg_number = TextLineChoice(
[10846]540        title = _(u'Identification Number'),
[15575]541        #description = _(u'Enter either registration or matriculation number.'),
[10845]542        readonly = False,
543        required = True,
544        source = contextual_reg_num_source,
545        )
546
547    date_of_birth = FormattedDate(
548        title = _(u'Date of Birth'),
549        required = False,
550        #date_format = u'%d/%m/%Y', # Use grok-instance-wide default
551        show_year = True,
552        )
553
554    email = schema.ASCIILine(
555        title = _(u'Email Address'),
556        required = True,
557        constraint=validate_email,
558        )
559
560    phone = PhoneNumber(
561        title = _(u'Phone'),
562        description = u'',
563        required = False,
564        )
565
566    special_application = schema.Choice(
567        title = _(u'Special Application'),
568        source = SpecialApplicationSource(),
569        required = True,
570        )
571
[8016]572class IApplicantEdit(IApplicant):
[13077]573    """This is an applicant interface for editing.
[5753]574
[6339]575    Here we can repeat the fields from base data and set the
576    `required` and `readonly` attributes to True to further restrict
[7262]577    the data access. Or we can allow only certain certificates to be
578    selected by choosing the appropriate source.
579
580    We cannot omit fields here. This has to be done in the
[6339]581    respective form page.
[6195]582    """
[7262]583
[8097]584    email = schema.ASCIILine(
585        title = _(u'Email Address'),
586        required = True,
587        constraint=validate_email,
588        )
[13077]589
[7262]590    course1 = schema.Choice(
[7708]591        title = _(u'1st Choice Course of Study'),
[7347]592        source = AppCatCertificateSource(),
[7262]593        required = True,
594        )
[13077]595
[7262]596    course2 = schema.Choice(
[7708]597        title = _(u'2nd Choice Course of Study'),
[7347]598        source = AppCatCertificateSource(),
[7262]599        required = False,
600        )
[13077]601
[6301]602    course_admitted = schema.Choice(
[7708]603        title = _(u'Admitted Course of Study'),
[7347]604        source = CertificateSource(),
[5753]605        required = False,
[6195]606        readonly = True,
[5753]607        )
[13077]608
[6195]609    notice = schema.Text(
[7708]610        title = _(u'Notice'),
[5753]611        required = False,
612        readonly = True,
613        )
[5758]614
[13089]615IApplicantEdit['email'].order = IApplicantEdit['sex'].order
[8097]616
[7268]617class IApplicantUpdateByRegNo(IApplicant):
[13077]618    """Skip regular reg_number validation if reg_number is used for finding
[7268]619    the applicant object.
620    """
[7270]621    reg_number = schema.TextLine(
[7268]622        title = u'Registration Number',
623        required = False,
624        )
625
[8037]626class IApplicantRegisterUpdate(IApplicant):
[13077]627    """This is a representation of an applicant for first-time registration.
[8778]628    This interface is used when applicants use the registration page to
[8037]629    update their records.
630    """
631    reg_number = schema.TextLine(
632        title = u'Registration Number',
633        required = True,
634        )
635
[11738]636    #firstname = schema.TextLine(
637    #    title = _(u'First Name'),
638    #    required = True,
639    #    )
640
641    lastname = schema.TextLine(
642        title = _(u'Last Name (Surname)'),
[8037]643        required = True,
644        )
645
646    email = schema.ASCIILine(
647        title = _(u'Email Address'),
648        required = True,
649        constraint=validate_email,
650        )
651
[7250]652class IApplicantOnlinePayment(IOnlinePayment):
653    """An applicant payment via payment gateways.
654    """
[8422]655
656    def doAfterApplicantPayment():
657        """Process applicant after payment was made.
658        """
659
[8453]660    def doAfterApplicantPaymentApproval():
661        """Process applicant after payment was approved.
662        """
663
[8422]664    def approveApplicantPayment():
665        """Approve payment and process applicant.
666        """
[13972]667
668class IApplicantRefereeReport(IKofaObject):
669    """A referee report.
670    """
671
672    r_id = Attribute('Report identifier')
673
[13976]674    creation_date = schema.Datetime(
[16058]675        title = _(u'Report Creation Date'),
[13976]676        readonly = False,
677        required = False,
678        )
679
[13972]680    name = schema.TextLine(
[16058]681        title = _(u'Referee Name'),
[13972]682        required = True,
683        )
684
685    email = schema.ASCIILine(
[16058]686        title = _(u'Referee Email Address'),
[13976]687        required = True,
[13972]688        constraint=validate_email,
689        )
690
691    phone = PhoneNumber(
[16058]692        title = _(u'Referee Phone'),
[13972]693        description = u'',
694        required = False,
695        )
696
697    report = schema.Text(
698        title = _(u'Report'),
699        required = False,
[13975]700        )
Note: See TracBrowser for help on using the repository browser.