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

Last change on this file since 16960 was 16746, checked in by Henrik Bettermann, 3 years ago

Increase range.

  • Property svn:keywords set to Id
File size: 21.9 KB
Line 
1## $Id: interfaces.py 16746 2022-01-10 20:27:54Z 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 - 6, 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        )
308
309    hidden= schema.Bool(
310        title = _(u'Hide container'),
311        required = False,
312        default = False,
313        )
314
315    with_picture= schema.Bool(
316        title = _(u'With passport picture'),
317        required = False,
318        default = True,
319        )
320
321    def addApplicant(applicant):
322        """Add an applicant.
323        """
324
325    def writeLogMessage(view, comment):
326        """Add an INFO message to applicants.log.
327        """
328
329    def traverse(name):
330        """Deliver appropriate containers.
331        """
332
333class IApplicantsContainerAdd(IApplicantsContainer):
334    """An applicants container contains university applicants.
335    """
336    prefix = schema.Choice(
337        title = _(u'Application Target'),
338        required = True,
339        source = ApplicationTypeSource(),
340        readonly = False,
341        )
342
343    year = schema.Choice(
344        title = _(u'Year of Entrance'),
345        required = False,
346        values = year_range(),
347        readonly = False,
348        )
349
350    container_number = schema.Choice(
351        title = _(u'Container Number'),
352        values = range(1,100),
353        description = _(u'If set, this number will be added to the container '
354                         'prefix (e.g. app3). If not set, the year of entrance will be '
355                         'used (e.g. app2019).'),
356        required = False,
357        readonly = False,
358        )
359
360IApplicantsContainerAdd[
361    'prefix'].order =  IApplicantsContainer['prefix'].order
362IApplicantsContainerAdd[
363    'year'].order =  IApplicantsContainer['year'].order
364IApplicantsContainerAdd[
365    'container_number'].order =  IApplicantsContainer['year'].order
366
367class IApplicantBaseData(IKofaObject):
368    """This is a base interface of an applicant with no field
369    required. For use with processors, forms, etc., please use one of
370    the derived interfaces below, which set more fields to required
371    state, depending on use-case.
372    """
373    state = Attribute('Application state of an applicant')
374    history = Attribute('Object history, a list of messages')
375    display_fullname = Attribute('The fullname of an applicant')
376    application_number = Attribute('The key under which the record is stored')
377    container_code = Attribute('Code of the parent container plus additional information if record is used or not')
378    translated_state = Attribute('Real name of the application state')
379    special = Attribute('True if special application')
380    payments = Attribute('List of payment objects stored in the applicant container')
381
382    application_date = Attribute('UTC datetime of submission, used for export only')
383    password = Attribute('Encrypted password of an applicant')
384
385
386    suspended = schema.Bool(
387        title = _(u'Account suspended'),
388        default = False,
389        required = False,
390        )
391
392    applicant_id = schema.TextLine(
393        title = _(u'Applicant Id'),
394        required = False,
395        readonly = False,
396        )
397
398    reg_number = TextLineChoice(
399        title = _(u'Registration Number'),
400        readonly = False,
401        required = False,
402        source = contextual_reg_num_source,
403        )
404
405    firstname = schema.TextLine(
406        title = _(u'First Name'),
407        required = True,
408        )
409
410    middlename = schema.TextLine(
411        title = _(u'Middle Name'),
412        required = False,
413        )
414
415    lastname = schema.TextLine(
416        title = _(u'Last Name (Surname)'),
417        required = True,
418        )
419
420    date_of_birth = FormattedDate(
421        title = _(u'Date of Birth'),
422        required = False,
423        show_year = True,
424        )
425
426    sex = schema.Choice(
427        title = _(u'Gender'),
428        source = GenderSource(),
429        required = True,
430        )
431
432    email = schema.ASCIILine(
433        title = _(u'Email Address'),
434        required = False,
435        constraint=validate_email,
436        )
437
438    phone = PhoneNumber(
439        title = _(u'Phone'),
440        description = u'',
441        required = False,
442        )
443
444    course1 = schema.Choice(
445        title = _(u'1st Choice Course of Study'),
446        source = AppCatCertificateSource(),
447        required = False,
448        )
449
450    course2 = schema.Choice(
451        title = _(u'2nd Choice Course of Study'),
452        source = AppCatCertificateSource(),
453        required = False,
454        )
455
456    notice = schema.Text(
457        title = _(u'Notice'),
458        required = False,
459        )
460    student_id = schema.TextLine(
461        title = _(u'Student Id'),
462        required = False,
463        readonly = False,
464        )
465    course_admitted = schema.Choice(
466        title = _(u'Admitted Course of Study'),
467        source = CertificateSource(),
468        required = False,
469        )
470    locked = schema.Bool(
471        title = _(u'Form locked'),
472        default = False,
473        required = False,
474        )
475
476    special_application = schema.Choice(
477        title = _(u'Special Application'),
478        source = SpecialApplicationSource(),
479        required = False,
480        )
481
482class IApplicantTestData(IKofaObject):
483    """This interface is for demonstration and testing only.
484    It can be omitted in customized versions of Kofa.
485    """
486
487    school_grades = schema.List(
488        title = _(u'School Grades'),
489        value_type = ResultEntryField(),
490        required = False,
491        defaultFactory=list,
492        )
493
494    referees = schema.List(
495        title = _(u'Referees'),
496        value_type = RefereeEntryField(),
497        required = False,
498        defaultFactory=list,
499        )
500
501IApplicantTestData['school_grades'].order = IApplicantBaseData['course2'].order
502
503class IApplicant(IApplicantBaseData, IApplicantTestData):
504    """This is basically the applicant base data. Here we repeat the
505    fields from base data if we have to set the `required` attribute
506    to True (which is the default).
507    """
508
509    def writeLogMessage(view, comment):
510        """Add an INFO message to applicants.log.
511        """
512
513    def createStudent(view, graduated):
514        """Create a student object from applicant data and copy
515        passport image and application slip. If graduated is set True,
516        a graduated student is being created. This method is supposed
517        to be used for transcript applicants. It is tested but not
518        used in the base package.
519        """
520class ISpecialApplicant(IKofaObject):
521    """This reduced interface is for former students or students who are not
522    users of the portal but have to pay supplementary fees.
523    This interface is used in browser components only. Thus we can't add
524    fields here to the regular IApplicant interface here. We can
525    only 'customize' fields.
526    """
527
528    suspended = schema.Bool(
529        title = _(u'Account suspended'),
530        default = False,
531        required = False,
532        )
533
534    locked = schema.Bool(
535        title = _(u'Form locked'),
536        default = False,
537        required = False,
538        )
539
540    applicant_id = schema.TextLine(
541        title = _(u'Applicant Id'),
542        required = False,
543        readonly = False,
544        )
545
546    firstname = schema.TextLine(
547        title = _(u'First Name'),
548        required = True,
549        )
550
551    middlename = schema.TextLine(
552        title = _(u'Middle Name'),
553        required = False,
554        )
555
556    lastname = schema.TextLine(
557        title = _(u'Last Name (Surname)'),
558        required = True,
559        )
560
561    reg_number = TextLineChoice(
562        title = _(u'Identification Number'),
563        #description = _(u'Enter either registration or matriculation number.'),
564        readonly = False,
565        required = True,
566        source = contextual_reg_num_source,
567        )
568
569    date_of_birth = FormattedDate(
570        title = _(u'Date of Birth'),
571        required = False,
572        #date_format = u'%d/%m/%Y', # Use grok-instance-wide default
573        show_year = True,
574        )
575
576    email = schema.ASCIILine(
577        title = _(u'Email Address'),
578        required = True,
579        constraint=validate_email,
580        )
581
582    phone = PhoneNumber(
583        title = _(u'Phone'),
584        description = u'',
585        required = False,
586        )
587
588    special_application = schema.Choice(
589        title = _(u'Special Application'),
590        source = SpecialApplicationSource(),
591        required = True,
592        )
593
594class IApplicantEdit(IApplicant):
595    """This is an applicant interface for editing.
596
597    Here we can repeat the fields from base data and set the
598    `required` and `readonly` attributes to True to further restrict
599    the data access. Or we can allow only certain certificates to be
600    selected by choosing the appropriate source.
601
602    We cannot omit fields here. This has to be done in the
603    respective form page.
604    """
605
606    email = schema.ASCIILine(
607        title = _(u'Email Address'),
608        required = True,
609        constraint=validate_email,
610        )
611
612    course1 = schema.Choice(
613        title = _(u'1st Choice Course of Study'),
614        source = AppCatCertificateSource(),
615        required = True,
616        )
617
618    course2 = schema.Choice(
619        title = _(u'2nd Choice Course of Study'),
620        source = AppCatCertificateSource(),
621        required = False,
622        )
623
624    course_admitted = schema.Choice(
625        title = _(u'Admitted Course of Study'),
626        source = CertificateSource(),
627        required = False,
628        readonly = True,
629        )
630
631    notice = schema.Text(
632        title = _(u'Notice'),
633        required = False,
634        readonly = True,
635        )
636
637IApplicantEdit['email'].order = IApplicantEdit['sex'].order
638
639class IApplicantUpdateByRegNo(IApplicant):
640    """Skip regular reg_number validation if reg_number is used for finding
641    the applicant object.
642    """
643    reg_number = schema.TextLine(
644        title = u'Registration Number',
645        required = False,
646        )
647
648class IApplicantRegisterUpdate(IApplicant):
649    """This is a representation of an applicant for first-time registration.
650    This interface is used when applicants use the registration page to
651    update their records.
652    """
653    reg_number = schema.TextLine(
654        title = u'Registration Number',
655        required = True,
656        )
657
658    #firstname = schema.TextLine(
659    #    title = _(u'First Name'),
660    #    required = True,
661    #    )
662
663    lastname = schema.TextLine(
664        title = _(u'Last Name (Surname)'),
665        required = True,
666        )
667
668    email = schema.ASCIILine(
669        title = _(u'Email Address'),
670        required = True,
671        constraint=validate_email,
672        )
673
674class IApplicantOnlinePayment(IOnlinePayment):
675    """An applicant payment via payment gateways.
676    """
677
678    def doAfterApplicantPayment():
679        """Process applicant after payment was made.
680        """
681
682    def doAfterApplicantPaymentApproval():
683        """Process applicant after payment was approved.
684        """
685
686    def approveApplicantPayment():
687        """Approve payment and process applicant.
688        """
689
690class IApplicantRefereeReport(IKofaObject):
691    """A referee report.
692    """
693
694    r_id = Attribute('Report identifier')
695    email = Attribute('Referee email address taken from the mandate')
696
697    creation_date = schema.Datetime(
698        title = _(u'Report Creation Date'),
699        readonly = False,
700        required = False,
701        )
702
703    name = schema.TextLine(
704        title = _(u'Referee Name'),
705        required = True,
706        )
707
708    email_pref = schema.ASCIILine(
709        title = _(u'Preferred Email Address'),
710        required = False,
711        constraint=validate_email,
712        readonly = False,
713        )
714
715    phone = PhoneNumber(
716        title = _(u'Referee Phone'),
717        description = u'',
718        required = False,
719        )
720
721    report = schema.Text(
722        title = _(u'Report'),
723        required = False,
724        )
Note: See TracBrowser for help on using the repository browser.