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

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

Implement referee report slip. Show passport picture on referee pages.

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