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

Last change on this file since 10131 was 10098, checked in by Henrik Bettermann, 12 years ago

Show containers by default. Only anonymous users must not see hidden containers.

  • Property svn:keywords set to Id
File size: 16.7 KB
RevLine 
[5638]1## $Id: interfaces.py 10098 2013-04-24 05:52:18Z 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        # appliction category not available when certificate was deleted.
94        # shouldn't that info be part of applicant info instead?
95        # when we cannot determine the appcat, we will display all courses.
96        appcat = getattr(getattr(context, '__parent__', None),
97                         'application_category', None)
98        catalog = getUtility(ICatalog, name='certificates_catalog')
99        result = catalog.searchResults(
100            application_category=(appcat,appcat))
101        result = sorted(result, key=lambda value: value.code)
[8607]102        # XXX: What does the following mean here?
[7683]103        curr_course = context.course1
104        if curr_course is not None and curr_course not in result:
105            # display also current course even if it is not catalogued
106            # (any more)
107            result = [curr_course,] + result
108        return result
109
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
[7844]155    APP_TYPES_DICT = Attribute(' dict of application types')
156
[7819]157class IApplicantsRoot(IKofaObject, IContainer):
[5676]158    """A container for university applicants containers.
[5645]159    """
[5638]160
[8388]161    description = schema.Text(
162        title = _(u'Human readable description in HTML format'),
163        required = False,
164        default = u'''This text can been seen by anonymous users.
165Here we put multi-lingual general information about the application procedure.
166>>de<<
167Dieser Text kann von anonymen Benutzern gelesen werden.
168Hier koennen mehrsprachige Informationen fuer Antragsteller hinterlegt werden.'''
169        )
170
171    description_dict = Attribute(
172        """Content as language dictionary with values in HTML format.""")
173
[7819]174class IApplicantsContainer(IKofaObject):
[5676]175    """An applicants container contains university applicants.
[5645]176
[5638]177    """
[6069]178
179    code = schema.TextLine(
[7708]180        title = _(u'Code'),
[6069]181        required = True,
182        readonly = True,
[6076]183        )
[6096]184
[6087]185    title = schema.TextLine(
[7708]186        title = _(u'Title'),
[6087]187        required = True,
[8562]188        readonly = False,
[6096]189        )
190
[6087]191    prefix = schema.Choice(
[7708]192        title = _(u'Application Target'),
[6087]193        required = True,
[7683]194        source = ApplicationTypeSource(),
[6087]195        readonly = True,
196        )
[6076]197
[6087]198    year = schema.Choice(
[7708]199        title = _(u'Year of Entrance'),
[6087]200        required = True,
[6158]201        values = year_range(),
[6087]202        readonly = True,
[6096]203        )
[6087]204
[8033]205    mode = schema.Choice(
206        title = _(u'Application Mode'),
207        vocabulary = application_modes_vocab,
208        required = True,
209        )
210
[8773]211    # Maybe FUTMinna still needs this ...
[7376]212    #ac_prefix = schema.Choice(
213    #    title = u'Activation code prefix',
214    #    required = True,
215    #    default = None,
[7683]216    #    source = ApplicationPinSource(),
[7376]217    #    )
[6076]218
[6189]219    application_category = schema.Choice(
[7708]220        title = _(u'Category for the grouping of certificates'),
[6189]221        required = True,
[7681]222        source = AppCatSource(),
[6189]223        )
224
[5645]225    description = schema.Text(
[8365]226        title = _(u'Human readable description in HTML format'),
[5638]227        required = False,
[6518]228        default = u'''This text can been seen by anonymous users.
[8033]229Here we put multi-lingual information about the study courses provided, the application procedure and deadlines.
[7708]230>>de<<
231Dieser Text kann von anonymen Benutzern gelesen werden.
232Hier koennen mehrsprachige Informationen fuer Antragsteller hinterlegt werden.'''
[5638]233        )
234
[7903]235    description_dict = Attribute(
236        """Content as language dictionary with values in HTML format.""")
[7708]237
[8200]238    startdate = schema.Datetime(
[7708]239        title = _(u'Application Start Date'),
[5638]240        required = False,
[8203]241        description = _('Example:') + u'2011-12-01 18:30:00+01:00',
[5638]242        )
243
[8200]244    enddate = schema.Datetime(
[7708]245        title = _(u'Application Closing Date'),
[5638]246        required = False,
[8203]247        description = _('Example:') + u'2011-12-31 23:59:59+01:00',
[5638]248        )
249
[5645]250    strict_deadline = schema.Bool(
[7708]251        title = _(u'Forbid additions after deadline (enddate)'),
[7984]252        required = False,
[5645]253        default = True,
254        )
[5638]255
[8525]256    application_fee = schema.Float(
257        title = _(u'Application Fee'),
258        default = 0.0,
259        required = False,
260        )
261
[10097]262    hidden= schema.Bool(
263        title = _(u'Hide container'),
264        required = False,
[10098]265        default = False,
[10097]266        )
267
[5638]268    def archive(id=None):
[5676]269        """Create on-dist archive of applicants stored in this term.
[5638]270
[5676]271        If id is `None`, all applicants are archived.
[5638]272
273        If id contains a single id string, only the respective
[5676]274        applicants are archived.
[5638]275
276        If id contains a list of id strings all of the respective
[5676]277        applicants types are saved to disk.
[5638]278        """
279
280    def clear(id=None, archive=True):
[5676]281        """Remove applicants of type given by 'id'.
[5638]282
[5676]283        Optionally archive the applicants.
[6076]284
[5676]285        If id is `None`, all applicants are archived.
[5638]286
287        If id contains a single id string, only the respective
[5676]288        applicants are archived.
[5638]289
290        If id contains a list of id strings all of the respective
[5676]291        applicant types are saved to disk.
[5638]292
293        If `archive` is ``False`` none of the archive-handling is done
[5676]294        and respective applicants are simply removed from the
[5638]295        database.
296        """
[6073]297
[9531]298    def writeLogMessage(view, comment):
299        """Adds an INFO message to the log file
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
[9217]324class IApplicantBaseData(IApplicantBase):
[5753]325    """The data for an applicant.
326
[7240]327    This is a base interface 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    """
[8052]332
333    history = Attribute('Object history, a list of messages')
[8286]334    state = Attribute('The application state of an applicant')
[8052]335    display_fullname = Attribute('The fullname of an applicant')
[8589]336    application_date = Attribute('UTC datetime of submission, used for export only')
[8052]337    password = Attribute('Encrypted password of a applicant')
338    application_number = Attribute('The key under which the record is stored')
339
[8983]340    suspended = schema.Bool(
341        title = _(u'Account suspended'),
342        default = False,
[9035]343        required = False,
[8983]344        )
345
[7240]346    applicant_id = schema.TextLine(
[7708]347        title = _(u'Applicant Id'),
[7240]348        required = False,
[7260]349        readonly = False,
[7240]350        )
[7270]351    reg_number = TextLineChoice(
[8033]352        title = _(u'Registration Number'),
[7263]353        readonly = False,
354        required = True,
355        source = contextual_reg_num_source,
[5753]356        )
[7376]357    #access_code = schema.TextLine(
358    #    title = u'Activation Code',
359    #    required = False,
360    #    readonly = True,
361    #    )
[5753]362    firstname = schema.TextLine(
[7708]363        title = _(u'First Name'),
[6352]364        required = True,
[5753]365        )
[7356]366    middlename = schema.TextLine(
[7708]367        title = _(u'Middle Name'),
[5753]368        required = False,
369        )
370    lastname = schema.TextLine(
[7708]371        title = _(u'Last Name (Surname)'),
[6352]372        required = True,
[5753]373        )
[8149]374    date_of_birth = FormattedDate(
[7708]375        title = _(u'Date of Birth'),
[8540]376        required = False,
[8154]377        #date_format = u'%d/%m/%Y', # Use grok-instance-wide default
[8149]378        show_year = True,
[5753]379        )
380    sex = schema.Choice(
[7708]381        title = _(u'Sex'),
[5753]382        source = GenderSource(),
[6352]383        required = True,
[5753]384        )
[6341]385    email = schema.ASCIILine(
[7708]386        title = _(u'Email Address'),
[8033]387        required = False,
[6343]388        constraint=validate_email,
[5753]389        )
[8176]390    phone = PhoneNumber(
[7708]391        title = _(u'Phone'),
[7331]392        description = u'',
[5753]393        required = False,
394        )
[7262]395    course1 = schema.Choice(
[7708]396        title = _(u'1st Choice Course of Study'),
[8518]397        source = AppCatCertificateSource(),
[7262]398        required = True,
399        )
400    course2 = schema.Choice(
[7708]401        title = _(u'2nd Choice Course of Study'),
[8518]402        source = AppCatCertificateSource(),
[7262]403        required = False,
404        )
[8044]405    #school_grades = schema.List(
406    #    title = _(u'School Grades'),
407    #    value_type = ResultEntryField(),
408    #    required = False,
409    #    default = [],
410    #    )
[6322]411
[8052]412    notice = schema.Text(
413        title = _(u'Notice'),
[5753]414        required = False,
415        )
[8533]416    student_id = schema.TextLine(
417        title = _(u'Student Id'),
418        required = False,
419        readonly = False,
420        )
[6248]421    course_admitted = schema.Choice(
[7708]422        title = _(u'Admitted Course of Study'),
[7347]423        source = CertificateSource(),
[5753]424        required = False,
425        )
[6302]426    locked = schema.Bool(
[7708]427        title = _(u'Form locked'),
[6302]428        default = False,
429        )
[5753]430
[8052]431class IApplicant(IApplicantBaseData):
[5753]432    """An applicant.
433
434    This is basically the applicant base data. Here we repeat the
[6195]435    fields from base data if we have to set the `required` attribute
436    to True (which is the default).
[5753]437    """
438
[8742]439    def writeLogMessage(view, comment):
[8052]440        """Adds an INFO message to the log file
441        """
442
[8014]443    def createStudent():
444        """Create a student object from applicatnt data
445        and copy applicant object.
446        """
447
[8016]448class IApplicantEdit(IApplicant):
[7867]449    """An applicant interface for editing.
[5753]450
[6339]451    Here we can repeat the fields from base data and set the
452    `required` and `readonly` attributes to True to further restrict
[7262]453    the data access. Or we can allow only certain certificates to be
454    selected by choosing the appropriate source.
455
456    We cannot omit fields here. This has to be done in the
[6339]457    respective form page.
[6195]458    """
[7262]459
[8097]460    email = schema.ASCIILine(
461        title = _(u'Email Address'),
462        required = True,
463        constraint=validate_email,
464        )
[7262]465    course1 = schema.Choice(
[7708]466        title = _(u'1st Choice Course of Study'),
[7347]467        source = AppCatCertificateSource(),
[7262]468        required = True,
469        )
470    course2 = schema.Choice(
[7708]471        title = _(u'2nd Choice Course of Study'),
[7347]472        source = AppCatCertificateSource(),
[7262]473        required = False,
474        )
[6301]475    course_admitted = schema.Choice(
[7708]476        title = _(u'Admitted Course of Study'),
[7347]477        source = CertificateSource(),
[5753]478        required = False,
[6195]479        readonly = True,
[5753]480        )
[6195]481    notice = schema.Text(
[7708]482        title = _(u'Notice'),
[5753]483        required = False,
484        readonly = True,
485        )
[5758]486
[8097]487IApplicantEdit['email'].order = IApplicantEdit[
488    'sex'].order
489
[7268]490class IApplicantUpdateByRegNo(IApplicant):
491    """Representation of an applicant.
492
[7270]493    Skip regular reg_number validation if reg_number is used for finding
[7268]494    the applicant object.
495    """
[7270]496    reg_number = schema.TextLine(
[7268]497        title = u'Registration Number',
498        required = False,
499        )
500
[8037]501class IApplicantRegisterUpdate(IApplicant):
502    """Representation of an applicant for first-time registration.
503
[8778]504    This interface is used when applicants use the registration page to
[8037]505    update their records.
506    """
507    reg_number = schema.TextLine(
508        title = u'Registration Number',
509        required = True,
510        )
511
512    firstname = schema.TextLine(
513        title = _(u'First Name'),
514        required = True,
515        )
516
517    email = schema.ASCIILine(
518        title = _(u'Email Address'),
519        required = True,
520        constraint=validate_email,
521        )
522
[7250]523class IApplicantOnlinePayment(IOnlinePayment):
524    """An applicant payment via payment gateways.
525
526    """
[8422]527
528    def doAfterApplicantPayment():
529        """Process applicant after payment was made.
530
531        """
532
[8453]533    def doAfterApplicantPaymentApproval():
534        """Process applicant after payment was approved.
535
536        """
537
[8422]538    def approveApplicantPayment():
539        """Approve payment and process applicant.
540
541        """
Note: See TracBrowser for help on using the repository browser.