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

Last change on this file was 17660, checked in by Henrik Bettermann, 9 months ago

Increase year range.

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