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

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

1..99

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