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

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

Fix formatting.

  • Property svn:keywords set to Id
File size: 15.9 KB
Line 
1## $Id: interfaces.py 7317 2011-12-09 10:53:26Z uli $
2##
3## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
4## This program is free software; you can redistribute it and/or modify
5## it under the terms of the GNU General Public License as published by
6## the Free Software Foundation; either version 2 of the License, or
7## (at your option) any later version.
8##
9## This program is distributed in the hope that it will be useful,
10## but WITHOUT ANY WARRANTY; without even the implied warranty of
11## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12## GNU General Public License for more details.
13##
14## You should have received a copy of the GNU General Public License
15## along with this program; if not, write to the Free Software
16## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17##
18"""Interfaces of the university application package.
19"""
20
21from grokcore.content.interfaces import IContainer
22
23from zope import schema
24from zope.interface import Interface, Attribute, implements, directlyProvides
25from zope.component import getUtilitiesFor, queryUtility
26from zope.catalog.interfaces import ICatalog
27from zope.schema.interfaces import (
28    ValidationError, ISource, IContextSourceBinder)
29from zc.sourcefactory.basic import BasicSourceFactory
30from waeup.sirp.schema import TextLineChoice
31from waeup.sirp.interfaces import (
32    IWAeUPObject, year_range, validate_email, academic_sessions_vocab)
33from waeup.sirp.university.vocabularies import application_categories
34from waeup.sirp.students.vocabularies import (
35  lgas_vocab, CertificateSource, GenderSource,
36  )
37from waeup.sirp.applicants.vocabularies import (
38  application_types_vocab, application_pins_vocab,
39  AppCatCertificateSource,
40  )
41from waeup.sirp.payments.interfaces import IOnlinePayment
42
43#: Maximum upload size for applicant passport photographs (in bytes)
44MAX_UPLOAD_SIZE = 1024 * 20
45
46class RegNumInSource(ValidationError):
47    """Registration number exists already
48    """
49    # The docstring of ValidationErrors is used as error description
50    # by zope.formlib.
51    pass
52
53class RegNumberSource(object):
54    implements(ISource)
55    cat_name = 'applicants_catalog'
56    field_name = 'reg_number'
57    validation_error = RegNumInSource
58    def __init__(self, context):
59        self.context = context
60        return
61
62    def __contains__(self, value):
63        cat = queryUtility(ICatalog, self.cat_name)
64        if cat is None:
65            return True
66        kw = {self.field_name: (value, value)}
67        results = cat.searchResults(**kw)
68        for entry in results:
69            if entry.applicant_id != self.context.applicant_id:
70                # XXX: sources should simply return False.
71                #      But then we get some stupid error message in forms
72                #      when validation fails.
73                raise self.validation_error(value)
74                #return False
75        return True
76
77def contextual_reg_num_source(context):
78    source = RegNumberSource(context)
79    return source
80directlyProvides(contextual_reg_num_source, IContextSourceBinder)
81
82class ApplicantContainerProviderSource(BasicSourceFactory):
83    """A source offering all available applicants container types.
84
85    The values returned by this source are names of utilities that can
86    create :class:`ApplicantContainer` instances. So, if you get a
87    name like ``'myactype'`` from this source, then you can do:
88
89      >>> from zope.component import getUtility
90      >>> p = getUtility(IApplicantsContainerProvider, name=myactype)
91      >>> my_applicants_container = p.factory()
92
93    Or you can access class-attributes like
94
95      >>> my_applicants_container.container_title
96      'Pretty'
97
98    """
99    def getValues(self):
100        """Returns a list of ``(<name>, <provider>)`` tuples.
101
102        Here ``<name>`` is the name under which an
103        :class:``ApplicantContainerProvider`` was registered as a
104        utility and ``<provider>`` is the utility itself.
105        """
106        return getUtilitiesFor(IApplicantsContainerProvider)
107
108    def getToken(self, value):
109        """Return the name of the ``(<name>, <provider>)`` tuple.
110        """
111        return value[0]
112
113    def getTitle(self, value):
114        """Get a 'title - description' string for a container type.
115        """
116        factory = value[1].factory
117        return "%s - %s" % (
118            factory.container_title, factory.container_description)
119
120class IApplicantsRoot(IWAeUPObject, IContainer):
121    """A container for university applicants containers.
122    """
123    pass
124
125class IApplicantsContainer(IWAeUPObject):
126    """An applicants container contains university applicants.
127
128    """
129
130    container_title = Attribute(
131        u'classattribute: title for type of container')
132    container_description = Attribute(
133        u'classattribute: description for type of container')
134
135
136    code = schema.TextLine(
137        title = u'Code',
138        default = u'-',
139        required = True,
140        readonly = True,
141        )
142
143    title = schema.TextLine(
144        title = u'Title',
145        required = True,
146        default = u'-',
147        readonly = True,
148        )
149
150    prefix = schema.Choice(
151        title = u'Application target',
152        required = True,
153        default = None,
154        source = application_types_vocab,
155        readonly = True,
156        )
157
158    year = schema.Choice(
159        title = u'Year of entrance',
160        required = True,
161        default = None,
162        values = year_range(),
163        readonly = True,
164        )
165
166    provider = schema.Choice(
167        title = u'Applicants container type',
168        required = True,
169        default = None,
170        source = ApplicantContainerProviderSource(),
171        readonly = True,
172        )
173
174    ac_prefix = schema.Choice(
175        title = u'Access code prefix',
176        required = True,
177        default = None,
178        source = application_pins_vocab,
179        )
180
181    application_category = schema.Choice(
182        title = u'Category for the grouping of certificates',
183        required = True,
184        default = None,
185        source = application_categories,
186        )
187
188    description = schema.Text(
189        title = u'Human readable description in reST format',
190        required = False,
191        default = u'''This text can been seen by anonymous users.
192Here we put information about the study courses provided, the application procedure and deadlines.'''
193        )
194
195    startdate = schema.Date(
196        title = u'Application start date',
197        required = False,
198        default = None,
199        )
200
201    enddate = schema.Date(
202        title = u'Application closing date',
203        required = False,
204        default = None,
205        )
206
207    strict_deadline = schema.Bool(
208        title = u'Forbid additions after deadline (enddate)',
209        required = True,
210        default = True,
211        )
212
213    def archive(id=None):
214        """Create on-dist archive of applicants stored in this term.
215
216        If id is `None`, all applicants are archived.
217
218        If id contains a single id string, only the respective
219        applicants are archived.
220
221        If id contains a list of id strings all of the respective
222        applicants types are saved to disk.
223        """
224
225    def clear(id=None, archive=True):
226        """Remove applicants of type given by 'id'.
227
228        Optionally archive the applicants.
229
230        If id is `None`, all applicants are archived.
231
232        If id contains a single id string, only the respective
233        applicants are archived.
234
235        If id contains a list of id strings all of the respective
236        applicant types are saved to disk.
237
238        If `archive` is ``False`` none of the archive-handling is done
239        and respective applicants are simply removed from the
240        database.
241        """
242
243class IApplicantsContainerAdd(IApplicantsContainer):
244    """An applicants container contains university applicants.
245    """
246    prefix = schema.Choice(
247        title = u'Application target',
248        required = True,
249        default = None,
250        source = application_types_vocab,
251        readonly = False,
252        )
253
254    year = schema.Choice(
255        title = u'Year of entrance',
256        required = True,
257        default = None,
258        values = year_range(),
259        readonly = False,
260        )
261
262    provider = schema.Choice(
263        title = u'Applicants container type',
264        required = True,
265        default = None,
266        source = ApplicantContainerProviderSource(),
267        readonly = False,
268        )
269
270IApplicantsContainerAdd[
271    'prefix'].order =  IApplicantsContainer['prefix'].order
272IApplicantsContainerAdd[
273    'year'].order =  IApplicantsContainer['year'].order
274IApplicantsContainerAdd[
275    'provider'].order =  IApplicantsContainer['provider'].order
276
277class IApplicantBaseData(IWAeUPObject):
278    """The data for an applicant.
279
280    This is a base interface with no field
281    required. For use with importers, forms, etc., please use one of
282    the derived interfaces below, which set more fields to required
283    state, depending on use-case.
284    """
285    history = Attribute('Object history, a list of messages.')
286    state = Attribute('The application state of an applicant')
287    fullname = Attribute('The fullname of an applicant')
288    application_date = Attribute('Date of submission, used for export only')
289    password = Attribute('Encrypted password of a applicant')
290    application_number = Attribute('The key under which the record is stored')
291
292    def loggerInfo(ob_class, comment):
293        """Adds an INFO message to the log file
294        """
295
296    applicant_id = schema.TextLine(
297        title = u'Applicant Id',
298        required = False,
299        readonly = False,
300        )
301    reg_number = TextLineChoice(
302        title = u'JAMB Registration Number',
303        readonly = False,
304        required = True,
305        default = None,
306        source = contextual_reg_num_source,
307        )
308    access_code = schema.TextLine(
309        title = u'Access Code',
310        required = False,
311        readonly = True,
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 = True,
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    course1 = schema.Choice(
352        title = u'1st Choice Course of Study',
353        source = CertificateSource(),
354        required = True,
355        )
356    course2 = schema.Choice(
357        title = u'2nd Choice Course of Study',
358        source = CertificateSource(),
359        required = False,
360        )
361
362    #
363    # Process Data
364    #
365    screening_score = schema.Int(
366        title = u'Screening Score',
367        required = False,
368        )
369    screening_venue = schema.TextLine(
370        title = u'Screening Venue',
371        required = False,
372        )
373    course_admitted = schema.Choice(
374        title = u'Admitted Course of Study',
375        source = CertificateSource(),
376        default = None,
377        required = False,
378        )
379    notice = schema.Text(
380        title = u'Notice',
381        required = False,
382        )
383    student_id = schema.TextLine(
384        title = u'Student Id',
385        required = False,
386        readonly = True,
387        )
388    locked = schema.Bool(
389        title = u'Form locked',
390        default = False,
391        )
392
393class IApplicant(IApplicantBaseData):
394    """An applicant.
395
396    This is basically the applicant base data. Here we repeat the
397    fields from base data if we have to set the `required` attribute
398    to True (which is the default).
399    """
400
401class IApplicantEdit(IApplicantBaseData):
402    """An applicant.
403
404    Here we can repeat the fields from base data and set the
405    `required` and `readonly` attributes to True to further restrict
406    the data access. Or we can allow only certain certificates to be
407    selected by choosing the appropriate source.
408
409    We cannot omit fields here. This has to be done in the
410    respective form page.
411    """
412
413    course1 = schema.Choice(
414        title = u'1st Choice Course of Study',
415        source = AppCatCertificateSource(),
416        required = True,
417        )
418    course2 = schema.Choice(
419        title = u'2nd Choice Course of Study',
420        source = AppCatCertificateSource(),
421        required = False,
422        )
423    screening_score = schema.Int(
424        title = u'Screening Score',
425        required = False,
426        readonly = True,
427        )
428    screening_venue = schema.TextLine(
429        title = u'Screening Venue',
430        required = False,
431        readonly = True,
432        )
433    course_admitted = schema.Choice(
434        title = u'Admitted Course of Study',
435        source = CertificateSource(),
436        default = None,
437        required = False,
438        readonly = True,
439        )
440    notice = schema.Text(
441        title = u'Notice',
442        required = False,
443        readonly = True,
444        )
445
446class IApplicantUpdateByRegNo(IApplicant):
447    """Representation of an applicant.
448
449    Skip regular reg_number validation if reg_number is used for finding
450    the applicant object.
451    """
452    reg_number = schema.TextLine(
453        title = u'Registration Number',
454        default = None,
455        required = False,
456        )
457
458class IApplicantOnlinePayment(IOnlinePayment):
459    """An applicant payment via payment gateways.
460
461    """
462    p_year = schema.Choice(
463        title = u'Payment Session',
464        source = academic_sessions_vocab,
465        required = False,
466        )
467
468IApplicantOnlinePayment['p_year'].order = IApplicantOnlinePayment[
469    'p_year'].order
470
471class IApplicantsContainerProvider(Interface):
472    """A provider for applicants containers.
473
474    Applicants container providers are meant to be looked up as
475    utilities. This way we can find all applicant container types
476    defined somewhere.
477
478    Each applicants container provider registered as utility provides
479    one container type and one should be able to call the `factory`
480    attribute to create an instance of the requested container type.
481
482    .. THE FOLLOWING SHOULD GO INTO SPHINX DOCS (and be tested)
483
484    Samples:
485
486    Given, you had an IApplicantsContainer implementation somewhere
487    and you would like to make it findable on request, then you would
488    normally create an appropriate provider utility like this::
489
490      import grok
491      from waeup.sirp.applicants.interfaces import IApplicantsContainerProvider
492
493      class MyContainerProvider(grok.GlobalUtility):
494          grok.implements(IApplicantsContainerProvider)
495          grok.name('MyContainerProvider') # Must be unique
496          factory = MyContainer # A class implementing IApplicantsContainer
497                                # or derivations thereof.
498
499    This utility would be registered on startup and could then be used
500    like this:
501
502      >>> from zope.component import getAllUtilitiesRegisteredFor
503      >>> from waeup.sirp.applicants.interfaces import (
504      ...     IApplicantsContainerProvider)
505      >>> all_providers = getAllUtilitiesRegisteredFor(
506      ...     IApplicantsContainerProvider)
507      >>> all_providers
508      [<MyContainerProvider object at 0x...>]
509
510    You could look up this specific provider by name:
511
512      >>> from zope.component import getUtility
513      >>> p = getUtility(IApplicantsContainerProvider, name='MyProvider')
514      >>> p
515      <MyContainerProvider object at 0x...>
516
517    An applicants container would then be created like this:
518
519      >>> provider = all_providers[0]
520      >>> container = provider.factory()
521      >>> container
522      <MyContainer object at 0x...>
523
524    """
525    factory = Attribute("A class that can create instances of the "
526                        "requested container type")
Note: See TracBrowser for help on using the repository browser.