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

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

Provide source for LGA field (Nigerian local government areas). We need a place where we can put large lists for vocabularies. The best place would be the customization package.

File size: 18.5 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
79class CertificateSource(BasicContextualSourceFactory):
80    """A certificate source delivers all certificates provided
81    in the portal.
82    """
83    def getValues(self, context):
84        catalog = getUtility(ICatalog, name='certificates_catalog')
85        return sorted(list(
86                catalog.searchResults(
87                    code=('', 'z*'))),
88                    key=lambda value: value.code)
89
90    def getToken(self, context, value):
91        return value.code
92
93    def getTitle(self, context, value):
94        return "%s - %s" % (value.code, value.title[:64])
95
96class AppCatCertificateSource(CertificateSource):
97    """An application certificate source delivers all courses which belong to
98    a certain application_category.
99    """
100    def getValues(self, context):
101        appcat = context.__parent__.application_category
102        catalog = getUtility(ICatalog, name='certificates_catalog')
103        return sorted(list(
104                catalog.searchResults(
105                    code=('', 'z*'),
106                    application_category=(appcat,appcat))),
107                    key=lambda value: value.code)
108
109def year_range():
110    curr_year = datetime.now().year
111    return range(curr_year - 2, curr_year + 5)
112
113class GenderSource(BasicSourceFactory):
114    """A gender source delivers basically a mapping
115       ``{'m': 'male', 'f': 'female'}``
116
117       Using a source, we make sure that the tokens (which are
118       stored/expected for instance from CSV files) are something one
119       can expect and not cryptic IntIDs.
120    """
121    def getValues(self):
122        return ['m', 'f']
123
124    def getToken(self, value):
125        return value[0].lower()
126
127    def getTitle(self, value):
128        if value == 'm':
129            return 'male'
130        if value == 'f':
131            return 'female'
132
133class ApplicantContainerProviderSource(BasicSourceFactory):
134    """A source offering all available applicants container types.
135
136    The values returned by this source are names of utilities that can
137    create :class:`ApplicantContainer` instances. So, if you get a
138    name like ``'myactype'`` from this source, then you can do:
139
140      >>> from zope.component import getUtility
141      >>> p = getUtility(IApplicantsContainerProvider, name=myactype)
142      >>> my_applicants_container = p.factory()
143
144    Or you can access class-attributes like
145
146      >>> my_applicants_container.container_title
147      'Pretty'
148
149    """
150    def getValues(self):
151        """Returns a list of ``(<name>, <provider>)`` tuples.
152
153        Here ``<name>`` is the name under which an
154        :class:``ApplicantContainerProvider`` was registered as a
155        utility and ``<provider>`` is the utility itself.
156        """
157        return getUtilitiesFor(IApplicantsContainerProvider)
158
159    def getToken(self, value):
160        """Return the name of the ``(<name>, <provider>)`` tuple.
161        """
162        return value[0]
163
164    def getTitle(self, value):
165        """Get a 'title - description' string for a container type.
166        """
167        factory = value[1].factory
168        return "%s - %s" % (
169            factory.container_title, factory.container_description)
170
171class IResultEntry(IWAeUPObject):
172    subject = schema.TextLine(
173        title = u'Subject',
174        description = u'The subject',
175        required=False,
176        )
177    score = schema.TextLine(
178        title = u'Score',
179        description = u'The score',
180        required=False,
181        )
182
183class IApplicantsRoot(IWAeUPObject, IContainer):
184    """A container for university applicants containers.
185    """
186    pass
187
188
189class IApplicantsContainer(IWAeUPObject):
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 = application_types_vocab,
219        readonly = True,
220        )
221
222    year = schema.Choice(
223        title = u'Year of entrance',
224        required = True,
225        default = None,
226        values = year_range(),
227        readonly = True,
228        )
229
230    provider = schema.Choice(
231        title = u'Applicants container type',
232        required = True,
233        default = None,
234        source = ApplicantContainerProviderSource(),
235        readonly = True,
236        )
237
238    ac_prefix = schema.Choice(
239        title = u'Access code prefix',
240        required = True,
241        default = None,
242        source = application_pins_vocab,
243        )
244
245    application_category = schema.Choice(
246        title = u'Category for the grouping of study courses',
247        required = True,
248        default = None,
249        source = application_categories,
250        )
251
252    description = schema.Text(
253        title = u'Human readable description in reST format',
254        required = False,
255        default = u'No description yet.'
256        )
257
258    startdate = schema.Date(
259        title = u'Date when the application period starts',
260        required = False,
261        default = None,
262        )
263
264    enddate = schema.Date(
265        title = u'Date when the application period ends',
266        required = False,
267        default = None,
268        )
269
270    strict_deadline = schema.Bool(
271        title = u'Forbid additions after deadline (enddate)',
272        required = True,
273        default = True,
274        )
275
276    def archive(id=None):
277        """Create on-dist archive of applicants stored in this term.
278
279        If id is `None`, all applicants are archived.
280
281        If id contains a single id string, only the respective
282        applicants are archived.
283
284        If id contains a list of id strings all of the respective
285        applicants types are saved to disk.
286        """
287
288    def clear(id=None, archive=True):
289        """Remove applicants of type given by 'id'.
290
291        Optionally archive the applicants.
292
293        If id is `None`, all applicants are archived.
294
295        If id contains a single id string, only the respective
296        applicants are archived.
297
298        If id contains a list of id strings all of the respective
299        applicant types are saved to disk.
300
301        If `archive` is ``False`` none of the archive-handling is done
302        and respective applicants are simply removed from the
303        database.
304        """
305
306class IApplicantsContainerAdd(IApplicantsContainer):
307    """An applicants container contains university applicants.
308    """
309    prefix = schema.Choice(
310        title = u'Application target',
311        required = True,
312        default = None,
313        source = application_types_vocab,
314        readonly = False,
315        )
316
317    year = schema.Choice(
318        title = u'Year of entrance',
319        required = True,
320        default = None,
321        values = year_range(),
322        readonly = False,
323        )
324
325    provider = schema.Choice(
326        title = u'Applicants container type',
327        required = True,
328        default = None,
329        source = ApplicantContainerProviderSource(),
330        readonly = False,
331        )
332
333IApplicantsContainerAdd[
334    'prefix'].order =  IApplicantsContainer['prefix'].order
335IApplicantsContainerAdd[
336    'year'].order =  IApplicantsContainer['year'].order
337IApplicantsContainerAdd[
338    'provider'].order =  IApplicantsContainer['provider'].order
339
340class IApplicantBaseData(IWAeUPObject):
341    """The data for an applicant.
342
343    This is a base interface with no field (except ``reg_no``)
344    required. For use with importers, forms, etc., please use one of
345    the derived interfaces below, which set more fields to required
346    state, depending on use-case.
347    """
348    locked = schema.Bool(
349        title = u'Form locked',
350        default = False,
351        #readonly = True,
352        )
353    reg_no = schema.TextLine(
354        title = u'JAMB Registration Number',
355        readonly = True,
356        )
357    access_code = schema.TextLine(
358        title = u'Access Code',
359        required = False,
360        readonly = True,
361        )
362    course1 = schema.Choice(
363        title = u'1st Choice Course of Study',
364        source = AppCatCertificateSource(),
365        required = False,
366        )
367    course2 = schema.Choice(
368        title = u'2nd Choice Course of Study',
369        source = AppCatCertificateSource(),
370        required = False,
371        )
372    firstname = schema.TextLine(
373        title = u'First Name',
374        required = False,
375        )
376    middlenames = schema.TextLine(
377        title = u'Middle Names',
378        required = False,
379        )
380    lastname = schema.TextLine(
381        title = u'Last Name (Surname)',
382        required = False,
383        )
384    date_of_birth = schema.Date(
385        title = u'Date of Birth',
386        required = False,
387        )
388    lga = schema.Choice(
389        source = lgas_vocab,
390        title = u'State/LGA',
391        required = False,
392        )
393    sex = schema.Choice(
394        title = u'Sex',
395        source = GenderSource(),
396        default = u'm',
397        required = False,
398        )
399    email = schema.TextLine(
400        title = u'Email',
401        required = False,
402        )
403    phone = schema.TextLine(
404        title = u'Phone',
405        required = False,
406        )
407    passport = ImageFile(
408        title = u'Passport Photograph',
409        default = DEFAULT_PASSPORT_IMAGE_MALE,
410        required = True,
411        #max_size = 20480,
412        )
413    confirm_passport = schema.Bool(
414        title = u"Passport picture confirmed",
415        default = False,
416        required = True,
417        )
418    #
419    # Process Data
420    #
421    application_date = schema.Date(
422        title = u'Application Date',
423        required = False,
424        readonly = True,
425        )
426    status = schema.TextLine(
427        # XXX: should be 'status' type
428        title = u'Application Status',
429        required = False,
430        readonly = True,
431        )
432    screening_score = schema.TextLine(
433        title = u'Screening Score',
434        required = False,
435        )
436    screening_venue = schema.TextLine(
437        title = u'Screening Venue',
438        required = False,
439        )
440    course_admitted = schema.Choice(
441        # XXX: should be choice
442        title = u'Admitted Course of Study',
443        source = CertificateSource(),
444        required = False,
445        )
446    entry_session = schema.TextLine(
447        # XXX: should be choice
448        title = u'Entry Session',
449        required = False,
450        )
451    notice = schema.Text(
452        title = u'Notice',
453        required = False,
454        )
455    student_id = schema.TextLine(
456        title = u'Student ID',
457        required = False,
458        readonly = True,
459        )
460
461class IApplicant(IApplicantBaseData):
462    """An applicant.
463
464    This is basically the applicant base data. Here we repeat the
465    fields from base data if we have to set the `required` attribute
466    to True (which is the default).
467    """
468
469class IApplicantEdit(IApplicantBaseData):
470    """An applicant.
471
472    Here we can repeat the fields from base data and set the `required` and
473    `readonly` attributes to True to further restrict the data access. We cannot
474    omit fields. This has to be done in the respective form page.
475    """
476    screening_score = schema.TextLine(
477        title = u'Screening Score',
478        required = False,
479        readonly = True,
480        )
481    screening_venue = schema.TextLine(
482        title = u'Screening Venue',
483        required = False,
484        readonly = True,
485        )
486    course_admitted = schema.TextLine(
487        # XXX: should be choice
488        title = u'Admitted Course of Study',
489        required = False,
490        readonly = True,
491        )
492    entry_session = schema.TextLine(
493        # XXX: should be choice
494        title = u'Entry Session',
495        required = False,
496        readonly = True,
497        )
498    notice = schema.Text(
499        title = u'Notice',
500        required = False,
501        readonly = True,
502        )
503    confirm_passport = schema.Bool(
504        title = u"I confirm that the Passport Photograph uploaded on this form is a true picture of me.",
505        default = False,
506        required = True,
507        )
508
509class IApplicantPrincipalInfo(IPrincipalInfo):
510    """Infos about principals that are applicants.
511    """
512    access_code = Attribute("The Access Code the user purchased")
513
514class IApplicantPrincipal(IPrincipal):
515    """A principal that is an applicant.
516
517    This interface extends zope.security.interfaces.IPrincipal and
518    requires also an `id` and other attributes defined there.
519    """
520    access_code = schema.TextLine(
521        title = u'Access Code',
522        description = u'The access code purchased by the user.',
523        required = True,
524        readonly = True)
525
526class IApplicantsFormChallenger(Interface):
527    """A challenger that uses a browser form to collect applicant
528       credentials.
529    """
530    loginpagename = schema.TextLine(
531        title = u'Loginpagename',
532        description = u"""Name of the login form used by challenger.
533
534        The form must provide an ``access_code`` input field.
535        """)
536
537    accesscode_field = schema.TextLine(
538        title = u'Access code field',
539        description = u'''Field of the login page which is looked up for
540                          access_code''',
541        default = u'access_code',
542        )
543
544
545class IApplicantSessionCredentials(Interface):
546    """Interface for storing and accessing applicant credentials in a
547       session.
548    """
549
550    def __init__(access_code):
551        """Create applicant session credentials."""
552
553    def getAccessCode():
554        """Return the access code."""
555
556
557class IApplicantsContainerProvider(Interface):
558    """A provider for applicants containers.
559
560    Applicants container providers are meant to be looked up as
561    utilities. This way we can find all applicant container types
562    defined somewhere.
563
564    Each applicants container provider registered as utility provides
565    one container type and one should be able to call the `factory`
566    attribute to create an instance of the requested container type.
567
568    .. THE FOLLOWING SHOULD GO INTO SPHINX DOCS (and be tested)
569
570    Samples
571    *******
572
573    Given, you had an IApplicantsContainer implementation somewhere
574    and you would like to make it findable on request, then you would
575    normally create an appropriate provider utility like this::
576
577      import grok
578      from waeup.sirp.applicants.interfaces import IApplicantsContainerProvider
579
580      class MyContainerProvider(grok.GlobalUtility):
581          grok.implements(IApplicantsContainerProvider)
582          grok.name('MyContainerProvider') # Must be unique
583          factory = MyContainer # A class implementing IApplicantsContainer
584                                # or derivations thereof.
585
586    This utility would be registered on startup and could then be used
587    like this:
588
589      >>> from zope.component import getAllUtilitiesRegisteredFor
590      >>> from waeup.sirp.applicants.interfaces import (
591      ...     IApplicantsContainerProvider)
592      >>> all_providers = getAllUtilitiesRegisteredFor(
593      ...     IApplicantsContainerProvider)
594      >>> all_providers
595      [<MyContainerProvider object at 0x...>]
596
597    You could look up this specific provider by name:
598
599      >>> from zope.component import getUtility
600      >>> p = getUtility(IApplicantsContainerProvider, name='MyProvider')
601      >>> p
602      <MyContainerProvider object at 0x...>
603
604    An applicants container would then be created like this:
605
606      >>> provider = all_providers[0]
607      >>> container = provider.factory()
608      >>> container
609      <MyContainer object at 0x...>
610
611    """
612    factory = Attribute("A class that can create instances of the "
613                        "requested container type")
Note: See TracBrowser for help on using the repository browser.