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

Last change on this file since 13132 was 13104, checked in by Henrik Bettermann, 9 years ago

More docs.

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