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

Last change on this file since 14102 was 14040, checked in by Henrik Bettermann, 8 years ago

Draft email to be sent to referees.

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