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

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

Add passport picture switch to applicants containers.

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