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

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

The year_range function was only used in applicants. Extend academic session range properly.

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