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

Last change on this file since 15043 was 14040, checked in by Henrik Bettermann, 8 years ago

Draft email to be sent to referees.

  • Property svn:keywords set to Id
File size: 20.0 KB
RevLine 
[5638]1## $Id: interfaces.py 14040 2016-08-02 08:55:19Z 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
[13077]292    def addApplicant(applicant):
293        """Add an applicant.
[5638]294        """
295
[13077]296    def writeLogMessage(view, comment):
[13167]297        """Add an INFO message to applicants.log.
[5638]298        """
[6073]299
[13077]300    def traverse(name):
301        """Deliver appropriate containers.
[9531]302        """
303
[6069]304class IApplicantsContainerAdd(IApplicantsContainer):
305    """An applicants container contains university applicants.
306    """
[6087]307    prefix = schema.Choice(
[7708]308        title = _(u'Application Target'),
[6069]309        required = True,
[7683]310        source = ApplicationTypeSource(),
[6069]311        readonly = False,
[6076]312        )
[6073]313
[6087]314    year = schema.Choice(
[7708]315        title = _(u'Year of Entrance'),
[6087]316        required = True,
[6158]317        values = year_range(),
[6087]318        readonly = False,
[6096]319        )
[6073]320
[6096]321IApplicantsContainerAdd[
322    'prefix'].order =  IApplicantsContainer['prefix'].order
323IApplicantsContainerAdd[
324    'year'].order =  IApplicantsContainer['year'].order
[6087]325
[13089]326class IApplicantBaseData(IKofaObject):
[13077]327    """This is a base interface of an applicant with no field
[7933]328    required. For use with processors, forms, etc., please use one of
[5753]329    the derived interfaces below, which set more fields to required
330    state, depending on use-case.
331    """
[13080]332    state = Attribute('Application state of an applicant')
[8052]333    history = Attribute('Object history, a list of messages')
334    display_fullname = Attribute('The fullname of an applicant')
[13080]335    application_number = Attribute('The key under which the record is stored')
[13216]336    container_code = Attribute('Code of the parent container plus additional information if record is used or not')
[13080]337    translated_state = Attribute('Real name of the application state')
338    special = Attribute('True if special application')
[13971]339    payments = Attribute('List of payment objects stored in the applicant container')
[13080]340
[8589]341    application_date = Attribute('UTC datetime of submission, used for export only')
[13211]342    password = Attribute('Encrypted password of an applicant')
[8052]343
[13080]344
[8983]345    suspended = schema.Bool(
346        title = _(u'Account suspended'),
347        default = False,
[9035]348        required = False,
[8983]349        )
350
[7240]351    applicant_id = schema.TextLine(
[7708]352        title = _(u'Applicant Id'),
[7240]353        required = False,
[7260]354        readonly = False,
[7240]355        )
[13077]356
[7270]357    reg_number = TextLineChoice(
[8033]358        title = _(u'Registration Number'),
[7263]359        readonly = False,
360        required = True,
361        source = contextual_reg_num_source,
[5753]362        )
[13077]363
[5753]364    firstname = schema.TextLine(
[7708]365        title = _(u'First Name'),
[6352]366        required = True,
[5753]367        )
[13077]368
[7356]369    middlename = schema.TextLine(
[7708]370        title = _(u'Middle Name'),
[5753]371        required = False,
372        )
[13077]373
[5753]374    lastname = schema.TextLine(
[7708]375        title = _(u'Last Name (Surname)'),
[6352]376        required = True,
[5753]377        )
[13077]378
[8149]379    date_of_birth = FormattedDate(
[7708]380        title = _(u'Date of Birth'),
[8540]381        required = False,
[8149]382        show_year = True,
[5753]383        )
[13077]384
[5753]385    sex = schema.Choice(
[7708]386        title = _(u'Sex'),
[5753]387        source = GenderSource(),
[6352]388        required = True,
[5753]389        )
[13077]390
[6341]391    email = schema.ASCIILine(
[7708]392        title = _(u'Email Address'),
[8033]393        required = False,
[6343]394        constraint=validate_email,
[5753]395        )
[13077]396
[8176]397    phone = PhoneNumber(
[7708]398        title = _(u'Phone'),
[7331]399        description = u'',
[5753]400        required = False,
401        )
[13077]402
[7262]403    course1 = schema.Choice(
[7708]404        title = _(u'1st Choice Course of Study'),
[8518]405        source = AppCatCertificateSource(),
[13882]406        required = False,
[7262]407        )
[13077]408
[7262]409    course2 = schema.Choice(
[7708]410        title = _(u'2nd Choice Course of Study'),
[8518]411        source = AppCatCertificateSource(),
[7262]412        required = False,
413        )
[13077]414
[8052]415    notice = schema.Text(
416        title = _(u'Notice'),
[5753]417        required = False,
418        )
[8533]419    student_id = schema.TextLine(
420        title = _(u'Student Id'),
421        required = False,
422        readonly = False,
423        )
[6248]424    course_admitted = schema.Choice(
[7708]425        title = _(u'Admitted Course of Study'),
[7347]426        source = CertificateSource(),
[5753]427        required = False,
428        )
[6302]429    locked = schema.Bool(
[7708]430        title = _(u'Form locked'),
[6302]431        default = False,
[10384]432        required = False,
[6302]433        )
[5753]434
[10831]435    special_application = schema.Choice(
436        title = _(u'Special Application'),
437        source = SpecialApplicationSource(),
438        required = False,
439        )
440
[14011]441class IApplicantTestData(IKofaObject):
[14040]442    """This interface is for demonstration and testing only.
[14011]443    It can be omitted in customized versions of Kofa.
444    """
445
446    school_grades = schema.List(
447        title = _(u'School Grades'),
448        value_type = ResultEntryField(),
449        required = False,
450        defaultFactory=list,
451        )
452
453    referees = schema.List(
454        title = _(u'Referees'),
455        value_type = RefereeEntryField(),
456        required = False,
457        defaultFactory=list,
458        )
459
460IApplicantTestData['school_grades'].order = IApplicantBaseData['course2'].order
461
462class IApplicant(IApplicantBaseData, IApplicantTestData):
[13077]463    """This is basically the applicant base data. Here we repeat the
[6195]464    fields from base data if we have to set the `required` attribute
465    to True (which is the default).
[5753]466    """
467
[8742]468    def writeLogMessage(view, comment):
[13167]469        """Add an INFO message to applicants.log.
[8052]470        """
471
[8014]472    def createStudent():
[13104]473        """Create a student object from applicant data and copy
474        passport image and application slip.
[8014]475        """
476
[13089]477class ISpecialApplicant(IKofaObject):
[13077]478    """This reduced interface is for former students or students who are not
[13089]479    users of the portal but have to pay supplementary fees.
[10845]480    This interface is used in browser components only. Thus we can't add
481    fields here to the regular IApplicant interface here. We can
482    only 'customize' fields.
483    """
484
485    suspended = schema.Bool(
486        title = _(u'Account suspended'),
487        default = False,
488        required = False,
489        )
490
[11599]491    locked = schema.Bool(
492        title = _(u'Form locked'),
493        default = False,
494        required = False,
495        )
496
[10845]497    applicant_id = schema.TextLine(
498        title = _(u'Applicant Id'),
499        required = False,
500        readonly = False,
501        )
502
503    firstname = schema.TextLine(
504        title = _(u'First Name'),
505        required = True,
506        )
507
508    middlename = schema.TextLine(
509        title = _(u'Middle Name'),
510        required = False,
511        )
512
513    lastname = schema.TextLine(
514        title = _(u'Last Name (Surname)'),
515        required = True,
516        )
517
518    reg_number = TextLineChoice(
[10846]519        title = _(u'Identification Number'),
520        description = u'Enter either registration or matriculation number.',
[10845]521        readonly = False,
522        required = True,
523        source = contextual_reg_num_source,
524        )
525
526    date_of_birth = FormattedDate(
527        title = _(u'Date of Birth'),
528        required = False,
529        #date_format = u'%d/%m/%Y', # Use grok-instance-wide default
530        show_year = True,
531        )
532
533    email = schema.ASCIILine(
534        title = _(u'Email Address'),
535        required = True,
536        constraint=validate_email,
537        )
538
539    phone = PhoneNumber(
540        title = _(u'Phone'),
541        description = u'',
542        required = False,
543        )
544
545    special_application = schema.Choice(
546        title = _(u'Special Application'),
547        source = SpecialApplicationSource(),
548        required = True,
549        )
550
[8016]551class IApplicantEdit(IApplicant):
[13077]552    """This is an applicant interface for editing.
[5753]553
[6339]554    Here we can repeat the fields from base data and set the
555    `required` and `readonly` attributes to True to further restrict
[7262]556    the data access. Or we can allow only certain certificates to be
557    selected by choosing the appropriate source.
558
559    We cannot omit fields here. This has to be done in the
[6339]560    respective form page.
[6195]561    """
[7262]562
[8097]563    email = schema.ASCIILine(
564        title = _(u'Email Address'),
565        required = True,
566        constraint=validate_email,
567        )
[13077]568
[7262]569    course1 = schema.Choice(
[7708]570        title = _(u'1st Choice Course of Study'),
[7347]571        source = AppCatCertificateSource(),
[7262]572        required = True,
573        )
[13077]574
[7262]575    course2 = schema.Choice(
[7708]576        title = _(u'2nd Choice Course of Study'),
[7347]577        source = AppCatCertificateSource(),
[7262]578        required = False,
579        )
[13077]580
[6301]581    course_admitted = schema.Choice(
[7708]582        title = _(u'Admitted Course of Study'),
[7347]583        source = CertificateSource(),
[5753]584        required = False,
[6195]585        readonly = True,
[5753]586        )
[13077]587
[6195]588    notice = schema.Text(
[7708]589        title = _(u'Notice'),
[5753]590        required = False,
591        readonly = True,
592        )
[5758]593
[13089]594IApplicantEdit['email'].order = IApplicantEdit['sex'].order
[8097]595
[7268]596class IApplicantUpdateByRegNo(IApplicant):
[13077]597    """Skip regular reg_number validation if reg_number is used for finding
[7268]598    the applicant object.
599    """
[7270]600    reg_number = schema.TextLine(
[7268]601        title = u'Registration Number',
602        required = False,
603        )
604
[8037]605class IApplicantRegisterUpdate(IApplicant):
[13077]606    """This is a representation of an applicant for first-time registration.
[8778]607    This interface is used when applicants use the registration page to
[8037]608    update their records.
609    """
610    reg_number = schema.TextLine(
611        title = u'Registration Number',
612        required = True,
613        )
614
[11738]615    #firstname = schema.TextLine(
616    #    title = _(u'First Name'),
617    #    required = True,
618    #    )
619
620    lastname = schema.TextLine(
621        title = _(u'Last Name (Surname)'),
[8037]622        required = True,
623        )
624
625    email = schema.ASCIILine(
626        title = _(u'Email Address'),
627        required = True,
628        constraint=validate_email,
629        )
630
[7250]631class IApplicantOnlinePayment(IOnlinePayment):
632    """An applicant payment via payment gateways.
633    """
[8422]634
635    def doAfterApplicantPayment():
636        """Process applicant after payment was made.
637        """
638
[8453]639    def doAfterApplicantPaymentApproval():
640        """Process applicant after payment was approved.
641        """
642
[8422]643    def approveApplicantPayment():
644        """Approve payment and process applicant.
645        """
[13972]646
647class IApplicantRefereeReport(IKofaObject):
648    """A referee report.
649    """
650
651    r_id = Attribute('Report identifier')
652
[13976]653    creation_date = schema.Datetime(
654        title = _(u'Ticket Creation Date'),
655        readonly = False,
656        required = False,
657        )
658
[13972]659    name = schema.TextLine(
660        title = _(u'Name'),
661        required = True,
662        )
663
664    email = schema.ASCIILine(
665        title = _(u'Email Address'),
[13976]666        required = True,
[13972]667        constraint=validate_email,
668        )
669
670    phone = PhoneNumber(
671        title = _(u'Phone'),
672        description = u'',
673        required = False,
674        )
675
676    report = schema.Text(
677        title = _(u'Report'),
678        required = False,
[13975]679        )
Note: See TracBrowser for help on using the repository browser.