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

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

Use new base interfaces.

  • Property svn:keywords set to Id
File size: 16.7 KB
Line 
1## $Id: interfaces.py 9004 2012-07-15 01:31:47Z uli $
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 grokcore.content.interfaces import IContainer
21from zc.sourcefactory.contextual import BasicContextualSourceFactory
22from zope import schema
23from zope.component import queryUtility, getUtility
24from zope.catalog.interfaces import ICatalog
25from zope.interface import Interface, Attribute, implements, directlyProvides
26from zope.schema.interfaces import (
27    ValidationError, ISource, IContextSourceBinder)
28from waeup.kofa.browser.interfaces import IApplicantBase
29from waeup.kofa.schema import TextLineChoice, FormattedDate
30from waeup.kofa.interfaces import (
31    IKofaObject, year_range, 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
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
51class ApplicantRegNumberSource(RegNumberSource):
52    """A source that accepts any reg number if not used already by a
53    different applicant.
54    """
55    cat_name = 'applicants_catalog'
56    field_name = 'reg_number'
57    validation_error = RegNumInSource
58    comp_field = 'applicant_id'
59
60def contextual_reg_num_source(context):
61    source = ApplicantRegNumberSource(context)
62    return source
63directlyProvides(contextual_reg_num_source, IContextSourceBinder)
64
65
66class AppCatCertificateSource(CertificateSource):
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.
74    """
75    def contains(self, context, value):
76        context_appcat = getattr(getattr(
77            context, '__parent__', None), 'application_category', _marker)
78        if context_appcat is _marker:
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
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)
97        # XXX: What does the following mean here?
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):
110        appcats_dict = getUtility(
111            IApplicantsUtils).APP_TYPES_DICT
112        return sorted(appcats_dict.keys())
113
114    def getToken(self, context, value):
115        return value
116
117    def getTitle(self, context, value):
118        appcats_dict = getUtility(
119            IApplicantsUtils).APP_TYPES_DICT
120        return appcats_dict[value][0]
121
122# Maybe FUTMinna still needs this ...
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):
128#        apppins_dict = getUtility(
129#            IApplicantsUtils).APP_TYPES_DICT
130#        return sorted(appcats_dict.keys())
131#
132#    def getToken(self, context, value):
133#        return value
134#
135#    def getTitle(self, context, value):
136#        apppins_dict = getUtility(
137#            IApplicantsUtils).APP_TYPES_DICT
138#        return u"%s (%s)" % (
139#            apppins_dict[value][1],self.apppins_dict[value][0])
140
141application_modes_vocab = SimpleKofaVocabulary(
142    (_('Create Application Records'), 'create'),
143    (_('Update Application Records'), 'update'),
144    )
145
146class IApplicantsUtils(Interface):
147    """A collection of methods which are subject to customization.
148    """
149
150    APP_TYPES_DICT = Attribute(' dict of application types')
151
152class IApplicantsRoot(IKofaObject, IContainer):
153    """A container for university applicants containers.
154    """
155
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
169class IApplicantsContainer(IKofaObject):
170    """An applicants container contains university applicants.
171
172    """
173
174    code = schema.TextLine(
175        title = _(u'Code'),
176        required = True,
177        readonly = True,
178        )
179
180    title = schema.TextLine(
181        title = _(u'Title'),
182        required = True,
183        readonly = False,
184        )
185
186    prefix = schema.Choice(
187        title = _(u'Application Target'),
188        required = True,
189        source = ApplicationTypeSource(),
190        readonly = True,
191        )
192
193    year = schema.Choice(
194        title = _(u'Year of Entrance'),
195        required = True,
196        values = year_range(),
197        readonly = True,
198        )
199
200    mode = schema.Choice(
201        title = _(u'Application Mode'),
202        vocabulary = application_modes_vocab,
203        required = True,
204        )
205
206    # Maybe FUTMinna still needs this ...
207    #ac_prefix = schema.Choice(
208    #    title = u'Activation code prefix',
209    #    required = True,
210    #    default = None,
211    #    source = ApplicationPinSource(),
212    #    )
213
214    application_category = schema.Choice(
215        title = _(u'Category for the grouping of certificates'),
216        required = True,
217        source = AppCatSource(),
218        )
219
220    description = schema.Text(
221        title = _(u'Human readable description in HTML format'),
222        required = False,
223        default = u'''This text can been seen by anonymous users.
224Here we put multi-lingual information about the study courses provided, the application procedure and deadlines.
225>>de<<
226Dieser Text kann von anonymen Benutzern gelesen werden.
227Hier koennen mehrsprachige Informationen fuer Antragsteller hinterlegt werden.'''
228        )
229
230    description_dict = Attribute(
231        """Content as language dictionary with values in HTML format.""")
232
233    startdate = schema.Datetime(
234        title = _(u'Application Start Date'),
235        required = False,
236        description = _('Example:') + u'2011-12-01 18:30:00+01:00',
237        )
238
239    enddate = schema.Datetime(
240        title = _(u'Application Closing Date'),
241        required = False,
242        description = _('Example:') + u'2011-12-31 23:59:59+01:00',
243        )
244
245    strict_deadline = schema.Bool(
246        title = _(u'Forbid additions after deadline (enddate)'),
247        required = False,
248        default = True,
249        )
250
251    application_fee = schema.Float(
252        title = _(u'Application Fee'),
253        default = 0.0,
254        required = False,
255        )
256
257    def archive(id=None):
258        """Create on-dist archive of applicants stored in this term.
259
260        If id is `None`, all applicants are archived.
261
262        If id contains a single id string, only the respective
263        applicants are archived.
264
265        If id contains a list of id strings all of the respective
266        applicants types are saved to disk.
267        """
268
269    def clear(id=None, archive=True):
270        """Remove applicants of type given by 'id'.
271
272        Optionally archive the applicants.
273
274        If id is `None`, all applicants are archived.
275
276        If id contains a single id string, only the respective
277        applicants are archived.
278
279        If id contains a list of id strings all of the respective
280        applicant types are saved to disk.
281
282        If `archive` is ``False`` none of the archive-handling is done
283        and respective applicants are simply removed from the
284        database.
285        """
286
287class IApplicantsContainerAdd(IApplicantsContainer):
288    """An applicants container contains university applicants.
289    """
290    prefix = schema.Choice(
291        title = _(u'Application Target'),
292        required = True,
293        source = ApplicationTypeSource(),
294        readonly = False,
295        )
296
297    year = schema.Choice(
298        title = _(u'Year of Entrance'),
299        required = True,
300        values = year_range(),
301        readonly = False,
302        )
303
304IApplicantsContainerAdd[
305    'prefix'].order =  IApplicantsContainer['prefix'].order
306IApplicantsContainerAdd[
307    'year'].order =  IApplicantsContainer['year'].order
308
309class IApplicantBaseData(IApplicantBase):
310    """The data for an applicant.
311
312    This is a base interface with no field
313    required. For use with processors, forms, etc., please use one of
314    the derived interfaces below, which set more fields to required
315    state, depending on use-case.
316    """
317
318    history = Attribute('Object history, a list of messages')
319    state = Attribute('The application state of an applicant')
320    display_fullname = Attribute('The fullname of an applicant')
321    application_date = Attribute('UTC datetime of submission, used for export only')
322    password = Attribute('Encrypted password of a applicant')
323    application_number = Attribute('The key under which the record is stored')
324
325    applicant_id = schema.TextLine(
326        title = _(u'Applicant Id'),
327        required = False,
328        readonly = False,
329        )
330    reg_number = TextLineChoice(
331        title = _(u'Registration Number'),
332        readonly = False,
333        required = True,
334        source = contextual_reg_num_source,
335        )
336    #access_code = schema.TextLine(
337    #    title = u'Activation Code',
338    #    required = False,
339    #    readonly = True,
340    #    )
341    firstname = schema.TextLine(
342        title = _(u'First Name'),
343        required = True,
344        )
345    middlename = schema.TextLine(
346        title = _(u'Middle Name'),
347        required = False,
348        )
349    lastname = schema.TextLine(
350        title = _(u'Last Name (Surname)'),
351        required = True,
352        )
353    date_of_birth = FormattedDate(
354        title = _(u'Date of Birth'),
355        required = False,
356        #date_format = u'%d/%m/%Y', # Use grok-instance-wide default
357        show_year = True,
358        )
359    sex = schema.Choice(
360        title = _(u'Sex'),
361        source = GenderSource(),
362        required = True,
363        )
364    email = schema.ASCIILine(
365        title = _(u'Email Address'),
366        required = False,
367        constraint=validate_email,
368        )
369    phone = PhoneNumber(
370        title = _(u'Phone'),
371        description = u'',
372        required = False,
373        )
374    course1 = schema.Choice(
375        title = _(u'1st Choice Course of Study'),
376        source = AppCatCertificateSource(),
377        required = True,
378        )
379    course2 = schema.Choice(
380        title = _(u'2nd Choice Course of Study'),
381        source = AppCatCertificateSource(),
382        required = False,
383        )
384    #school_grades = schema.List(
385    #    title = _(u'School Grades'),
386    #    value_type = ResultEntryField(),
387    #    required = False,
388    #    default = [],
389    #    )
390
391    notice = schema.Text(
392        title = _(u'Notice'),
393        required = False,
394        )
395    screening_venue = schema.TextLine(
396        title = _(u'Screening Venue'),
397        required = False,
398        )
399    screening_score = schema.Int(
400        title = _(u'Screening Score'),
401        required = False,
402        )
403    student_id = schema.TextLine(
404        title = _(u'Student Id'),
405        required = False,
406        readonly = False,
407        )
408    course_admitted = schema.Choice(
409        title = _(u'Admitted Course of Study'),
410        source = CertificateSource(),
411        required = False,
412        )
413    locked = schema.Bool(
414        title = _(u'Form locked'),
415        default = False,
416        )
417
418class IApplicant(IApplicantBaseData):
419    """An applicant.
420
421    This is basically the applicant base data. Here we repeat the
422    fields from base data if we have to set the `required` attribute
423    to True (which is the default).
424    """
425
426    def writeLogMessage(view, comment):
427        """Adds an INFO message to the log file
428        """
429
430    def createStudent():
431        """Create a student object from applicatnt data
432        and copy applicant object.
433        """
434
435class IApplicantEdit(IApplicant):
436    """An applicant interface for editing.
437
438    Here we can repeat the fields from base data and set the
439    `required` and `readonly` attributes to True to further restrict
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
444    respective form page.
445    """
446
447    email = schema.ASCIILine(
448        title = _(u'Email Address'),
449        required = True,
450        constraint=validate_email,
451        )
452    course1 = schema.Choice(
453        title = _(u'1st Choice Course of Study'),
454        source = AppCatCertificateSource(),
455        required = True,
456        )
457    course2 = schema.Choice(
458        title = _(u'2nd Choice Course of Study'),
459        source = AppCatCertificateSource(),
460        required = False,
461        )
462    screening_score = schema.Int(
463        title = _(u'Screening Score'),
464        required = False,
465        readonly = True,
466        )
467    screening_venue = schema.TextLine(
468        title = _(u'Screening Venue'),
469        required = False,
470        readonly = True,
471        )
472    course_admitted = schema.Choice(
473        title = _(u'Admitted Course of Study'),
474        source = CertificateSource(),
475        required = False,
476        readonly = True,
477        )
478    notice = schema.Text(
479        title = _(u'Notice'),
480        required = False,
481        readonly = True,
482        )
483
484IApplicantEdit['email'].order = IApplicantEdit[
485    'sex'].order
486
487class IApplicantUpdateByRegNo(IApplicant):
488    """Representation of an applicant.
489
490    Skip regular reg_number validation if reg_number is used for finding
491    the applicant object.
492    """
493    reg_number = schema.TextLine(
494        title = u'Registration Number',
495        required = False,
496        )
497
498class IApplicantRegisterUpdate(IApplicant):
499    """Representation of an applicant for first-time registration.
500
501    This interface is used when applicants use the registration page to
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
520class IApplicantOnlinePayment(IOnlinePayment):
521    """An applicant payment via payment gateways.
522
523    """
524
525    def doAfterApplicantPayment():
526        """Process applicant after payment was made.
527
528        """
529
530    def doAfterApplicantPaymentApproval():
531        """Process applicant after payment was approved.
532
533        """
534
535    def approveApplicantPayment():
536        """Approve payment and process applicant.
537
538        """
Note: See TracBrowser for help on using the repository browser.