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

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

Do only allow certificates in corresponding application category as choice course of study.

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