source: main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/applicants/interfaces.py @ 9079

Last change on this file since 9079 was 9004, checked in by uli, 12 years ago

Use new base interfaces.

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