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

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

Some boolean values must not be required.

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