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

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

Use the new schemas/widgets automatically.

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