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

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

Make sure not to confuse an application_category None with no
application_category at all.

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