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

Last change on this file since 7789 was 7787, checked in by uli, 13 years ago

Clean up a bit. We now use ResultEntryField? in interfaces.

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