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

Last change on this file since 15539 was 15502, checked in by Henrik Bettermann, 5 years ago

Add passport picture switch to applicants containers.

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