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

Last change on this file since 10186 was 10186, checked in by Henrik Bettermann, 11 years ago

Prepare getTitle for customization.

  • Property svn:keywords set to Id
File size: 17.0 KB
RevLine 
[5638]1## $Id: interfaces.py 10186 2013-05-22 07:34:47Z 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
[8613]38from waeup.kofa.university.vocabularies import AppCatSource, CertificateSource
[5638]39
[7075]40#: Maximum upload size for applicant passport photographs (in bytes)
[7086]41MAX_UPLOAD_SIZE = 1024 * 20
[7075]42
[8611]43_marker = object() # a marker different from None
44
[9115]45def year_range():
46    curr_year = datetime.now().year
47    return range(curr_year - 2, curr_year + 5)
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))
[10184]98        result = getUtility(
99            IApplicantsUtils).filterCertificates(result)
[7683]100        curr_course = context.course1
101        if curr_course is not None and curr_course not in result:
[10184]102            # display also current course even if certificate has been removed
[7683]103            result = [curr_course,] + result
104        return result
105
[10186]106    def getTitle(self, context, value):
107        return getUtility(
108            IApplicantsUtils).getCertTitle(context, value)
109
[7683]110class ApplicationTypeSource(BasicContextualSourceFactory):
111    """An application type source delivers screening types defined in the
112    portal.
113    """
114    def getValues(self, context):
[7688]115        appcats_dict = getUtility(
[7844]116            IApplicantsUtils).APP_TYPES_DICT
[7688]117        return sorted(appcats_dict.keys())
[7683]118
119    def getToken(self, context, value):
120        return value
121
122    def getTitle(self, context, value):
[7688]123        appcats_dict = getUtility(
[7844]124            IApplicantsUtils).APP_TYPES_DICT
[7688]125        return appcats_dict[value][0]
[7683]126
[8773]127# Maybe FUTMinna still needs this ...
[7683]128#class ApplicationPinSource(BasicContextualSourceFactory):
129#    """An application pin source delivers PIN prefixes for application
130#    defined in the portal.
131#    """
132#    def getValues(self, context):
[7688]133#        apppins_dict = getUtility(
[7844]134#            IApplicantsUtils).APP_TYPES_DICT
[7688]135#        return sorted(appcats_dict.keys())
[7683]136#
137#    def getToken(self, context, value):
138#        return value
139#
140#    def getTitle(self, context, value):
[7688]141#        apppins_dict = getUtility(
[7844]142#            IApplicantsUtils).APP_TYPES_DICT
[7683]143#        return u"%s (%s)" % (
[7688]144#            apppins_dict[value][1],self.apppins_dict[value][0])
[7683]145
[8033]146application_modes_vocab = SimpleKofaVocabulary(
147    (_('Create Application Records'), 'create'),
148    (_('Update Application Records'), 'update'),
149    )
[6075]150
[7682]151class IApplicantsUtils(Interface):
152    """A collection of methods which are subject to customization.
153    """
154
[10184]155    APP_TYPES_DICT = Attribute('dict of application types')
[7844]156
[10184]157    def setPaymentDetails(container, payment):
158        """Set the payment data of an applicant.
159        """
160
161    def getApplicantsStatistics(container):
162        """Count applicants in containers.
163        """
164
165    def filterCertificates(resultset):
166        """Filter and sort certificates for AppCatCertificateSource.
167        """
168
[10186]169    def getCertTitle(context, value):
170        """Compose the titles in AppCatCertificateSource.
171        """
172
[7819]173class IApplicantsRoot(IKofaObject, IContainer):
[5676]174    """A container for university applicants containers.
[5645]175    """
[5638]176
[8388]177    description = schema.Text(
178        title = _(u'Human readable description in HTML format'),
179        required = False,
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
187    description_dict = Attribute(
188        """Content as language dictionary with values in HTML format.""")
189
[7819]190class IApplicantsContainer(IKofaObject):
[5676]191    """An applicants container contains university applicants.
[5645]192
[5638]193    """
[6069]194
195    code = schema.TextLine(
[7708]196        title = _(u'Code'),
[6069]197        required = True,
198        readonly = True,
[6076]199        )
[6096]200
[6087]201    title = schema.TextLine(
[7708]202        title = _(u'Title'),
[6087]203        required = True,
[8562]204        readonly = False,
[6096]205        )
206
[6087]207    prefix = schema.Choice(
[7708]208        title = _(u'Application Target'),
[6087]209        required = True,
[7683]210        source = ApplicationTypeSource(),
[6087]211        readonly = True,
212        )
[6076]213
[6087]214    year = schema.Choice(
[7708]215        title = _(u'Year of Entrance'),
[6087]216        required = True,
[6158]217        values = year_range(),
[6087]218        readonly = True,
[6096]219        )
[6087]220
[8033]221    mode = schema.Choice(
222        title = _(u'Application Mode'),
223        vocabulary = application_modes_vocab,
224        required = True,
225        )
226
[8773]227    # Maybe FUTMinna still needs this ...
[7376]228    #ac_prefix = schema.Choice(
229    #    title = u'Activation code prefix',
230    #    required = True,
231    #    default = None,
[7683]232    #    source = ApplicationPinSource(),
[7376]233    #    )
[6076]234
[6189]235    application_category = schema.Choice(
[7708]236        title = _(u'Category for the grouping of certificates'),
[6189]237        required = True,
[7681]238        source = AppCatSource(),
[6189]239        )
240
[5645]241    description = schema.Text(
[8365]242        title = _(u'Human readable description in HTML format'),
[5638]243        required = False,
[6518]244        default = u'''This text can been seen by anonymous users.
[8033]245Here we put multi-lingual information about the study courses provided, the application procedure and deadlines.
[7708]246>>de<<
247Dieser Text kann von anonymen Benutzern gelesen werden.
248Hier koennen mehrsprachige Informationen fuer Antragsteller hinterlegt werden.'''
[5638]249        )
250
[7903]251    description_dict = Attribute(
252        """Content as language dictionary with values in HTML format.""")
[7708]253
[8200]254    startdate = schema.Datetime(
[7708]255        title = _(u'Application Start Date'),
[5638]256        required = False,
[8203]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,
[8203]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
[10097]278    hidden= schema.Bool(
279        title = _(u'Hide container'),
280        required = False,
[10098]281        default = False,
[10097]282        )
283
[5638]284    def archive(id=None):
[5676]285        """Create on-dist archive of applicants stored in this term.
[5638]286
[5676]287        If id is `None`, all applicants are archived.
[5638]288
289        If id contains a single id string, only the respective
[5676]290        applicants are archived.
[5638]291
292        If id contains a list of id strings all of the respective
[5676]293        applicants types are saved to disk.
[5638]294        """
295
296    def clear(id=None, archive=True):
[5676]297        """Remove applicants of type given by 'id'.
[5638]298
[5676]299        Optionally archive the applicants.
[6076]300
[5676]301        If id is `None`, all applicants are archived.
[5638]302
303        If id contains a single id string, only the respective
[5676]304        applicants are archived.
[5638]305
306        If id contains a list of id strings all of the respective
[5676]307        applicant types are saved to disk.
[5638]308
309        If `archive` is ``False`` none of the archive-handling is done
[5676]310        and respective applicants are simply removed from the
[5638]311        database.
312        """
[6073]313
[9531]314    def writeLogMessage(view, comment):
315        """Adds an INFO message to the log file
316        """
317
[6069]318class IApplicantsContainerAdd(IApplicantsContainer):
319    """An applicants container contains university applicants.
320    """
[6087]321    prefix = schema.Choice(
[7708]322        title = _(u'Application Target'),
[6069]323        required = True,
[7683]324        source = ApplicationTypeSource(),
[6069]325        readonly = False,
[6076]326        )
[6073]327
[6087]328    year = schema.Choice(
[7708]329        title = _(u'Year of Entrance'),
[6087]330        required = True,
[6158]331        values = year_range(),
[6087]332        readonly = False,
[6096]333        )
[6073]334
[6096]335IApplicantsContainerAdd[
336    'prefix'].order =  IApplicantsContainer['prefix'].order
337IApplicantsContainerAdd[
338    'year'].order =  IApplicantsContainer['year'].order
[6087]339
[9217]340class IApplicantBaseData(IApplicantBase):
[5753]341    """The data for an applicant.
342
[7240]343    This is a base interface with no field
[7933]344    required. For use with processors, forms, etc., please use one of
[5753]345    the derived interfaces below, which set more fields to required
346    state, depending on use-case.
347    """
[8052]348
349    history = Attribute('Object history, a list of messages')
[8286]350    state = Attribute('The application state of an applicant')
[8052]351    display_fullname = Attribute('The fullname of an applicant')
[8589]352    application_date = Attribute('UTC datetime of submission, used for export only')
[8052]353    password = Attribute('Encrypted password of a applicant')
354    application_number = Attribute('The key under which the record is stored')
355
[8983]356    suspended = schema.Bool(
357        title = _(u'Account suspended'),
358        default = False,
[9035]359        required = False,
[8983]360        )
361
[7240]362    applicant_id = schema.TextLine(
[7708]363        title = _(u'Applicant Id'),
[7240]364        required = False,
[7260]365        readonly = False,
[7240]366        )
[7270]367    reg_number = TextLineChoice(
[8033]368        title = _(u'Registration Number'),
[7263]369        readonly = False,
370        required = True,
371        source = contextual_reg_num_source,
[5753]372        )
[7376]373    #access_code = schema.TextLine(
374    #    title = u'Activation Code',
375    #    required = False,
376    #    readonly = True,
377    #    )
[5753]378    firstname = schema.TextLine(
[7708]379        title = _(u'First Name'),
[6352]380        required = True,
[5753]381        )
[7356]382    middlename = schema.TextLine(
[7708]383        title = _(u'Middle Name'),
[5753]384        required = False,
385        )
386    lastname = schema.TextLine(
[7708]387        title = _(u'Last Name (Surname)'),
[6352]388        required = True,
[5753]389        )
[8149]390    date_of_birth = FormattedDate(
[7708]391        title = _(u'Date of Birth'),
[8540]392        required = False,
[8154]393        #date_format = u'%d/%m/%Y', # Use grok-instance-wide default
[8149]394        show_year = True,
[5753]395        )
396    sex = schema.Choice(
[7708]397        title = _(u'Sex'),
[5753]398        source = GenderSource(),
[6352]399        required = True,
[5753]400        )
[6341]401    email = schema.ASCIILine(
[7708]402        title = _(u'Email Address'),
[8033]403        required = False,
[6343]404        constraint=validate_email,
[5753]405        )
[8176]406    phone = PhoneNumber(
[7708]407        title = _(u'Phone'),
[7331]408        description = u'',
[5753]409        required = False,
410        )
[7262]411    course1 = schema.Choice(
[7708]412        title = _(u'1st Choice Course of Study'),
[8518]413        source = AppCatCertificateSource(),
[7262]414        required = True,
415        )
416    course2 = schema.Choice(
[7708]417        title = _(u'2nd Choice Course of Study'),
[8518]418        source = AppCatCertificateSource(),
[7262]419        required = False,
420        )
[8044]421    #school_grades = schema.List(
422    #    title = _(u'School Grades'),
423    #    value_type = ResultEntryField(),
424    #    required = False,
425    #    default = [],
426    #    )
[6322]427
[8052]428    notice = schema.Text(
429        title = _(u'Notice'),
[5753]430        required = False,
431        )
[8533]432    student_id = schema.TextLine(
433        title = _(u'Student Id'),
434        required = False,
435        readonly = False,
436        )
[6248]437    course_admitted = schema.Choice(
[7708]438        title = _(u'Admitted Course of Study'),
[7347]439        source = CertificateSource(),
[5753]440        required = False,
441        )
[6302]442    locked = schema.Bool(
[7708]443        title = _(u'Form locked'),
[6302]444        default = False,
445        )
[5753]446
[8052]447class IApplicant(IApplicantBaseData):
[5753]448    """An applicant.
449
450    This is basically the applicant base data. Here we repeat the
[6195]451    fields from base data if we have to set the `required` attribute
452    to True (which is the default).
[5753]453    """
454
[8742]455    def writeLogMessage(view, comment):
[8052]456        """Adds an INFO message to the log file
457        """
458
[8014]459    def createStudent():
460        """Create a student object from applicatnt data
461        and copy applicant object.
462        """
463
[8016]464class IApplicantEdit(IApplicant):
[7867]465    """An applicant interface for editing.
[5753]466
[6339]467    Here we can repeat the fields from base data and set the
468    `required` and `readonly` attributes to True to further restrict
[7262]469    the data access. Or we can allow only certain certificates to be
470    selected by choosing the appropriate source.
471
472    We cannot omit fields here. This has to be done in the
[6339]473    respective form page.
[6195]474    """
[7262]475
[8097]476    email = schema.ASCIILine(
477        title = _(u'Email Address'),
478        required = True,
479        constraint=validate_email,
480        )
[7262]481    course1 = schema.Choice(
[7708]482        title = _(u'1st Choice Course of Study'),
[7347]483        source = AppCatCertificateSource(),
[7262]484        required = True,
485        )
486    course2 = schema.Choice(
[7708]487        title = _(u'2nd Choice Course of Study'),
[7347]488        source = AppCatCertificateSource(),
[7262]489        required = False,
490        )
[6301]491    course_admitted = schema.Choice(
[7708]492        title = _(u'Admitted Course of Study'),
[7347]493        source = CertificateSource(),
[5753]494        required = False,
[6195]495        readonly = True,
[5753]496        )
[6195]497    notice = schema.Text(
[7708]498        title = _(u'Notice'),
[5753]499        required = False,
500        readonly = True,
501        )
[5758]502
[8097]503IApplicantEdit['email'].order = IApplicantEdit[
504    'sex'].order
505
[7268]506class IApplicantUpdateByRegNo(IApplicant):
507    """Representation of an applicant.
508
[7270]509    Skip regular reg_number validation if reg_number is used for finding
[7268]510    the applicant object.
511    """
[7270]512    reg_number = schema.TextLine(
[7268]513        title = u'Registration Number',
514        required = False,
515        )
516
[8037]517class IApplicantRegisterUpdate(IApplicant):
518    """Representation of an applicant for first-time registration.
519
[8778]520    This interface is used when applicants use the registration page to
[8037]521    update their records.
522    """
523    reg_number = schema.TextLine(
524        title = u'Registration Number',
525        required = True,
526        )
527
528    firstname = schema.TextLine(
529        title = _(u'First Name'),
530        required = True,
531        )
532
533    email = schema.ASCIILine(
534        title = _(u'Email Address'),
535        required = True,
536        constraint=validate_email,
537        )
538
[7250]539class IApplicantOnlinePayment(IOnlinePayment):
540    """An applicant payment via payment gateways.
541
542    """
[8422]543
544    def doAfterApplicantPayment():
545        """Process applicant after payment was made.
546
547        """
548
[8453]549    def doAfterApplicantPaymentApproval():
550        """Process applicant after payment was approved.
551
552        """
553
[8422]554    def approveApplicantPayment():
555        """Approve payment and process applicant.
556
557        """
Note: See TracBrowser for help on using the repository browser.