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

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

Render Admitted Course of Study seperately and provide URL to certificate.

File size: 18.6 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        default = 'foreigner',
392        required = True,
393        )
394    sex = schema.Choice(
395        title = u'Sex',
396        source = GenderSource(),
397        default = u'm',
398        required = False,
399        )
400    email = schema.TextLine(
401        title = u'Email',
402        required = False,
403        )
404    phone = schema.TextLine(
405        title = u'Phone',
406        required = False,
407        )
408    passport = ImageFile(
409        title = u'Passport Photograph',
410        default = DEFAULT_PASSPORT_IMAGE_MALE,
411        required = True,
412        #max_size = 20480,
413        )
414    confirm_passport = schema.Bool(
415        title = u"Passport picture confirmed",
416        default = False,
417        required = True,
418        )
419    #
420    # Process Data
421    #
422    application_date = schema.Date(
423        title = u'Application Date',
424        required = False,
425        readonly = True,
426        )
427    status = schema.TextLine(
428        # XXX: should be 'status' type
429        title = u'Application Status',
430        required = False,
431        readonly = True,
432        )
433    screening_score = schema.TextLine(
434        title = u'Screening Score',
435        required = False,
436        )
437    screening_venue = schema.TextLine(
438        title = u'Screening Venue',
439        required = False,
440        )
441    course_admitted = schema.Choice(
442        title = u'Admitted Course of Study',
443        source = CertificateSource(),
444        default = None,
445        required = False,
446        )
447    entry_session = schema.TextLine(
448        # XXX: should be choice
449        title = u'Entry Session',
450        required = False,
451        )
452    notice = schema.Text(
453        title = u'Notice',
454        required = False,
455        )
456    student_id = schema.TextLine(
457        title = u'Student ID',
458        required = False,
459        readonly = True,
460        )
461
462class IApplicant(IApplicantBaseData):
463    """An applicant.
464
465    This is basically the applicant base data. Here we repeat the
466    fields from base data if we have to set the `required` attribute
467    to True (which is the default).
468    """
469
470class IApplicantEdit(IApplicantBaseData):
471    """An applicant.
472
473    Here we can repeat the fields from base data and set the `required` and
474    `readonly` attributes to True to further restrict the data access. We cannot
475    omit fields. This has to be done in the respective form page.
476    """
477    screening_score = schema.TextLine(
478        title = u'Screening Score',
479        required = False,
480        readonly = True,
481        )
482    screening_venue = schema.TextLine(
483        title = u'Screening Venue',
484        required = False,
485        readonly = True,
486        )
487    course_admitted = schema.TextLine(
488        # XXX: should be choice
489        title = u'Admitted Course of Study',
490        required = False,
491        readonly = True,
492        default = None,
493        )
494    entry_session = schema.TextLine(
495        # XXX: should be choice
496        title = u'Entry Session',
497        required = False,
498        readonly = True,
499        )
500    notice = schema.Text(
501        title = u'Notice',
502        required = False,
503        readonly = True,
504        )
505    confirm_passport = schema.Bool(
506        title = u"I confirm that the Passport Photograph uploaded on this form is a true picture of me.",
507        default = False,
508        required = True,
509        )
510
511class IApplicantPrincipalInfo(IPrincipalInfo):
512    """Infos about principals that are applicants.
513    """
514    access_code = Attribute("The Access Code the user purchased")
515
516class IApplicantPrincipal(IPrincipal):
517    """A principal that is an applicant.
518
519    This interface extends zope.security.interfaces.IPrincipal and
520    requires also an `id` and other attributes defined there.
521    """
522    access_code = schema.TextLine(
523        title = u'Access Code',
524        description = u'The access code purchased by the user.',
525        required = True,
526        readonly = True)
527
528class IApplicantsFormChallenger(Interface):
529    """A challenger that uses a browser form to collect applicant
530       credentials.
531    """
532    loginpagename = schema.TextLine(
533        title = u'Loginpagename',
534        description = u"""Name of the login form used by challenger.
535
536        The form must provide an ``access_code`` input field.
537        """)
538
539    accesscode_field = schema.TextLine(
540        title = u'Access code field',
541        description = u'''Field of the login page which is looked up for
542                          access_code''',
543        default = u'access_code',
544        )
545
546
547class IApplicantSessionCredentials(Interface):
548    """Interface for storing and accessing applicant credentials in a
549       session.
550    """
551
552    def __init__(access_code):
553        """Create applicant session credentials."""
554
555    def getAccessCode():
556        """Return the access code."""
557
558
559class IApplicantsContainerProvider(Interface):
560    """A provider for applicants containers.
561
562    Applicants container providers are meant to be looked up as
563    utilities. This way we can find all applicant container types
564    defined somewhere.
565
566    Each applicants container provider registered as utility provides
567    one container type and one should be able to call the `factory`
568    attribute to create an instance of the requested container type.
569
570    .. THE FOLLOWING SHOULD GO INTO SPHINX DOCS (and be tested)
571
572    Samples
573    *******
574
575    Given, you had an IApplicantsContainer implementation somewhere
576    and you would like to make it findable on request, then you would
577    normally create an appropriate provider utility like this::
578
579      import grok
580      from waeup.sirp.applicants.interfaces import IApplicantsContainerProvider
581
582      class MyContainerProvider(grok.GlobalUtility):
583          grok.implements(IApplicantsContainerProvider)
584          grok.name('MyContainerProvider') # Must be unique
585          factory = MyContainer # A class implementing IApplicantsContainer
586                                # or derivations thereof.
587
588    This utility would be registered on startup and could then be used
589    like this:
590
591      >>> from zope.component import getAllUtilitiesRegisteredFor
592      >>> from waeup.sirp.applicants.interfaces import (
593      ...     IApplicantsContainerProvider)
594      >>> all_providers = getAllUtilitiesRegisteredFor(
595      ...     IApplicantsContainerProvider)
596      >>> all_providers
597      [<MyContainerProvider object at 0x...>]
598
599    You could look up this specific provider by name:
600
601      >>> from zope.component import getUtility
602      >>> p = getUtility(IApplicantsContainerProvider, name='MyProvider')
603      >>> p
604      <MyContainerProvider object at 0x...>
605
606    An applicants container would then be created like this:
607
608      >>> provider = all_providers[0]
609      >>> container = provider.factory()
610      >>> container
611      <MyContainer object at 0x...>
612
613    """
614    factory = Attribute("A class that can create instances of the "
615                        "requested container type")
Note: See TracBrowser for help on using the repository browser.