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

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

Let's call the registration number reg_number instead of reg_no, like in students. Issoufou: This requires an update of the plugins on the portal configuration page.

  • Property svn:keywords set to Id
File size: 15.9 KB
Line 
1## $Id: interfaces.py 7270 2011-12-04 17:50:45Z henrik $
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 ValidationError, ISource, IContextSourceBinder
28from zc.sourcefactory.basic import BasicSourceFactory
29from waeup.sirp.schema import TextLineChoice
30from waeup.sirp.interfaces import (
31    IWAeUPObject, year_range, validate_email, academic_sessions_vocab)
32from waeup.sirp.university.vocabularies import application_categories
33from waeup.sirp.students.vocabularies import (
34  lgas_vocab, CertificateSource, GenderSource,
35  )
36from waeup.sirp.applicants.vocabularies import (
37  application_types_vocab, application_pins_vocab,
38  AppCatCertificateSource,
39  )
40from waeup.sirp.payments.interfaces import IOnlinePayment
41
42#: Maximum upload size for applicant passport photographs (in bytes)
43MAX_UPLOAD_SIZE = 1024 * 20
44
45class RegNumInSource(ValidationError):
46    """Registration number exists already
47    """
48    # The docstring of ValidationErrors is used as error description
49    # by zope.formlib.
50    pass
51
52class RegNumberSource(object):
53    implements(ISource)
54    cat_name = 'applicants_catalog'
55    field_name = 'reg_number'
56    validation_error = RegNumInSource
57    def __init__(self, context):
58        self.context = context
59        return
60
61    def __contains__(self, value):
62        cat = queryUtility(ICatalog, self.cat_name)
63        if cat is None:
64            return True
65        kw = {self.field_name: (value, value)}
66        results = cat.searchResults(**kw)
67        for entry in results:
68            if entry.applicant_id != self.context.applicant_id:
69                # XXX: sources should simply return False.
70                #      But then we get some stupid error message in forms
71                #      when validation fails.
72                raise self.validation_error(value)
73                #return False
74        return True
75
76def contextual_reg_num_source(context):
77    source = RegNumberSource(context)
78    return source
79directlyProvides(contextual_reg_num_source, IContextSourceBinder)
80
81class ApplicantContainerProviderSource(BasicSourceFactory):
82    """A source offering all available applicants container types.
83
84    The values returned by this source are names of utilities that can
85    create :class:`ApplicantContainer` instances. So, if you get a
86    name like ``'myactype'`` from this source, then you can do:
87
88      >>> from zope.component import getUtility
89      >>> p = getUtility(IApplicantsContainerProvider, name=myactype)
90      >>> my_applicants_container = p.factory()
91
92    Or you can access class-attributes like
93
94      >>> my_applicants_container.container_title
95      'Pretty'
96
97    """
98    def getValues(self):
99        """Returns a list of ``(<name>, <provider>)`` tuples.
100
101        Here ``<name>`` is the name under which an
102        :class:``ApplicantContainerProvider`` was registered as a
103        utility and ``<provider>`` is the utility itself.
104        """
105        return getUtilitiesFor(IApplicantsContainerProvider)
106
107    def getToken(self, value):
108        """Return the name of the ``(<name>, <provider>)`` tuple.
109        """
110        return value[0]
111
112    def getTitle(self, value):
113        """Get a 'title - description' string for a container type.
114        """
115        factory = value[1].factory
116        return "%s - %s" % (
117            factory.container_title, factory.container_description)
118
119class IApplicantsRoot(IWAeUPObject, IContainer):
120    """A container for university applicants containers.
121    """
122    pass
123
124class IApplicantsContainer(IWAeUPObject):
125    """An applicants container contains university applicants.
126
127    """
128
129    container_title = Attribute(
130        u'classattribute: title for type of container')
131    container_description = Attribute(
132        u'classattribute: description for type of container')
133
134
135    code = schema.TextLine(
136        title = u'Code',
137        default = u'-',
138        required = True,
139        readonly = True,
140        )
141
142    title = schema.TextLine(
143        title = u'Title',
144        required = True,
145        default = u'-',
146        readonly = True,
147        )
148
149    prefix = schema.Choice(
150        title = u'Application target',
151        required = True,
152        default = None,
153        source = application_types_vocab,
154        readonly = True,
155        )
156
157    year = schema.Choice(
158        title = u'Year of entrance',
159        required = True,
160        default = None,
161        values = year_range(),
162        readonly = True,
163        )
164
165    provider = schema.Choice(
166        title = u'Applicants container type',
167        required = True,
168        default = None,
169        source = ApplicantContainerProviderSource(),
170        readonly = True,
171        )
172
173    ac_prefix = schema.Choice(
174        title = u'Access code prefix',
175        required = True,
176        default = None,
177        source = application_pins_vocab,
178        )
179
180    application_category = schema.Choice(
181        title = u'Category for the grouping of certificates',
182        required = True,
183        default = None,
184        source = application_categories,
185        )
186
187    description = schema.Text(
188        title = u'Human readable description in reST format',
189        required = False,
190        default = u'''This text can been seen by anonymous users.
191Here we put information about the study courses provided, the application procedure and deadlines.'''
192        )
193
194    startdate = schema.Date(
195        title = u'Application start date',
196        required = False,
197        default = None,
198        )
199
200    enddate = schema.Date(
201        title = u'Application closing date',
202        required = False,
203        default = None,
204        )
205
206    strict_deadline = schema.Bool(
207        title = u'Forbid additions after deadline (enddate)',
208        required = True,
209        default = True,
210        )
211
212    def archive(id=None):
213        """Create on-dist archive of applicants stored in this term.
214
215        If id is `None`, all applicants are archived.
216
217        If id contains a single id string, only the respective
218        applicants are archived.
219
220        If id contains a list of id strings all of the respective
221        applicants types are saved to disk.
222        """
223
224    def clear(id=None, archive=True):
225        """Remove applicants of type given by 'id'.
226
227        Optionally archive the applicants.
228
229        If id is `None`, all applicants are archived.
230
231        If id contains a single id string, only the respective
232        applicants are archived.
233
234        If id contains a list of id strings all of the respective
235        applicant types are saved to disk.
236
237        If `archive` is ``False`` none of the archive-handling is done
238        and respective applicants are simply removed from the
239        database.
240        """
241
242class IApplicantsContainerAdd(IApplicantsContainer):
243    """An applicants container contains university applicants.
244    """
245    prefix = schema.Choice(
246        title = u'Application target',
247        required = True,
248        default = None,
249        source = application_types_vocab,
250        readonly = False,
251        )
252
253    year = schema.Choice(
254        title = u'Year of entrance',
255        required = True,
256        default = None,
257        values = year_range(),
258        readonly = False,
259        )
260
261    provider = schema.Choice(
262        title = u'Applicants container type',
263        required = True,
264        default = None,
265        source = ApplicantContainerProviderSource(),
266        readonly = False,
267        )
268
269IApplicantsContainerAdd[
270    'prefix'].order =  IApplicantsContainer['prefix'].order
271IApplicantsContainerAdd[
272    'year'].order =  IApplicantsContainer['year'].order
273IApplicantsContainerAdd[
274    'provider'].order =  IApplicantsContainer['provider'].order
275
276class IApplicantBaseData(IWAeUPObject):
277    """The data for an applicant.
278
279    This is a base interface with no field
280    required. For use with importers, forms, etc., please use one of
281    the derived interfaces below, which set more fields to required
282    state, depending on use-case.
283    """
284    history = Attribute('Object history, a list of messages.')
285    state = Attribute('The application state of an applicant')
286    fullname = Attribute('The fullname of an applicant')
287    application_date = Attribute('Date of submission, used for export only')
288    password = Attribute('Encrypted password of a applicant')
289    application_number = Attribute('The key under which the record is stored')
290
291    def loggerInfo(ob_class, comment):
292        """Adds an INFO message to the log file
293        """
294
295    applicant_id = schema.TextLine(
296        title = u'Applicant Id',
297        required = False,
298        readonly = False,
299        )
300    reg_number = TextLineChoice(
301        title = u'JAMB Registration Number',
302        readonly = False,
303        required = True,
304        default = None,
305        source = contextual_reg_num_source,
306        )
307    access_code = schema.TextLine(
308        title = u'Access Code',
309        required = False,
310        readonly = True,
311        )
312    firstname = schema.TextLine(
313        title = u'First Name',
314        required = True,
315        )
316    middlenames = schema.TextLine(
317        title = u'Middle Names',
318        required = False,
319        )
320    lastname = schema.TextLine(
321        title = u'Last Name (Surname)',
322        required = True,
323        )
324    date_of_birth = schema.Date(
325        title = u'Date of Birth',
326        required = True,
327        )
328    lga = schema.Choice(
329        source = lgas_vocab,
330        title = u'State/LGA',
331        default = 'foreigner',
332        required = True,
333        )
334    sex = schema.Choice(
335        title = u'Sex',
336        source = GenderSource(),
337        default = u'm',
338        required = True,
339        )
340    email = schema.ASCIILine(
341        title = u'Email',
342        required = True,
343        constraint=validate_email,
344        )
345    phone = schema.Int(
346        title = u'Phone',
347        description = u'Enter phone number with country code and without spaces.',
348        required = False,
349        )
350    course1 = schema.Choice(
351        title = u'1st Choice Course of Study',
352        source = CertificateSource(),
353        required = True,
354        )
355    course2 = schema.Choice(
356        title = u'2nd Choice Course of Study',
357        source = CertificateSource(),
358        required = False,
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. Or we can allow only certain certificates to be
406    selected by choosing the appropriate source.
407
408    We cannot omit fields here. This has to be done in the
409    respective form page.
410    """
411
412    course1 = schema.Choice(
413        title = u'1st Choice Course of Study',
414        source = AppCatCertificateSource(),
415        required = True,
416        )
417    course2 = schema.Choice(
418        title = u'2nd Choice Course of Study',
419        source = AppCatCertificateSource(),
420        required = False,
421        )
422    screening_score = schema.Int(
423        title = u'Screening Score',
424        required = False,
425        readonly = True,
426        )
427    screening_venue = schema.TextLine(
428        title = u'Screening Venue',
429        required = False,
430        readonly = True,
431        )
432    course_admitted = schema.Choice(
433        title = u'Admitted Course of Study',
434        source = CertificateSource(),
435        default = None,
436        required = False,
437        readonly = True,
438        )
439    notice = schema.Text(
440        title = u'Notice',
441        required = False,
442        readonly = True,
443        )
444
445class IApplicantUpdateByRegNo(IApplicant):
446    """Representation of an applicant.
447
448    Skip regular reg_number validation if reg_number is used for finding
449    the applicant object.
450    """
451    reg_number = schema.TextLine(
452        title = u'Registration Number',
453        default = None,
454        required = False,
455        )
456
457class IApplicantOnlinePayment(IOnlinePayment):
458    """An applicant payment via payment gateways.
459
460    """
461    p_year = schema.Choice(
462        title = u'Payment Session',
463        source = academic_sessions_vocab,
464        required = False,
465        )
466
467IApplicantOnlinePayment['p_year'].order = IApplicantOnlinePayment[
468    'p_year'].order
469
470class IApplicantsContainerProvider(Interface):
471    """A provider for applicants containers.
472
473    Applicants container providers are meant to be looked up as
474    utilities. This way we can find all applicant container types
475    defined somewhere.
476
477    Each applicants container provider registered as utility provides
478    one container type and one should be able to call the `factory`
479    attribute to create an instance of the requested container type.
480
481    .. THE FOLLOWING SHOULD GO INTO SPHINX DOCS (and be tested)
482
483    Samples:
484
485    Given, you had an IApplicantsContainer implementation somewhere
486    and you would like to make it findable on request, then you would
487    normally create an appropriate provider utility like this::
488
489      import grok
490      from waeup.sirp.applicants.interfaces import IApplicantsContainerProvider
491
492      class MyContainerProvider(grok.GlobalUtility):
493          grok.implements(IApplicantsContainerProvider)
494          grok.name('MyContainerProvider') # Must be unique
495          factory = MyContainer # A class implementing IApplicantsContainer
496                                # or derivations thereof.
497
498    This utility would be registered on startup and could then be used
499    like this:
500
501      >>> from zope.component import getAllUtilitiesRegisteredFor
502      >>> from waeup.sirp.applicants.interfaces import (
503      ...     IApplicantsContainerProvider)
504      >>> all_providers = getAllUtilitiesRegisteredFor(
505      ...     IApplicantsContainerProvider)
506      >>> all_providers
507      [<MyContainerProvider object at 0x...>]
508
509    You could look up this specific provider by name:
510
511      >>> from zope.component import getUtility
512      >>> p = getUtility(IApplicantsContainerProvider, name='MyProvider')
513      >>> p
514      <MyContainerProvider object at 0x...>
515
516    An applicants container would then be created like this:
517
518      >>> provider = all_providers[0]
519      >>> container = provider.factory()
520      >>> container
521      <MyContainer object at 0x...>
522
523    """
524    factory = Attribute("A class that can create instances of the "
525                        "requested container type")
Note: See TracBrowser for help on using the repository browser.