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

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

Adjust field titles.

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