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

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

Set up utils for application section (not yet used).

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