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

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

Simplify logging. Adjust to students.

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