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

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

Some stuff to start school grade support. Messy, but a beginning. At least manage and edit of applicants provide some school grade selections.

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