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

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

Use a contextual source for the selection of study courses (certificates).

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