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

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

Add application_slip_notice field to ApplicantsContainer?.

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