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

Last change on this file since 13082 was 13080, checked in by Henrik Bettermann, 10 years ago

Improve interfaces for documentation.

  • Property svn:keywords set to Id
File size: 19.1 KB
Line 
1## $Id: interfaces.py 13080 2015-06-20 07:55:18Z 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 (
39    AppCatSource, CertificateSource, SpecialApplicationSource)
40
41#: Maximum upload size for applicant passport photographs (in bytes)
42MAX_UPLOAD_SIZE = 1024 * 20
43
44_marker = object() # a marker different from None
45
46def year_range():
47    curr_year = datetime.now().year
48    return range(curr_year - 2, curr_year + 5)
49
50class RegNumInSource(ValidationError):
51    """Registration number exists already
52    """
53    # The docstring of ValidationErrors is used as error description
54    # by zope.formlib.
55    pass
56
57class ApplicantRegNumberSource(RegNumberSource):
58    """A source that accepts any reg number if not used already by a
59    different applicant.
60    """
61    cat_name = 'applicants_catalog'
62    field_name = 'reg_number'
63    validation_error = RegNumInSource
64    comp_field = 'applicant_id'
65
66def contextual_reg_num_source(context):
67    source = ApplicantRegNumberSource(context)
68    return source
69directlyProvides(contextual_reg_num_source, IContextSourceBinder)
70
71
72class AppCatCertificateSource(CertificateSource):
73    """An application certificate source delivers all certificates
74    which belong to a certain application_category.
75
76    This source is meant to be used with Applicants.
77
78    The application category must match the application category of
79    the context parent, normally an applicants container.
80    """
81    def contains(self, context, value):
82        context_appcat = getattr(getattr(
83            context, '__parent__', None), 'application_category', _marker)
84        if context_appcat is _marker:
85            # If the context (applicant) has no application category,
86            # then it might be not part of a container (yet), for
87            # instance during imports. We consider this correct.
88            return True
89        if value.application_category == context_appcat:
90            return True
91        return False
92
93    def getValues(self, context):
94        appcat = getattr(getattr(context, '__parent__', None),
95                         'application_category', None)
96        catalog = getUtility(ICatalog, name='certificates_catalog')
97        result = catalog.searchResults(
98            application_category=(appcat,appcat))
99        resultlist = getUtility(
100            IApplicantsUtils).filterCertificates(context, result)
101        return resultlist
102
103    def getTitle(self, context, value):
104        return getUtility(
105            IApplicantsUtils).getCertTitle(context, value)
106
107class ApplicationTypeSource(BasicContextualSourceFactory):
108    """An application type source delivers screening types defined in the
109    portal.
110    """
111    def getValues(self, context):
112        appcats_dict = getUtility(
113            IApplicantsUtils).APP_TYPES_DICT
114        return sorted(appcats_dict.keys())
115
116    def getToken(self, context, value):
117        return value
118
119    def getTitle(self, context, value):
120        appcats_dict = getUtility(
121            IApplicantsUtils).APP_TYPES_DICT
122        return appcats_dict[value][0]
123
124# Maybe FUTMinna still needs this ...
125#class ApplicationPinSource(BasicContextualSourceFactory):
126#    """An application pin source delivers PIN prefixes for application
127#    defined in the portal.
128#    """
129#    def getValues(self, context):
130#        apppins_dict = getUtility(
131#            IApplicantsUtils).APP_TYPES_DICT
132#        return sorted(appcats_dict.keys())
133#
134#    def getToken(self, context, value):
135#        return value
136#
137#    def getTitle(self, context, value):
138#        apppins_dict = getUtility(
139#            IApplicantsUtils).APP_TYPES_DICT
140#        return u"%s (%s)" % (
141#            apppins_dict[value][1],self.apppins_dict[value][0])
142
143application_modes_vocab = SimpleKofaVocabulary(
144    (_('Create Application Records'), 'create'),
145    (_('Update Application Records'), 'update'),
146    )
147
148class IApplicantsUtils(Interface):
149    """A collection of methods which are subject to customization.
150    """
151    APP_TYPES_DICT = Attribute('dict of application types')
152
153    def setPaymentDetails(container, payment):
154        """Set the payment data of an applicant.
155        """
156
157    def getApplicantsStatistics(container):
158        """Count applicants in containers.
159        """
160
161    def filterCertificates(context, resultset):
162        """Filter and sort certificates for AppCatCertificateSource.
163        """
164
165    def getCertTitle(context, value):
166        """Compose the titles in AppCatCertificateSource.
167        """
168
169class IApplicantsRoot(IKofaObject, IContainer):
170    """A container for applicants containers.
171    """
172    description_dict = Attribute('Language translation dictionary with values in HTML format')
173    local_roles = Attribute('List of local role names')
174    logger_name = Attribute('Name of the logger')
175    logger_filename = Attribute('Name of the logger file')
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
187class IApplicantsContainer(IKofaObject):
188    """An applicants container contains applicants.
189    """
190    statistics = Attribute('Applicant counts')
191    expired = Attribute('True if application has started but not ended')
192
193    description_dict = Attribute('Language translation dictionary with values in HTML format')
194    local_roles = Attribute('List of local role names')
195
196
197    code = schema.TextLine(
198        title = _(u'Code'),
199        required = True,
200        readonly = True,
201        )
202
203    title = schema.TextLine(
204        title = _(u'Title'),
205        required = True,
206        readonly = False,
207        )
208
209    prefix = schema.Choice(
210        title = _(u'Application Target'),
211        required = True,
212        source = ApplicationTypeSource(),
213        readonly = True,
214        )
215
216    year = schema.Choice(
217        title = _(u'Year of Entrance'),
218        required = True,
219        values = year_range(),
220        readonly = True,
221        )
222
223    mode = schema.Choice(
224        title = _(u'Application Mode'),
225        vocabulary = application_modes_vocab,
226        required = True,
227        )
228
229    # Maybe FUTMinna still needs this ...
230    #ac_prefix = schema.Choice(
231    #    title = u'Activation code prefix',
232    #    required = True,
233    #    default = None,
234    #    source = ApplicationPinSource(),
235    #    )
236
237    application_category = schema.Choice(
238        title = _(u'Category for the grouping of certificates'),
239        required = True,
240        source = AppCatSource(),
241        )
242
243    description = schema.Text(
244        title = _(u'Human readable description in HTML format'),
245        required = False,
246        default = u'''This text can been seen by anonymous users.
247Here we put multi-lingual information about the study courses provided, the application procedure and deadlines.
248>>de<<
249Dieser Text kann von anonymen Benutzern gelesen werden.
250Hier koennen mehrsprachige Informationen fuer Antragsteller hinterlegt werden.'''
251        )
252
253    startdate = schema.Datetime(
254        title = _(u'Application Start Date'),
255        required = False,
256        description = _('Example: ') + u'2011-12-01 18:30:00+01:00',
257        )
258
259    enddate = schema.Datetime(
260        title = _(u'Application Closing Date'),
261        required = False,
262        description = _('Example: ') + u'2011-12-31 23:59:59+01:00',
263        )
264
265    strict_deadline = schema.Bool(
266        title = _(u'Forbid additions after deadline (enddate)'),
267        required = False,
268        default = True,
269        )
270
271    application_fee = schema.Float(
272        title = _(u'Application Fee'),
273        default = 0.0,
274        required = False,
275        )
276
277    application_slip_notice = schema.Text(
278        title = _(u'Human readable notice on application slip in HTML format'),
279        required = False,
280        )
281
282
283    hidden= schema.Bool(
284        title = _(u'Hide container'),
285        required = False,
286        default = False,
287        )
288
289    def addApplicant(applicant):
290        """Add an applicant.
291        """
292
293    def writeLogMessage(view, comment):
294        """Add an INFO message to the log file.
295        """
296
297    def traverse(name):
298        """Deliver appropriate containers.
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(IApplicantBase):
324    """This is a base interface of an applicant with no field
325    required. For use with processors, forms, etc., please use one of
326    the derived interfaces below, which set more fields to required
327    state, depending on use-case.
328    """
329    state = Attribute('Application state of an applicant')
330    history = Attribute('Object history, a list of messages')
331    display_fullname = Attribute('The fullname of an applicant')
332    application_number = Attribute('The key under which the record is stored')
333    container_code = Attribute('Code of the parent container')
334    translated_state = Attribute('Real name of the application state')
335    special = Attribute('True if special application')
336    application_number = Attribute('The key under which the record is stored')
337
338    application_date = Attribute('UTC datetime of submission, used for export only')
339    password = Attribute('Encrypted password of a applicant')
340
341
342    suspended = schema.Bool(
343        title = _(u'Account suspended'),
344        default = False,
345        required = False,
346        )
347
348    applicant_id = schema.TextLine(
349        title = _(u'Applicant Id'),
350        required = False,
351        readonly = False,
352        )
353
354    reg_number = TextLineChoice(
355        title = _(u'Registration Number'),
356        readonly = False,
357        required = True,
358        source = contextual_reg_num_source,
359        )
360
361    firstname = schema.TextLine(
362        title = _(u'First Name'),
363        required = True,
364        )
365
366    middlename = schema.TextLine(
367        title = _(u'Middle Name'),
368        required = False,
369        )
370
371    lastname = schema.TextLine(
372        title = _(u'Last Name (Surname)'),
373        required = True,
374        )
375
376    date_of_birth = FormattedDate(
377        title = _(u'Date of Birth'),
378        required = False,
379        show_year = True,
380        )
381
382    sex = schema.Choice(
383        title = _(u'Sex'),
384        source = GenderSource(),
385        required = True,
386        )
387
388    email = schema.ASCIILine(
389        title = _(u'Email Address'),
390        required = False,
391        constraint=validate_email,
392        )
393
394    phone = PhoneNumber(
395        title = _(u'Phone'),
396        description = u'',
397        required = False,
398        )
399
400    course1 = schema.Choice(
401        title = _(u'1st Choice Course of Study'),
402        source = AppCatCertificateSource(),
403        required = True,
404        )
405
406    course2 = schema.Choice(
407        title = _(u'2nd Choice Course of Study'),
408        source = AppCatCertificateSource(),
409        required = False,
410        )
411
412    #school_grades = schema.List(
413    #    title = _(u'School Grades'),
414    #    value_type = ResultEntryField(),
415    #    required = False,
416    #    default = [],
417    #    )
418
419    notice = schema.Text(
420        title = _(u'Notice'),
421        required = False,
422        )
423    student_id = schema.TextLine(
424        title = _(u'Student Id'),
425        required = False,
426        readonly = False,
427        )
428    course_admitted = schema.Choice(
429        title = _(u'Admitted Course of Study'),
430        source = CertificateSource(),
431        required = False,
432        )
433    locked = schema.Bool(
434        title = _(u'Form locked'),
435        default = False,
436        required = False,
437        )
438
439    special_application = schema.Choice(
440        title = _(u'Special Application'),
441        source = SpecialApplicationSource(),
442        required = False,
443        )
444
445class IApplicant(IApplicantBaseData):
446    """This is basically the applicant base data. Here we repeat the
447    fields from base data if we have to set the `required` attribute
448    to True (which is the default).
449    """
450
451    def writeLogMessage(view, comment):
452        """Adds an INFO message to the log file
453        """
454
455    def createStudent():
456        """Create a student object from applicatnt data
457        and copy applicant object.
458        """
459
460class ISpecialApplicant(IApplicantBase):
461    """This reduced interface is for former students or students who are not
462    student users of the portal but have to pay supplementary fees.
463
464    This interface is used in browser components only. Thus we can't add
465    fields here to the regular IApplicant interface here. We can
466    only 'customize' fields.
467    """
468    history = Attribute('Object history, a list of messages')
469    state = Attribute('The application state of an applicant')
470    display_fullname = Attribute('The fullname of an applicant')
471    application_date = Attribute('UTC datetime of submission, used for export only')
472    password = Attribute('Encrypted password of a applicant')
473    application_number = Attribute('The key under which the record is stored')
474
475    suspended = schema.Bool(
476        title = _(u'Account suspended'),
477        default = False,
478        required = False,
479        )
480
481    locked = schema.Bool(
482        title = _(u'Form locked'),
483        default = False,
484        required = False,
485        )
486
487    applicant_id = schema.TextLine(
488        title = _(u'Applicant Id'),
489        required = False,
490        readonly = False,
491        )
492
493    firstname = schema.TextLine(
494        title = _(u'First Name'),
495        required = True,
496        )
497
498    middlename = schema.TextLine(
499        title = _(u'Middle Name'),
500        required = False,
501        )
502
503    lastname = schema.TextLine(
504        title = _(u'Last Name (Surname)'),
505        required = True,
506        )
507
508    reg_number = TextLineChoice(
509        title = _(u'Identification Number'),
510        description = u'Enter either registration or matriculation number.',
511        readonly = False,
512        required = True,
513        source = contextual_reg_num_source,
514        )
515
516    date_of_birth = FormattedDate(
517        title = _(u'Date of Birth'),
518        required = False,
519        #date_format = u'%d/%m/%Y', # Use grok-instance-wide default
520        show_year = True,
521        )
522
523    email = schema.ASCIILine(
524        title = _(u'Email Address'),
525        required = True,
526        constraint=validate_email,
527        )
528
529    phone = PhoneNumber(
530        title = _(u'Phone'),
531        description = u'',
532        required = False,
533        )
534
535    special_application = schema.Choice(
536        title = _(u'Special Application'),
537        source = SpecialApplicationSource(),
538        required = True,
539        )
540
541class IApplicantEdit(IApplicant):
542    """This is an applicant interface for editing.
543
544    Here we can repeat the fields from base data and set the
545    `required` and `readonly` attributes to True to further restrict
546    the data access. Or we can allow only certain certificates to be
547    selected by choosing the appropriate source.
548
549    We cannot omit fields here. This has to be done in the
550    respective form page.
551    """
552
553    email = schema.ASCIILine(
554        title = _(u'Email Address'),
555        required = True,
556        constraint=validate_email,
557        )
558
559    course1 = schema.Choice(
560        title = _(u'1st Choice Course of Study'),
561        source = AppCatCertificateSource(),
562        required = True,
563        )
564
565    course2 = schema.Choice(
566        title = _(u'2nd Choice Course of Study'),
567        source = AppCatCertificateSource(),
568        required = False,
569        )
570
571    course_admitted = schema.Choice(
572        title = _(u'Admitted Course of Study'),
573        source = CertificateSource(),
574        required = False,
575        readonly = True,
576        )
577
578    notice = schema.Text(
579        title = _(u'Notice'),
580        required = False,
581        readonly = True,
582        )
583
584IApplicantEdit['email'].order = IApplicantEdit[
585    'sex'].order
586
587class IApplicantUpdateByRegNo(IApplicant):
588    """Skip regular reg_number validation if reg_number is used for finding
589    the applicant object.
590    """
591    reg_number = schema.TextLine(
592        title = u'Registration Number',
593        required = False,
594        )
595
596class IApplicantRegisterUpdate(IApplicant):
597    """This is a representation of an applicant for first-time registration.
598    This interface is used when applicants use the registration page to
599    update their records.
600    """
601    reg_number = schema.TextLine(
602        title = u'Registration Number',
603        required = True,
604        )
605
606    #firstname = schema.TextLine(
607    #    title = _(u'First Name'),
608    #    required = True,
609    #    )
610
611    lastname = schema.TextLine(
612        title = _(u'Last Name (Surname)'),
613        required = True,
614        )
615
616    email = schema.ASCIILine(
617        title = _(u'Email Address'),
618        required = True,
619        constraint=validate_email,
620        )
621
622class IApplicantOnlinePayment(IOnlinePayment):
623    """An applicant payment via payment gateways.
624
625    """
626
627    def doAfterApplicantPayment():
628        """Process applicant after payment was made.
629        """
630
631    def doAfterApplicantPaymentApproval():
632        """Process applicant after payment was approved.
633        """
634
635    def approveApplicantPayment():
636        """Approve payment and process applicant.
637        """
Note: See TracBrowser for help on using the repository browser.