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

Last change on this file since 7090 was 7089, checked in by uli, 13 years ago

Use path to default passport image instead of open file descriptor.
If we would use a file descriptor, we had to make sure to reset it after
each read (seek(0), which did not happen) and we could run into trouble
when multiple requests/threads access the file at the same time.

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