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

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

Add new application workflow state (processed) and transition (process).

  • Property svn:keywords set to Id
File size: 18.7 KB
Line 
1## $Id: interfaces.py 13651 2016-02-05 08:48:02Z 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, validate_html,
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 * 50
42
43_marker = object() # a marker different from None
44
45def year_range():
46    curr_year = datetime.now().year
47    return range(curr_year - 4, 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).sortCertificates(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        constraint=validate_html,
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        constraint=validate_html,
247        default = u'''This text can been seen by anonymous users.
248Here we put multi-lingual information about the study courses provided, the application procedure and deadlines.
249>>de<<
250Dieser Text kann von anonymen Benutzern gelesen werden.
251Hier koennen mehrsprachige Informationen fuer Antragsteller hinterlegt werden.'''
252        )
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    application_slip_notice = schema.Text(
279        title = _(u'Human readable notice on application slip in HTML format'),
280        required = False,
281        constraint=validate_html,
282        )
283
284    hidden= schema.Bool(
285        title = _(u'Hide container'),
286        required = False,
287        default = False,
288        )
289
290    def addApplicant(applicant):
291        """Add an applicant.
292        """
293
294    def writeLogMessage(view, comment):
295        """Add an INFO message to applicants.log.
296        """
297
298    def traverse(name):
299        """Deliver appropriate containers.
300        """
301
302class IApplicantsContainerAdd(IApplicantsContainer):
303    """An applicants container contains university applicants.
304    """
305    prefix = schema.Choice(
306        title = _(u'Application Target'),
307        required = True,
308        source = ApplicationTypeSource(),
309        readonly = False,
310        )
311
312    year = schema.Choice(
313        title = _(u'Year of Entrance'),
314        required = True,
315        values = year_range(),
316        readonly = False,
317        )
318
319IApplicantsContainerAdd[
320    'prefix'].order =  IApplicantsContainer['prefix'].order
321IApplicantsContainerAdd[
322    'year'].order =  IApplicantsContainer['year'].order
323
324class IApplicantBaseData(IKofaObject):
325    """This is a base interface of an applicant with no field
326    required. For use with processors, forms, etc., please use one of
327    the derived interfaces below, which set more fields to required
328    state, depending on use-case.
329    """
330    state = Attribute('Application state of an applicant')
331    history = Attribute('Object history, a list of messages')
332    display_fullname = Attribute('The fullname of an applicant')
333    application_number = Attribute('The key under which the record is stored')
334    container_code = Attribute('Code of the parent container plus additional information if record is used or not')
335    translated_state = Attribute('Real name of the application state')
336    special = Attribute('True if special application')
337
338    application_date = Attribute('UTC datetime of submission, used for export only')
339    password = Attribute('Encrypted password of an 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        """Add an INFO message to applicants.log.
453        """
454
455    def createStudent():
456        """Create a student object from applicant data and copy
457        passport image and application slip.
458        """
459
460class ISpecialApplicant(IKofaObject):
461    """This reduced interface is for former students or students who are not
462    users of the portal but have to pay supplementary fees.
463    This interface is used in browser components only. Thus we can't add
464    fields here to the regular IApplicant interface here. We can
465    only 'customize' fields.
466    """
467
468    suspended = schema.Bool(
469        title = _(u'Account suspended'),
470        default = False,
471        required = False,
472        )
473
474    locked = schema.Bool(
475        title = _(u'Form locked'),
476        default = False,
477        required = False,
478        )
479
480    applicant_id = schema.TextLine(
481        title = _(u'Applicant Id'),
482        required = False,
483        readonly = False,
484        )
485
486    firstname = schema.TextLine(
487        title = _(u'First Name'),
488        required = True,
489        )
490
491    middlename = schema.TextLine(
492        title = _(u'Middle Name'),
493        required = False,
494        )
495
496    lastname = schema.TextLine(
497        title = _(u'Last Name (Surname)'),
498        required = True,
499        )
500
501    reg_number = TextLineChoice(
502        title = _(u'Identification Number'),
503        description = u'Enter either registration or matriculation number.',
504        readonly = False,
505        required = True,
506        source = contextual_reg_num_source,
507        )
508
509    date_of_birth = FormattedDate(
510        title = _(u'Date of Birth'),
511        required = False,
512        #date_format = u'%d/%m/%Y', # Use grok-instance-wide default
513        show_year = True,
514        )
515
516    email = schema.ASCIILine(
517        title = _(u'Email Address'),
518        required = True,
519        constraint=validate_email,
520        )
521
522    phone = PhoneNumber(
523        title = _(u'Phone'),
524        description = u'',
525        required = False,
526        )
527
528    special_application = schema.Choice(
529        title = _(u'Special Application'),
530        source = SpecialApplicationSource(),
531        required = True,
532        )
533
534class IApplicantEdit(IApplicant):
535    """This is an applicant interface for editing.
536
537    Here we can repeat the fields from base data and set the
538    `required` and `readonly` attributes to True to further restrict
539    the data access. Or we can allow only certain certificates to be
540    selected by choosing the appropriate source.
541
542    We cannot omit fields here. This has to be done in the
543    respective form page.
544    """
545
546    email = schema.ASCIILine(
547        title = _(u'Email Address'),
548        required = True,
549        constraint=validate_email,
550        )
551
552    course1 = schema.Choice(
553        title = _(u'1st Choice Course of Study'),
554        source = AppCatCertificateSource(),
555        required = True,
556        )
557
558    course2 = schema.Choice(
559        title = _(u'2nd Choice Course of Study'),
560        source = AppCatCertificateSource(),
561        required = False,
562        )
563
564    course_admitted = schema.Choice(
565        title = _(u'Admitted Course of Study'),
566        source = CertificateSource(),
567        required = False,
568        readonly = True,
569        )
570
571    notice = schema.Text(
572        title = _(u'Notice'),
573        required = False,
574        readonly = True,
575        )
576
577IApplicantEdit['email'].order = IApplicantEdit['sex'].order
578
579class IApplicantUpdateByRegNo(IApplicant):
580    """Skip regular reg_number validation if reg_number is used for finding
581    the applicant object.
582    """
583    reg_number = schema.TextLine(
584        title = u'Registration Number',
585        required = False,
586        )
587
588class IApplicantRegisterUpdate(IApplicant):
589    """This is a representation of an applicant for first-time registration.
590    This interface is used when applicants use the registration page to
591    update their records.
592    """
593    reg_number = schema.TextLine(
594        title = u'Registration Number',
595        required = True,
596        )
597
598    #firstname = schema.TextLine(
599    #    title = _(u'First Name'),
600    #    required = True,
601    #    )
602
603    lastname = schema.TextLine(
604        title = _(u'Last Name (Surname)'),
605        required = True,
606        )
607
608    email = schema.ASCIILine(
609        title = _(u'Email Address'),
610        required = True,
611        constraint=validate_email,
612        )
613
614class IApplicantOnlinePayment(IOnlinePayment):
615    """An applicant payment via payment gateways.
616    """
617
618    def doAfterApplicantPayment():
619        """Process applicant after payment was made.
620        """
621
622    def doAfterApplicantPaymentApproval():
623        """Process applicant after payment was approved.
624        """
625
626    def approveApplicantPayment():
627        """Approve payment and process applicant.
628        """
Note: See TracBrowser for help on using the repository browser.