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

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

Define two different application modes: create and update. The latter expects existing (imported) application records. The ApplicantRegistrationPage? renders different form fields and creates or updates application records depending on the selected mode.

The update registration mode is not yet secure enough. Further security features will be added.

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