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

Last change on this file since 16343 was 16343, checked in by Henrik Bettermann, 4 years ago

Do not print 'None' on slips.

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