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

Last change on this file since 13991 was 13976, checked in by Henrik Bettermann, 9 years ago

Add first refereereports browser components.

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