source: main/waeup.sirp/trunk/src/waeup/sirp/applicants/interfaces.py @ 6255

Last change on this file since 6255 was 6255, checked in by Henrik Bettermann, 13 years ago

Implement choice field for entry_session.

Scores must be integers.

File size: 18.8 KB
Line 
1##
2## interfaces.py
3## Login : <uli@pu.smp.net>
4## Started on  Sun Jan 16 15:30:01 2011 Uli Fouquet
5## $Id$
6##
7## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
8## This program is free software; you can redistribute it and/or modify
9## it under the terms of the GNU General Public License as published by
10## the Free Software Foundation; either version 2 of the License, or
11## (at your option) any later version.
12##
13## This program is distributed in the hope that it will be useful,
14## but WITHOUT ANY WARRANTY; without even the implied warranty of
15## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16## GNU General Public License for more details.
17##
18## You should have received a copy of the GNU General Public License
19## along with this program; if not, write to the Free Software
20## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21##
22"""Interfaces regarding student applicants and related components.
23"""
24import os
25import waeup.sirp.browser
26from datetime import datetime
27from grokcore.content.interfaces import IContainer
28from zc.sourcefactory.basic import BasicSourceFactory
29from zc.sourcefactory.contextual import BasicContextualSourceFactory
30from zope import schema
31from zope.component import getUtility, getUtilitiesFor
32from zope.interface import Interface, Attribute
33from zope.catalog.interfaces import ICatalog
34from zope.pluggableauth.interfaces import IPrincipalInfo
35from zope.security.interfaces import IGroupClosureAwarePrincipal as IPrincipal
36from waeup.sirp.image.schema import ImageFile
37from waeup.sirp.image.image import WAeUPImageFile
38from waeup.sirp.interfaces import IWAeUPObject, SimpleWAeUPVocabulary
39from waeup.sirp.university.vocabularies import application_categories
40from waeup.sirp.applicants.lgas import LGAS
41
42
43IMAGE_PATH = os.path.join(
44    os.path.dirname(waeup.sirp.browser.__file__),
45    'static'
46    )
47DEFAULT_PASSPORT_IMAGE_MALE = WAeUPImageFile(
48    'passport.jpg',
49    open(os.path.join(IMAGE_PATH, 'placeholder_m.jpg')).read(),
50    )
51DEFAULT_PASSPORT_IMAGE_FEMALE = WAeUPImageFile(
52    'passport.jpg',
53    open(os.path.join(IMAGE_PATH, 'placeholder_f.jpg')).read(),
54    )
55
56#: Types of applications we support.
57APPLICATION_TYPES = (
58    ('General Studies', 'app','APP'),
59    ('Pre-NCE Programme', 'prence','PRE'),
60    ('Post UME Screening Test', 'pume','PUME'),
61    ('Post UDE Screening', 'pude','PUDE'),
62    ('Part Time Degree in Education', 'sandwich','SAND'),
63    ('Part-Time Degree Programmes', 'pt','PTP'),
64    ('Diploma Programmes', 'dp','DPP'),
65    ('PCE Screening', 'pce','PCE'),
66    ('Certificate Programmes', 'ct','CTP'),
67    ('Common Entry Screening Test', 'cest','CEST'),
68    )
69
70#: A :class:`waeup.sirp.interfaces.SimpleWAeUPVocabulary` of supported
71#: application or screening types.
72application_types_vocab = SimpleWAeUPVocabulary(
73    *[(x[0],x[1]) for x in APPLICATION_TYPES])
74application_pins_vocab = SimpleWAeUPVocabulary(
75    *[(u"%s (%s)" % (x[2],x[0]),x[2]) for x in APPLICATION_TYPES])
76lgas_vocab = SimpleWAeUPVocabulary(
77    *sorted([(x[1],x[0]) for x in LGAS]))
78
79def year_range():
80    curr_year = datetime.now().year
81    return range(curr_year - 2, curr_year + 5)
82
83def entry_sessions():
84    curr_year = datetime.now().year
85    year_range = range(curr_year - 5, curr_year + 2)
86    return [('%s/%s' % (year,year+1), '%s' % year) for year in year_range]
87
88entry_session_vocab = SimpleWAeUPVocabulary(*entry_sessions())
89
90class CertificateSource(BasicContextualSourceFactory):
91    """A certificate source delivers all certificates provided
92    in the portal.
93    """
94    def getValues(self, context):
95        catalog = getUtility(ICatalog, name='certificates_catalog')
96        return sorted(list(
97                catalog.searchResults(
98                    code=('', 'z*'))),
99                    key=lambda value: value.code)
100
101    def getToken(self, context, value):
102        return value.code
103
104    def getTitle(self, context, value):
105        return "%s - %s" % (value.code, value.title[:64])
106
107class AppCatCertificateSource(CertificateSource):
108    """An application certificate source delivers all courses which belong to
109    a certain application_category.
110    """
111    def getValues(self, context):
112        appcat = context.__parent__.application_category
113        catalog = getUtility(ICatalog, name='certificates_catalog')
114        return sorted(list(
115                catalog.searchResults(
116                    code=('', 'z*'),
117                    application_category=(appcat,appcat))),
118                    key=lambda value: value.code)
119
120class GenderSource(BasicSourceFactory):
121    """A gender source delivers basically a mapping
122       ``{'m': 'male', 'f': 'female'}``
123
124       Using a source, we make sure that the tokens (which are
125       stored/expected for instance from CSV files) are something one
126       can expect and not cryptic IntIDs.
127    """
128    def getValues(self):
129        return ['m', 'f']
130
131    def getToken(self, value):
132        return value[0].lower()
133
134    def getTitle(self, value):
135        if value == 'm':
136            return 'male'
137        if value == 'f':
138            return 'female'
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 IResultEntry(IWAeUPObject):
179    subject = schema.TextLine(
180        title = u'Subject',
181        description = u'The subject',
182        required=False,
183        )
184    score = schema.TextLine(
185        title = u'Score',
186        description = u'The score',
187        required=False,
188        )
189
190class IApplicantsRoot(IWAeUPObject, IContainer):
191    """A container for university applicants containers.
192    """
193    pass
194
195
196class IApplicantsContainer(IWAeUPObject):
197    """An applicants container contains university applicants.
198
199    """
200
201    container_title = Attribute(
202        u'classattribute: title for type of container')
203    container_description = Attribute(
204        u'classattribute: description for type of container')
205
206
207    code = schema.TextLine(
208        title = u'Code',
209        default = u'-',
210        required = True,
211        readonly = True,
212        )
213
214    title = schema.TextLine(
215        title = u'Title',
216        required = True,
217        default = u'-',
218        readonly = True,
219        )
220
221    prefix = schema.Choice(
222        title = u'Application target',
223        required = True,
224        default = None,
225        source = application_types_vocab,
226        readonly = 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    ac_prefix = schema.Choice(
246        title = u'Access code prefix',
247        required = True,
248        default = None,
249        source = application_pins_vocab,
250        )
251
252    application_category = schema.Choice(
253        title = u'Category for the grouping of study courses',
254        required = True,
255        default = None,
256        source = application_categories,
257        )
258
259    description = schema.Text(
260        title = u'Human readable description in reST format',
261        required = False,
262        default = u'No description yet.'
263        )
264
265    startdate = schema.Date(
266        title = u'Date when the application period starts',
267        required = False,
268        default = None,
269        )
270
271    enddate = schema.Date(
272        title = u'Date when the application period ends',
273        required = False,
274        default = None,
275        )
276
277    strict_deadline = schema.Bool(
278        title = u'Forbid additions after deadline (enddate)',
279        required = True,
280        default = True,
281        )
282
283    def archive(id=None):
284        """Create on-dist archive of applicants stored in this term.
285
286        If id is `None`, all applicants are archived.
287
288        If id contains a single id string, only the respective
289        applicants are archived.
290
291        If id contains a list of id strings all of the respective
292        applicants types are saved to disk.
293        """
294
295    def clear(id=None, archive=True):
296        """Remove applicants of type given by 'id'.
297
298        Optionally archive the applicants.
299
300        If id is `None`, all applicants are archived.
301
302        If id contains a single id string, only the respective
303        applicants are archived.
304
305        If id contains a list of id strings all of the respective
306        applicant types are saved to disk.
307
308        If `archive` is ``False`` none of the archive-handling is done
309        and respective applicants are simply removed from the
310        database.
311        """
312
313class IApplicantsContainerAdd(IApplicantsContainer):
314    """An applicants container contains university applicants.
315    """
316    prefix = schema.Choice(
317        title = u'Application target',
318        required = True,
319        default = None,
320        source = application_types_vocab,
321        readonly = False,
322        )
323
324    year = schema.Choice(
325        title = u'Year of entrance',
326        required = True,
327        default = None,
328        values = year_range(),
329        readonly = False,
330        )
331
332    provider = schema.Choice(
333        title = u'Applicants container type',
334        required = True,
335        default = None,
336        source = ApplicantContainerProviderSource(),
337        readonly = False,
338        )
339
340IApplicantsContainerAdd[
341    'prefix'].order =  IApplicantsContainer['prefix'].order
342IApplicantsContainerAdd[
343    'year'].order =  IApplicantsContainer['year'].order
344IApplicantsContainerAdd[
345    'provider'].order =  IApplicantsContainer['provider'].order
346
347class IApplicantBaseData(IWAeUPObject):
348    """The data for an applicant.
349
350    This is a base interface with no field (except ``reg_no``)
351    required. For use with importers, forms, etc., please use one of
352    the derived interfaces below, which set more fields to required
353    state, depending on use-case.
354    """
355    locked = schema.Bool(
356        title = u'Form locked',
357        default = False,
358        #readonly = True,
359        )
360    reg_no = schema.TextLine(
361        title = u'JAMB Registration Number',
362        readonly = True,
363        )
364    access_code = schema.TextLine(
365        title = u'Access Code',
366        required = False,
367        readonly = True,
368        )
369    course1 = schema.Choice(
370        title = u'1st Choice Course of Study',
371        source = AppCatCertificateSource(),
372        required = False,
373        )
374    course2 = schema.Choice(
375        title = u'2nd Choice Course of Study',
376        source = AppCatCertificateSource(),
377        required = False,
378        )
379    firstname = schema.TextLine(
380        title = u'First Name',
381        required = False,
382        )
383    middlenames = schema.TextLine(
384        title = u'Middle Names',
385        required = False,
386        )
387    lastname = schema.TextLine(
388        title = u'Last Name (Surname)',
389        required = False,
390        )
391    date_of_birth = schema.Date(
392        title = u'Date of Birth',
393        required = False,
394        )
395    lga = schema.Choice(
396        source = lgas_vocab,
397        title = u'State/LGA',
398        default = 'foreigner',
399        required = True,
400        )
401    sex = schema.Choice(
402        title = u'Sex',
403        source = GenderSource(),
404        default = u'm',
405        required = False,
406        )
407    email = schema.TextLine(
408        title = u'Email',
409        required = False,
410        )
411    phone = schema.TextLine(
412        title = u'Phone',
413        required = False,
414        )
415    passport = ImageFile(
416        title = u'Passport Photograph',
417        default = DEFAULT_PASSPORT_IMAGE_MALE,
418        required = True,
419        #max_size = 20480,
420        )
421    confirm_passport = schema.Bool(
422        title = u"Passport picture confirmed",
423        default = False,
424        required = True,
425        )
426    #
427    # Process Data
428    #
429    application_date = schema.Date(
430        title = u'Application Date',
431        required = False,
432        readonly = True,
433        )
434    status = schema.TextLine(
435        # XXX: should be 'status' type
436        title = u'Application Status',
437        required = False,
438        readonly = True,
439        )
440    screening_score = schema.Int(
441        title = u'Screening Score',
442        required = False,
443        )
444    screening_venue = schema.TextLine(
445        title = u'Screening Venue',
446        required = False,
447        )
448    course_admitted = schema.Choice(
449        title = u'Admitted Course of Study',
450        source = CertificateSource(),
451        default = None,
452        required = False,
453        )
454    entry_session = schema.Choice(
455        source = entry_session_vocab,
456        title = u'Entry Session',
457        required = False,
458        )
459    notice = schema.Text(
460        title = u'Notice',
461        required = False,
462        )
463    student_id = schema.TextLine(
464        title = u'Student ID',
465        required = False,
466        readonly = True,
467        )
468
469class IApplicant(IApplicantBaseData):
470    """An applicant.
471
472    This is basically the applicant base data. Here we repeat the
473    fields from base data if we have to set the `required` attribute
474    to True (which is the default).
475    """
476
477class IApplicantEdit(IApplicantBaseData):
478    """An applicant.
479
480    Here we can repeat the fields from base data and set the `required` and
481    `readonly` attributes to True to further restrict the data access. We cannot
482    omit fields. This has to be done in the respective form page.
483    """
484    screening_score = schema.Int(
485        title = u'Screening Score',
486        required = False,
487        readonly = True,
488        )
489    screening_venue = schema.TextLine(
490        title = u'Screening Venue',
491        required = False,
492        readonly = True,
493        )
494    course_admitted = schema.TextLine(
495        # XXX: should be choice
496        title = u'Admitted Course of Study',
497        required = False,
498        readonly = True,
499        default = None,
500        )
501    entry_session = schema.TextLine(
502        # XXX: should be choice
503        title = u'Entry Session',
504        required = False,
505        readonly = True,
506        )
507    notice = schema.Text(
508        title = u'Notice',
509        required = False,
510        readonly = True,
511        )
512    confirm_passport = schema.Bool(
513        title = u"I confirm that the Passport Photograph uploaded on this form is a true picture of me.",
514        default = False,
515        required = True,
516        )
517
518class IApplicantPrincipalInfo(IPrincipalInfo):
519    """Infos about principals that are applicants.
520    """
521    access_code = Attribute("The Access Code the user purchased")
522
523class IApplicantPrincipal(IPrincipal):
524    """A principal that is an applicant.
525
526    This interface extends zope.security.interfaces.IPrincipal and
527    requires also an `id` and other attributes defined there.
528    """
529    access_code = schema.TextLine(
530        title = u'Access Code',
531        description = u'The access code purchased by the user.',
532        required = True,
533        readonly = True)
534
535class IApplicantsFormChallenger(Interface):
536    """A challenger that uses a browser form to collect applicant
537       credentials.
538    """
539    loginpagename = schema.TextLine(
540        title = u'Loginpagename',
541        description = u"""Name of the login form used by challenger.
542
543        The form must provide an ``access_code`` input field.
544        """)
545
546    accesscode_field = schema.TextLine(
547        title = u'Access code field',
548        description = u'''Field of the login page which is looked up for
549                          access_code''',
550        default = u'access_code',
551        )
552
553
554class IApplicantSessionCredentials(Interface):
555    """Interface for storing and accessing applicant credentials in a
556       session.
557    """
558
559    def __init__(access_code):
560        """Create applicant session credentials."""
561
562    def getAccessCode():
563        """Return the access code."""
564
565
566class IApplicantsContainerProvider(Interface):
567    """A provider for applicants containers.
568
569    Applicants container providers are meant to be looked up as
570    utilities. This way we can find all applicant container types
571    defined somewhere.
572
573    Each applicants container provider registered as utility provides
574    one container type and one should be able to call the `factory`
575    attribute to create an instance of the requested container type.
576
577    .. THE FOLLOWING SHOULD GO INTO SPHINX DOCS (and be tested)
578
579    Samples
580    *******
581
582    Given, you had an IApplicantsContainer implementation somewhere
583    and you would like to make it findable on request, then you would
584    normally create an appropriate provider utility like this::
585
586      import grok
587      from waeup.sirp.applicants.interfaces import IApplicantsContainerProvider
588
589      class MyContainerProvider(grok.GlobalUtility):
590          grok.implements(IApplicantsContainerProvider)
591          grok.name('MyContainerProvider') # Must be unique
592          factory = MyContainer # A class implementing IApplicantsContainer
593                                # or derivations thereof.
594
595    This utility would be registered on startup and could then be used
596    like this:
597
598      >>> from zope.component import getAllUtilitiesRegisteredFor
599      >>> from waeup.sirp.applicants.interfaces import (
600      ...     IApplicantsContainerProvider)
601      >>> all_providers = getAllUtilitiesRegisteredFor(
602      ...     IApplicantsContainerProvider)
603      >>> all_providers
604      [<MyContainerProvider object at 0x...>]
605
606    You could look up this specific provider by name:
607
608      >>> from zope.component import getUtility
609      >>> p = getUtility(IApplicantsContainerProvider, name='MyProvider')
610      >>> p
611      <MyContainerProvider object at 0x...>
612
613    An applicants container would then be created like this:
614
615      >>> provider = all_providers[0]
616      >>> container = provider.factory()
617      >>> container
618      <MyContainer object at 0x...>
619
620    """
621    factory = Attribute("A class that can create instances of the "
622                        "requested container type")
Note: See TracBrowser for help on using the repository browser.