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

Last change on this file since 12758 was 11869, checked in by Henrik Bettermann, 10 years ago

Add application_slip_notice field to ApplicantsContainer?.

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