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

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

Move grade and subject sources to central interfaces. They are needed in several submodules.

  • Property svn:keywords set to Id
File size: 20.3 KB
Line 
1## $Id: interfaces.py 7772 2012-03-07 09:06:12Z 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)
35from waeup.sirp.interfaces import MessageFactory as _
36from waeup.sirp.university.vocabularies import (
37    course_levels, AppCatSource)
38from waeup.sirp.students.vocabularies import (
39    lgas_vocab, CertificateSource, GenderSource)
40from waeup.sirp.payments.interfaces import IOnlinePayment
41from waeup.sirp.schema import MultiValObject
42
43#: Maximum upload size for applicant passport photographs (in bytes)
44MAX_UPLOAD_SIZE = 1024 * 20
45
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'
56    field_name = 'reg_number'
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
82
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):
109        appcats_dict = getUtility(
110            IApplicantsUtils).getApplicationTypeDict()
111        return sorted(appcats_dict.keys())
112
113    def getToken(self, context, value):
114        return value
115
116    def getTitle(self, context, value):
117        appcats_dict = getUtility(
118            IApplicantsUtils).getApplicationTypeDict()
119        return appcats_dict[value][0]
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):
127#        apppins_dict = getUtility(
128#            IApplicantsUtils).getApplicationTypeDict()
129#        return sorted(appcats_dict.keys())
130#
131#    def getToken(self, context, value):
132#        return value
133#
134#    def getTitle(self, context, value):
135#        apppins_dict = getUtility(
136#            IApplicantsUtils).getApplicationTypeDict()
137#        return u"%s (%s)" % (
138#            apppins_dict[value][1],self.apppins_dict[value][0])
139
140class ApplicantContainerProviderSource(BasicSourceFactory):
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'
155
156    """
157    def getValues(self):
158        """Returns a list of ``(<name>, <provider>)`` tuples.
159
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
166    def getToken(self, value):
167        """Return the name of the ``(<name>, <provider>)`` tuple.
168        """
169        return value[0]
170
171    def getTitle(self, value):
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)
177
178class IApplicantsUtils(Interface):
179    """A collection of methods which are subject to customization.
180    """
181    pass
182
183class IApplicantsRoot(ISIRPObject, IContainer):
184    """A container for university applicants containers.
185    """
186    pass
187
188class IApplicantsContainer(ISIRPObject):
189    """An applicants container contains university applicants.
190
191    """
192
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
198
199    code = schema.TextLine(
200        title = _(u'Code'),
201        default = u'-',
202        required = True,
203        readonly = True,
204        )
205
206    title = schema.TextLine(
207        title = _(u'Title'),
208        required = True,
209        default = u'-',
210        readonly = True,
211        )
212
213    prefix = schema.Choice(
214        title = _(u'Application Target'),
215        required = True,
216        default = None,
217        source = ApplicationTypeSource(),
218        readonly = True,
219        )
220
221    entry_level = schema.Choice(
222        title = _(u'Entry Level'),
223        vocabulary = course_levels,
224        default = 100,
225        required = True,
226        )
227
228    year = schema.Choice(
229        title = _(u'Year of Entrance'),
230        required = True,
231        default = None,
232        values = year_range(),
233        readonly = True,
234        )
235
236    provider = schema.Choice(
237        title = _(u'Applicants Container Type'),
238        required = True,
239        default = None,
240        source = ApplicantContainerProviderSource(),
241        readonly = True,
242        )
243
244    # Maybe Uniben still needs this ...
245    #ac_prefix = schema.Choice(
246    #    title = u'Activation code prefix',
247    #    required = True,
248    #    default = None,
249    #    source = ApplicationPinSource(),
250    #    )
251
252    application_category = schema.Choice(
253        title = _(u'Category for the grouping of certificates'),
254        required = True,
255        default = None,
256        source = AppCatSource(),
257        )
258
259    description = schema.Text(
260        title = _(u'Human readable description in reST format'),
261        required = False,
262        default = u'''This text can been seen by anonymous users.
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.'''
267        )
268
269    description_dict = schema.Dict(
270        title = u'Content as language dictionary with values in html format',
271        required = False,
272        default = {},
273        )
274
275    startdate = schema.Date(
276        title = _(u'Application Start Date'),
277        required = False,
278        default = None,
279        )
280
281    enddate = schema.Date(
282        title = _(u'Application Closing Date'),
283        required = False,
284        default = None,
285        )
286
287    strict_deadline = schema.Bool(
288        title = _(u'Forbid additions after deadline (enddate)'),
289        required = True,
290        default = True,
291        )
292
293    def archive(id=None):
294        """Create on-dist archive of applicants stored in this term.
295
296        If id is `None`, all applicants are archived.
297
298        If id contains a single id string, only the respective
299        applicants are archived.
300
301        If id contains a list of id strings all of the respective
302        applicants types are saved to disk.
303        """
304
305    def clear(id=None, archive=True):
306        """Remove applicants of type given by 'id'.
307
308        Optionally archive the applicants.
309
310        If id is `None`, all applicants are archived.
311
312        If id contains a single id string, only the respective
313        applicants are archived.
314
315        If id contains a list of id strings all of the respective
316        applicant types are saved to disk.
317
318        If `archive` is ``False`` none of the archive-handling is done
319        and respective applicants are simply removed from the
320        database.
321        """
322
323class IApplicantsContainerAdd(IApplicantsContainer):
324    """An applicants container contains university applicants.
325    """
326    prefix = schema.Choice(
327        title = _(u'Application Target'),
328        required = True,
329        default = None,
330        source = ApplicationTypeSource(),
331        readonly = False,
332        )
333
334    year = schema.Choice(
335        title = _(u'Year of Entrance'),
336        required = True,
337        default = None,
338        values = year_range(),
339        readonly = False,
340        )
341
342    provider = schema.Choice(
343        title = _(u'Applicants Container Type'),
344        required = True,
345        default = None,
346        source = ApplicantContainerProviderSource(),
347        readonly = False,
348        )
349
350IApplicantsContainerAdd[
351    'prefix'].order =  IApplicantsContainer['prefix'].order
352IApplicantsContainerAdd[
353    'year'].order =  IApplicantsContainer['year'].order
354IApplicantsContainerAdd[
355    'provider'].order =  IApplicantsContainer['provider'].order
356
357class IResultEntry(Interface):
358    subject = schema.Choice(
359        title = _(u'Subject'),
360        source = SubjectSource(),
361        )
362    #subject = schema.TextLine(
363    #    title = _(u'Subject'),
364    #    )
365    grade = schema.Choice(
366        title = _(u'Grade'),
367        source = GradeSource(),
368        )
369    #grade = schema.TextLine(
370    #    title = _(u'Grade'),
371    #    )
372
373class IApplicantBaseData(ISIRPObject):
374    """The data for an applicant.
375
376    This is a base interface with no field
377    required. For use with importers, forms, etc., please use one of
378    the derived interfaces below, which set more fields to required
379    state, depending on use-case.
380
381    This base interface is also implemented by the
382    :class:`waeup.sirp.students.StudentApplication` class in the
383    students package. Thus, these are the data which are saved after
384    admission.
385    """
386    applicant_id = schema.TextLine(
387        title = _(u'Applicant Id'),
388        required = False,
389        readonly = False,
390        )
391    reg_number = TextLineChoice(
392        title = _(u'JAMB Registration Number'),
393        readonly = False,
394        required = True,
395        default = None,
396        source = contextual_reg_num_source,
397        )
398    #access_code = schema.TextLine(
399    #    title = u'Activation Code',
400    #    required = False,
401    #    readonly = True,
402    #    )
403    firstname = schema.TextLine(
404        title = _(u'First Name'),
405        required = True,
406        )
407    middlename = schema.TextLine(
408        title = _(u'Middle Name'),
409        required = False,
410        )
411    lastname = schema.TextLine(
412        title = _(u'Last Name (Surname)'),
413        required = True,
414        )
415    date_of_birth = schema.Date(
416        title = _(u'Date of Birth'),
417        required = True,
418        )
419    lga = schema.Choice(
420        source = lgas_vocab,
421        title = _(u'State/LGA'),
422        default = 'foreigner',
423        required = True,
424        )
425    sex = schema.Choice(
426        title = _(u'Sex'),
427        source = GenderSource(),
428        default = u'm',
429        required = True,
430        )
431    email = schema.ASCIILine(
432        title = _(u'Email Address'),
433        required = True,
434        constraint=validate_email,
435        )
436    phone = schema.TextLine(
437        title = _(u'Phone'),
438        description = u'',
439        required = False,
440        )
441    course1 = schema.Choice(
442        title = _(u'1st Choice Course of Study'),
443        source = CertificateSource(),
444        required = True,
445        )
446    course2 = schema.Choice(
447        title = _(u'2nd Choice Course of Study'),
448        source = CertificateSource(),
449        required = False,
450        )
451    school_grades = schema.List(
452        title = _(u'School Grades'),
453        value_type = MultiValObject(
454            schema = IResultEntry),
455        #value_type = schema.Object(
456        #    schema = IResultEntry),
457        required = True,
458        default = [],
459        )
460    #school_grades = schema.Dict(
461    #    title = _(u'School Grades'),
462    #    key_type = schema.TextLine(
463    #        title = _(u'Subject'),
464    #        ),
465    #    value_type = schema.TextLine(
466    #        title = _(u'Grade'),
467    #        )
468    #    )
469
470    #
471    # Data to be imported after screening
472    #
473    screening_score = schema.Int(
474        title = _(u'Screening Score'),
475        required = False,
476        )
477    screening_venue = schema.TextLine(
478        title = _(u'Screening Venue'),
479        required = False,
480        )
481    course_admitted = schema.Choice(
482        title = _(u'Admitted Course of Study'),
483        source = CertificateSource(),
484        default = None,
485        required = False,
486        )
487    notice = schema.Text(
488        title = _(u'Notice'),
489        required = False,
490        )
491
492class IApplicantProcessData(IApplicantBaseData):
493    """An applicant.
494
495    Here we add process attributes and methods to the base data.
496    """
497
498    history = Attribute('Object history, a list of messages.')
499    state = Attribute('The application state of an applicant')
500    display_fullname = Attribute('The fullname of an applicant')
501    application_date = Attribute('Date of submission, used for export only')
502    password = Attribute('Encrypted password of a applicant')
503    application_number = Attribute('The key under which the record is stored')
504
505    def loggerInfo(ob_class, comment):
506        """Adds an INFO message to the log file
507        """
508
509    student_id = schema.TextLine(
510        title = _(u'Student Id'),
511        required = False,
512        readonly = False,
513        )
514    locked = schema.Bool(
515        title = _(u'Form locked'),
516        default = False,
517        )
518
519class IApplicant(IApplicantProcessData):
520    """An applicant.
521
522    This is basically the applicant base data. Here we repeat the
523    fields from base data if we have to set the `required` attribute
524    to True (which is the default).
525    """
526
527class IApplicantEdit(IApplicantProcessData):
528    """An applicant.
529
530    Here we can repeat the fields from base data and set the
531    `required` and `readonly` attributes to True to further restrict
532    the data access. Or we can allow only certain certificates to be
533    selected by choosing the appropriate source.
534
535    We cannot omit fields here. This has to be done in the
536    respective form page.
537    """
538
539    course1 = schema.Choice(
540        title = _(u'1st Choice Course of Study'),
541        source = AppCatCertificateSource(),
542        required = True,
543        )
544    course2 = schema.Choice(
545        title = _(u'2nd Choice Course of Study'),
546        source = AppCatCertificateSource(),
547        required = False,
548        )
549    screening_score = schema.Int(
550        title = _(u'Screening Score'),
551        required = False,
552        readonly = True,
553        )
554    screening_venue = schema.TextLine(
555        title = _(u'Screening Venue'),
556        required = False,
557        readonly = True,
558        )
559    course_admitted = schema.Choice(
560        title = _(u'Admitted Course of Study'),
561        source = CertificateSource(),
562        default = None,
563        required = False,
564        readonly = True,
565        )
566    notice = schema.Text(
567        title = _(u'Notice'),
568        required = False,
569        readonly = True,
570        )
571
572    def createStudent():
573        """Create a student object from applicatnt data
574        and copy applicant object.
575        """
576
577class IApplicantUpdateByRegNo(IApplicant):
578    """Representation of an applicant.
579
580    Skip regular reg_number validation if reg_number is used for finding
581    the applicant object.
582    """
583    reg_number = schema.TextLine(
584        title = u'Registration Number',
585        default = None,
586        required = False,
587        )
588
589class IApplicantOnlinePayment(IOnlinePayment):
590    """An applicant payment via payment gateways.
591
592    """
593    p_year = schema.Choice(
594        title = _(u'Payment Session'),
595        source = academic_sessions_vocab,
596        required = False,
597        )
598
599IApplicantOnlinePayment['p_year'].order = IApplicantOnlinePayment[
600    'p_year'].order
601
602class IApplicantsContainerProvider(Interface):
603    """A provider for applicants containers.
604
605    Applicants container providers are meant to be looked up as
606    utilities. This way we can find all applicant container types
607    defined somewhere.
608
609    Each applicants container provider registered as utility provides
610    one container type and one should be able to call the `factory`
611    attribute to create an instance of the requested container type.
612
613    .. THE FOLLOWING SHOULD GO INTO SPHINX DOCS (and be tested)
614
615    Samples:
616
617    Given, you had an IApplicantsContainer implementation somewhere
618    and you would like to make it findable on request, then you would
619    normally create an appropriate provider utility like this::
620
621      import grok
622      from waeup.sirp.applicants.interfaces import IApplicantsContainerProvider
623
624      class MyContainerProvider(grok.GlobalUtility):
625          grok.implements(IApplicantsContainerProvider)
626          grok.name('MyContainerProvider') # Must be unique
627          factory = MyContainer # A class implementing IApplicantsContainer
628                                # or derivations thereof.
629
630    This utility would be registered on startup and could then be used
631    like this:
632
633      >>> from zope.component import getAllUtilitiesRegisteredFor
634      >>> from waeup.sirp.applicants.interfaces import (
635      ...     IApplicantsContainerProvider)
636      >>> all_providers = getAllUtilitiesRegisteredFor(
637      ...     IApplicantsContainerProvider)
638      >>> all_providers
639      [<MyContainerProvider object at 0x...>]
640
641    You could look up this specific provider by name:
642
643      >>> from zope.component import getUtility
644      >>> p = getUtility(IApplicantsContainerProvider, name='MyProvider')
645      >>> p
646      <MyContainerProvider object at 0x...>
647
648    An applicants container would then be created like this:
649
650      >>> provider = all_providers[0]
651      >>> container = provider.factory()
652      >>> container
653      <MyContainer object at 0x...>
654
655    """
656    factory = Attribute("A class that can create instances of the "
657                        "requested container type")
Note: See TracBrowser for help on using the repository browser.