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

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

Increase MAX_UPLOAD_SIZE, 20 kB is more realistic.

We can import DEFAULT_PASSPORT_IMAGE_MALE directly instead of IMAGE_PATH. That means configuration is solely done in interfaces.py not in browser.py.

DEFAULT_PASSPORT_IMAGE_FEMALE is no longer used.

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