source: main/waeup.sirp/branches/ulif-schoolgrades/src/waeup/sirp/applicants/interfaces.py @ 7774

Last change on this file since 7774 was 7773, checked in by uli, 13 years ago

Move IResultEntry to central interfaces modules.

  • Property svn:keywords set to Id
File size: 19.9 KB
RevLine 
[5638]1## $Id: interfaces.py 7773 2012-03-07 10:04:38Z uli $
[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"""
[6256]20
[5866]21from grokcore.content.interfaces import IContainer
[6256]22
[5638]23from zope import schema
[7263]24from zope.interface import Interface, Attribute, implements, directlyProvides
[7683]25from zope.component import getUtilitiesFor, queryUtility, getUtility
[7263]26from zope.catalog.interfaces import ICatalog
[7317]27from zope.schema.interfaces import (
28    ValidationError, ISource, IContextSourceBinder)
[6256]29from zc.sourcefactory.basic import BasicSourceFactory
[7683]30from zc.sourcefactory.contextual import BasicContextualSourceFactory
[7263]31from waeup.sirp.schema import TextLineChoice
[7250]32from waeup.sirp.interfaces import (
[7772]33    ISIRPObject, year_range, validate_email, academic_sessions_vocab,
[7773]34    SubjectSource, GradeSource, IResultEntry)
[7708]35from waeup.sirp.interfaces import MessageFactory as _
[7436]36from waeup.sirp.university.vocabularies import (
[7681]37    course_levels, AppCatSource)
[7347]38from waeup.sirp.students.vocabularies import (
[7436]39    lgas_vocab, CertificateSource, GenderSource)
[7250]40from waeup.sirp.payments.interfaces import IOnlinePayment
[7770]41from waeup.sirp.schema import MultiValObject
[5638]42
[7075]43#: Maximum upload size for applicant passport photographs (in bytes)
[7086]44MAX_UPLOAD_SIZE = 1024 * 20
[7075]45
[7263]46class RegNumInSource(ValidationError):
47    """Registration number exists already
48    """
49    # The docstring of ValidationErrors is used as error description
50    # by zope.formlib.
51    pass
52
53class RegNumberSource(object):
54    implements(ISource)
55    cat_name = 'applicants_catalog'
[7270]56    field_name = 'reg_number'
[7263]57    validation_error = RegNumInSource
58    def __init__(self, context):
59        self.context = context
60        return
61
62    def __contains__(self, value):
63        cat = queryUtility(ICatalog, self.cat_name)
64        if cat is None:
65            return True
66        kw = {self.field_name: (value, value)}
67        results = cat.searchResults(**kw)
68        for entry in results:
69            if entry.applicant_id != self.context.applicant_id:
70                # XXX: sources should simply return False.
71                #      But then we get some stupid error message in forms
72                #      when validation fails.
73                raise self.validation_error(value)
74                #return False
75        return True
76
77def contextual_reg_num_source(context):
78    source = RegNumberSource(context)
79    return source
80directlyProvides(contextual_reg_num_source, IContextSourceBinder)
81
[7761]82
[7683]83class AppCatCertificateSource(CertificateSource):
84    """An application certificate source delivers all courses which belong to
85    a certain application_category.
86    """
87    def getValues(self, context):
88        # appliction category not available when certificate was deleted.
89        # shouldn't that info be part of applicant info instead?
90        # when we cannot determine the appcat, we will display all courses.
91        appcat = getattr(getattr(context, '__parent__', None),
92                         'application_category', None)
93        catalog = getUtility(ICatalog, name='certificates_catalog')
94        result = catalog.searchResults(
95            application_category=(appcat,appcat))
96        result = sorted(result, key=lambda value: value.code)
97        curr_course = context.course1
98        if curr_course is not None and curr_course not in result:
99            # display also current course even if it is not catalogued
100            # (any more)
101            result = [curr_course,] + result
102        return result
103
104class ApplicationTypeSource(BasicContextualSourceFactory):
105    """An application type source delivers screening types defined in the
106    portal.
107    """
108    def getValues(self, context):
[7688]109        appcats_dict = getUtility(
[7683]110            IApplicantsUtils).getApplicationTypeDict()
[7688]111        return sorted(appcats_dict.keys())
[7683]112
113    def getToken(self, context, value):
114        return value
115
116    def getTitle(self, context, value):
[7688]117        appcats_dict = getUtility(
118            IApplicantsUtils).getApplicationTypeDict()
119        return appcats_dict[value][0]
[7683]120
121# Maybe Uniben still needs this ...
122#class ApplicationPinSource(BasicContextualSourceFactory):
123#    """An application pin source delivers PIN prefixes for application
124#    defined in the portal.
125#    """
126#    def getValues(self, context):
[7688]127#        apppins_dict = getUtility(
[7683]128#            IApplicantsUtils).getApplicationTypeDict()
[7688]129#        return sorted(appcats_dict.keys())
[7683]130#
131#    def getToken(self, context, value):
132#        return value
133#
134#    def getTitle(self, context, value):
[7688]135#        apppins_dict = getUtility(
136#            IApplicantsUtils).getApplicationTypeDict()
[7683]137#        return u"%s (%s)" % (
[7688]138#            apppins_dict[value][1],self.apppins_dict[value][0])
[7683]139
[6069]140class ApplicantContainerProviderSource(BasicSourceFactory):
[6075]141    """A source offering all available applicants container types.
142
143    The values returned by this source are names of utilities that can
144    create :class:`ApplicantContainer` instances. So, if you get a
145    name like ``'myactype'`` from this source, then you can do:
146
147      >>> from zope.component import getUtility
148      >>> p = getUtility(IApplicantsContainerProvider, name=myactype)
149      >>> my_applicants_container = p.factory()
150
151    Or you can access class-attributes like
152
153      >>> my_applicants_container.container_title
154      'Pretty'
[6076]155
[6069]156    """
157    def getValues(self):
[6075]158        """Returns a list of ``(<name>, <provider>)`` tuples.
[5753]159
[6075]160        Here ``<name>`` is the name under which an
161        :class:``ApplicantContainerProvider`` was registered as a
162        utility and ``<provider>`` is the utility itself.
163        """
164        return getUtilitiesFor(IApplicantsContainerProvider)
165
[6069]166    def getToken(self, value):
[6075]167        """Return the name of the ``(<name>, <provider>)`` tuple.
168        """
169        return value[0]
170
[6069]171    def getTitle(self, value):
[6075]172        """Get a 'title - description' string for a container type.
173        """
174        factory = value[1].factory
175        return "%s - %s" % (
176            factory.container_title, factory.container_description)
[6069]177
[7682]178class IApplicantsUtils(Interface):
179    """A collection of methods which are subject to customization.
180    """
[7683]181    pass
[7682]182
[7321]183class IApplicantsRoot(ISIRPObject, IContainer):
[5676]184    """A container for university applicants containers.
[5645]185    """
[5866]186    pass
[5638]187
[7321]188class IApplicantsContainer(ISIRPObject):
[5676]189    """An applicants container contains university applicants.
[5645]190
[5638]191    """
[6069]192
[6075]193    container_title = Attribute(
194        u'classattribute: title for type of container')
195    container_description = Attribute(
196        u'classattribute: description for type of container')
197
[6076]198
[6069]199    code = schema.TextLine(
[7708]200        title = _(u'Code'),
[6087]201        default = u'-',
[6069]202        required = True,
203        readonly = True,
[6076]204        )
[6096]205
[6087]206    title = schema.TextLine(
[7708]207        title = _(u'Title'),
[6087]208        required = True,
209        default = u'-',
210        readonly = True,
[6096]211        )
212
[6087]213    prefix = schema.Choice(
[7708]214        title = _(u'Application Target'),
[6087]215        required = True,
216        default = None,
[7683]217        source = ApplicationTypeSource(),
[6087]218        readonly = True,
219        )
[6076]220
[7436]221    entry_level = schema.Choice(
[7708]222        title = _(u'Entry Level'),
[7436]223        vocabulary = course_levels,
224        default = 100,
225        required = True,
226        )
227
[6087]228    year = schema.Choice(
[7708]229        title = _(u'Year of Entrance'),
[6087]230        required = True,
231        default = None,
[6158]232        values = year_range(),
[6087]233        readonly = True,
[6096]234        )
[6087]235
[6069]236    provider = schema.Choice(
[7708]237        title = _(u'Applicants Container Type'),
[6069]238        required = True,
239        default = None,
240        source = ApplicantContainerProviderSource(),
[6087]241        readonly = True,
[6076]242        )
[6158]243
[7683]244    # Maybe Uniben still needs this ...
[7376]245    #ac_prefix = schema.Choice(
246    #    title = u'Activation code prefix',
247    #    required = True,
248    #    default = None,
[7683]249    #    source = ApplicationPinSource(),
[7376]250    #    )
[6076]251
[6189]252    application_category = schema.Choice(
[7708]253        title = _(u'Category for the grouping of certificates'),
[6189]254        required = True,
255        default = None,
[7681]256        source = AppCatSource(),
[6189]257        )
258
[5645]259    description = schema.Text(
[7708]260        title = _(u'Human readable description in reST format'),
[5638]261        required = False,
[6518]262        default = u'''This text can been seen by anonymous users.
[7708]263Here we put mult-lingual information about the study courses provided, the application procedure and deadlines.
264>>de<<
265Dieser Text kann von anonymen Benutzern gelesen werden.
266Hier koennen mehrsprachige Informationen fuer Antragsteller hinterlegt werden.'''
[5638]267        )
268
[7708]269    description_dict = schema.Dict(
270        title = u'Content as language dictionary with values in html format',
271        required = False,
272        default = {},
273        )
274
[5638]275    startdate = schema.Date(
[7708]276        title = _(u'Application Start Date'),
[5638]277        required = False,
278        default = None,
279        )
280
281    enddate = schema.Date(
[7708]282        title = _(u'Application Closing Date'),
[5638]283        required = False,
284        default = None,
285        )
286
[5645]287    strict_deadline = schema.Bool(
[7708]288        title = _(u'Forbid additions after deadline (enddate)'),
[5645]289        required = True,
290        default = True,
291        )
[5638]292
293    def archive(id=None):
[5676]294        """Create on-dist archive of applicants stored in this term.
[5638]295
[5676]296        If id is `None`, all applicants are archived.
[5638]297
298        If id contains a single id string, only the respective
[5676]299        applicants are archived.
[5638]300
301        If id contains a list of id strings all of the respective
[5676]302        applicants types are saved to disk.
[5638]303        """
304
305    def clear(id=None, archive=True):
[5676]306        """Remove applicants of type given by 'id'.
[5638]307
[5676]308        Optionally archive the applicants.
[6076]309
[5676]310        If id is `None`, all applicants are archived.
[5638]311
312        If id contains a single id string, only the respective
[5676]313        applicants are archived.
[5638]314
315        If id contains a list of id strings all of the respective
[5676]316        applicant types are saved to disk.
[5638]317
318        If `archive` is ``False`` none of the archive-handling is done
[5676]319        and respective applicants are simply removed from the
[5638]320        database.
321        """
[6073]322
[6069]323class IApplicantsContainerAdd(IApplicantsContainer):
324    """An applicants container contains university applicants.
325    """
[6087]326    prefix = schema.Choice(
[7708]327        title = _(u'Application Target'),
[6069]328        required = True,
[6087]329        default = None,
[7683]330        source = ApplicationTypeSource(),
[6069]331        readonly = False,
[6076]332        )
[6073]333
[6087]334    year = schema.Choice(
[7708]335        title = _(u'Year of Entrance'),
[6087]336        required = True,
337        default = None,
[6158]338        values = year_range(),
[6087]339        readonly = False,
[6096]340        )
[6073]341
[6087]342    provider = schema.Choice(
[7708]343        title = _(u'Applicants Container Type'),
[6087]344        required = True,
345        default = None,
346        source = ApplicantContainerProviderSource(),
347        readonly = False,
348        )
349
[6096]350IApplicantsContainerAdd[
351    'prefix'].order =  IApplicantsContainer['prefix'].order
352IApplicantsContainerAdd[
353    'year'].order =  IApplicantsContainer['year'].order
354IApplicantsContainerAdd[
355    'provider'].order =  IApplicantsContainer['provider'].order
[6087]356
[7321]357class IApplicantBaseData(ISIRPObject):
[5753]358    """The data for an applicant.
359
[7240]360    This is a base interface with no field
[5753]361    required. For use with importers, forms, etc., please use one of
362    the derived interfaces below, which set more fields to required
363    state, depending on use-case.
[7338]364
[7761]365    This base interface is also implemented by the
366    :class:`waeup.sirp.students.StudentApplication` class in the
367    students package. Thus, these are the data which are saved after
368    admission.
[5753]369    """
[7240]370    applicant_id = schema.TextLine(
[7708]371        title = _(u'Applicant Id'),
[7240]372        required = False,
[7260]373        readonly = False,
[7240]374        )
[7270]375    reg_number = TextLineChoice(
[7708]376        title = _(u'JAMB Registration Number'),
[7263]377        readonly = False,
378        required = True,
379        default = None,
380        source = contextual_reg_num_source,
[5753]381        )
[7376]382    #access_code = schema.TextLine(
383    #    title = u'Activation Code',
384    #    required = False,
385    #    readonly = True,
386    #    )
[5753]387    firstname = schema.TextLine(
[7708]388        title = _(u'First Name'),
[6352]389        required = True,
[5753]390        )
[7356]391    middlename = schema.TextLine(
[7708]392        title = _(u'Middle Name'),
[5753]393        required = False,
394        )
395    lastname = schema.TextLine(
[7708]396        title = _(u'Last Name (Surname)'),
[6352]397        required = True,
[5753]398        )
399    date_of_birth = schema.Date(
[7708]400        title = _(u'Date of Birth'),
[6352]401        required = True,
[5753]402        )
[6249]403    lga = schema.Choice(
404        source = lgas_vocab,
[7708]405        title = _(u'State/LGA'),
[6254]406        default = 'foreigner',
407        required = True,
[5753]408        )
409    sex = schema.Choice(
[7708]410        title = _(u'Sex'),
[5753]411        source = GenderSource(),
412        default = u'm',
[6352]413        required = True,
[5753]414        )
[6341]415    email = schema.ASCIILine(
[7708]416        title = _(u'Email Address'),
[7240]417        required = True,
[6343]418        constraint=validate_email,
[5753]419        )
[7324]420    phone = schema.TextLine(
[7708]421        title = _(u'Phone'),
[7331]422        description = u'',
[5753]423        required = False,
424        )
[7262]425    course1 = schema.Choice(
[7708]426        title = _(u'1st Choice Course of Study'),
[7347]427        source = CertificateSource(),
[7262]428        required = True,
429        )
430    course2 = schema.Choice(
[7708]431        title = _(u'2nd Choice Course of Study'),
[7347]432        source = CertificateSource(),
[7262]433        required = False,
434        )
[7761]435    school_grades = schema.List(
436        title = _(u'School Grades'),
[7770]437        value_type = MultiValObject(
[7761]438            schema = IResultEntry),
[7770]439        #value_type = schema.Object(
440        #    schema = IResultEntry),
[7761]441        required = True,
442        default = [],
443        )
444    #school_grades = schema.Dict(
445    #    title = _(u'School Grades'),
446    #    key_type = schema.TextLine(
447    #        title = _(u'Subject'),
448    #        ),
449    #    value_type = schema.TextLine(
450    #        title = _(u'Grade'),
451    #        )
452    #    )
[6322]453
[5753]454    #
[7338]455    # Data to be imported after screening
[5753]456    #
[6255]457    screening_score = schema.Int(
[7708]458        title = _(u'Screening Score'),
[5753]459        required = False,
460        )
461    screening_venue = schema.TextLine(
[7708]462        title = _(u'Screening Venue'),
[5753]463        required = False,
464        )
[6248]465    course_admitted = schema.Choice(
[7708]466        title = _(u'Admitted Course of Study'),
[7347]467        source = CertificateSource(),
[6254]468        default = None,
[5753]469        required = False,
470        )
[7347]471    notice = schema.Text(
[7708]472        title = _(u'Notice'),
[7347]473        required = False,
474        )
[7338]475
476class IApplicantProcessData(IApplicantBaseData):
477    """An applicant.
478
479    Here we add process attributes and methods to the base data.
480    """
481
482    history = Attribute('Object history, a list of messages.')
483    state = Attribute('The application state of an applicant')
[7364]484    display_fullname = Attribute('The fullname of an applicant')
[7338]485    application_date = Attribute('Date of submission, used for export only')
486    password = Attribute('Encrypted password of a applicant')
487    application_number = Attribute('The key under which the record is stored')
488
489    def loggerInfo(ob_class, comment):
490        """Adds an INFO message to the log file
491        """
492
[5753]493    student_id = schema.TextLine(
[7708]494        title = _(u'Student Id'),
[5753]495        required = False,
[7351]496        readonly = False,
[5753]497        )
[6302]498    locked = schema.Bool(
[7708]499        title = _(u'Form locked'),
[6302]500        default = False,
501        )
[5753]502
[7338]503class IApplicant(IApplicantProcessData):
[5753]504    """An applicant.
505
506    This is basically the applicant base data. Here we repeat the
[6195]507    fields from base data if we have to set the `required` attribute
508    to True (which is the default).
[5753]509    """
510
[7338]511class IApplicantEdit(IApplicantProcessData):
[6195]512    """An applicant.
[5753]513
[6339]514    Here we can repeat the fields from base data and set the
515    `required` and `readonly` attributes to True to further restrict
[7262]516    the data access. Or we can allow only certain certificates to be
517    selected by choosing the appropriate source.
518
519    We cannot omit fields here. This has to be done in the
[6339]520    respective form page.
[6195]521    """
[7262]522
523    course1 = schema.Choice(
[7708]524        title = _(u'1st Choice Course of Study'),
[7347]525        source = AppCatCertificateSource(),
[7262]526        required = True,
527        )
528    course2 = schema.Choice(
[7708]529        title = _(u'2nd Choice Course of Study'),
[7347]530        source = AppCatCertificateSource(),
[7262]531        required = False,
532        )
[6255]533    screening_score = schema.Int(
[7708]534        title = _(u'Screening Score'),
[6195]535        required = False,
[5941]536        readonly = True,
537        )
[6195]538    screening_venue = schema.TextLine(
[7708]539        title = _(u'Screening Venue'),
[5753]540        required = False,
541        readonly = True,
542        )
[6301]543    course_admitted = schema.Choice(
[7708]544        title = _(u'Admitted Course of Study'),
[7347]545        source = CertificateSource(),
[6301]546        default = None,
[5753]547        required = False,
[6195]548        readonly = True,
[5753]549        )
[6195]550    notice = schema.Text(
[7708]551        title = _(u'Notice'),
[5753]552        required = False,
553        readonly = True,
554        )
[5758]555
[7338]556    def createStudent():
557        """Create a student object from applicatnt data
558        and copy applicant object.
559        """
560
[7268]561class IApplicantUpdateByRegNo(IApplicant):
562    """Representation of an applicant.
563
[7270]564    Skip regular reg_number validation if reg_number is used for finding
[7268]565    the applicant object.
566    """
[7270]567    reg_number = schema.TextLine(
[7268]568        title = u'Registration Number',
569        default = None,
570        required = False,
571        )
572
[7250]573class IApplicantOnlinePayment(IOnlinePayment):
574    """An applicant payment via payment gateways.
575
576    """
577    p_year = schema.Choice(
[7708]578        title = _(u'Payment Session'),
[7250]579        source = academic_sessions_vocab,
580        required = False,
581        )
582
583IApplicantOnlinePayment['p_year'].order = IApplicantOnlinePayment[
584    'p_year'].order
585
[5846]586class IApplicantsContainerProvider(Interface):
[5820]587    """A provider for applicants containers.
588
589    Applicants container providers are meant to be looked up as
590    utilities. This way we can find all applicant container types
591    defined somewhere.
592
593    Each applicants container provider registered as utility provides
594    one container type and one should be able to call the `factory`
595    attribute to create an instance of the requested container type.
596
597    .. THE FOLLOWING SHOULD GO INTO SPHINX DOCS (and be tested)
[6076]598
[6500]599    Samples:
[6076]600
[5820]601    Given, you had an IApplicantsContainer implementation somewhere
602    and you would like to make it findable on request, then you would
603    normally create an appropriate provider utility like this::
604
605      import grok
[5846]606      from waeup.sirp.applicants.interfaces import IApplicantsContainerProvider
[5820]607
608      class MyContainerProvider(grok.GlobalUtility):
[5846]609          grok.implements(IApplicantsContainerProvider)
[5820]610          grok.name('MyContainerProvider') # Must be unique
611          factory = MyContainer # A class implementing IApplicantsContainer
612                                # or derivations thereof.
613
614    This utility would be registered on startup and could then be used
615    like this:
616
617      >>> from zope.component import getAllUtilitiesRegisteredFor
618      >>> from waeup.sirp.applicants.interfaces import (
[5846]619      ...     IApplicantsContainerProvider)
[5820]620      >>> all_providers = getAllUtilitiesRegisteredFor(
[5846]621      ...     IApplicantsContainerProvider)
[5820]622      >>> all_providers
623      [<MyContainerProvider object at 0x...>]
624
625    You could look up this specific provider by name:
626
627      >>> from zope.component import getUtility
[5846]628      >>> p = getUtility(IApplicantsContainerProvider, name='MyProvider')
[5820]629      >>> p
630      <MyContainerProvider object at 0x...>
[6076]631
[5820]632    An applicants container would then be created like this:
633
634      >>> provider = all_providers[0]
635      >>> container = provider.factory()
636      >>> container
637      <MyContainer object at 0x...>
638
639    """
640    factory = Attribute("A class that can create instances of the "
641                        "requested container type")
Note: See TracBrowser for help on using the repository browser.