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

Last change on this file since 8127 was 8097, checked in by Henrik Bettermann, 13 years ago

Email address is required.

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