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

Last change on this file since 13228 was 13218, checked in by Henrik Bettermann, 9 years ago

Add UI components to purge applicants containers.

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