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

Last change on this file since 10186 was 10186, checked in by Henrik Bettermann, 11 years ago

Prepare getTitle for customization.

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