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

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

The filed title should be 'Email Address' not only 'Email'.

  • Property svn:keywords set to Id
File size: 16.5 KB
Line 
1## $Id: interfaces.py 7555 2012-02-02 08:43:25Z 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    application_categories, course_levels)
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 IApplicantsRoot(ISIRPObject, IContainer):
119    """A container for university applicants containers.
120    """
121    pass
122
123class IApplicantsContainer(ISIRPObject):
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    entry_level = schema.Choice(
157        title = u'Entry Level',
158        vocabulary = course_levels,
159        default = 100,
160        required = True,
161        )
162
163    year = schema.Choice(
164        title = u'Year of entrance',
165        required = True,
166        default = None,
167        values = year_range(),
168        readonly = True,
169        )
170
171    provider = schema.Choice(
172        title = u'Applicants container type',
173        required = True,
174        default = None,
175        source = ApplicantContainerProviderSource(),
176        readonly = True,
177        )
178
179    #ac_prefix = schema.Choice(
180    #    title = u'Activation code prefix',
181    #    required = True,
182    #    default = None,
183    #    source = application_pins_vocab,
184    #    )
185
186    application_category = schema.Choice(
187        title = u'Category for the grouping of certificates',
188        required = True,
189        default = None,
190        source = application_categories,
191        )
192
193    description = schema.Text(
194        title = u'Human readable description in reST format',
195        required = False,
196        default = u'''This text can been seen by anonymous users.
197Here we put information about the study courses provided, the application procedure and deadlines.'''
198        )
199
200    startdate = schema.Date(
201        title = u'Application start date',
202        required = False,
203        default = None,
204        )
205
206    enddate = schema.Date(
207        title = u'Application closing date',
208        required = False,
209        default = None,
210        )
211
212    strict_deadline = schema.Bool(
213        title = u'Forbid additions after deadline (enddate)',
214        required = True,
215        default = True,
216        )
217
218    def archive(id=None):
219        """Create on-dist archive of applicants stored in this term.
220
221        If id is `None`, all applicants are archived.
222
223        If id contains a single id string, only the respective
224        applicants are archived.
225
226        If id contains a list of id strings all of the respective
227        applicants types are saved to disk.
228        """
229
230    def clear(id=None, archive=True):
231        """Remove applicants of type given by 'id'.
232
233        Optionally archive the applicants.
234
235        If id is `None`, all applicants are archived.
236
237        If id contains a single id string, only the respective
238        applicants are archived.
239
240        If id contains a list of id strings all of the respective
241        applicant types are saved to disk.
242
243        If `archive` is ``False`` none of the archive-handling is done
244        and respective applicants are simply removed from the
245        database.
246        """
247
248class IApplicantsContainerAdd(IApplicantsContainer):
249    """An applicants container contains university applicants.
250    """
251    prefix = schema.Choice(
252        title = u'Application target',
253        required = True,
254        default = None,
255        source = application_types_vocab,
256        readonly = False,
257        )
258
259    year = schema.Choice(
260        title = u'Year of entrance',
261        required = True,
262        default = None,
263        values = year_range(),
264        readonly = False,
265        )
266
267    provider = schema.Choice(
268        title = u'Applicants container type',
269        required = True,
270        default = None,
271        source = ApplicantContainerProviderSource(),
272        readonly = False,
273        )
274
275IApplicantsContainerAdd[
276    'prefix'].order =  IApplicantsContainer['prefix'].order
277IApplicantsContainerAdd[
278    'year'].order =  IApplicantsContainer['year'].order
279IApplicantsContainerAdd[
280    'provider'].order =  IApplicantsContainer['provider'].order
281
282class IApplicantBaseData(ISIRPObject):
283    """The data for an applicant.
284
285    This is a base interface with no field
286    required. For use with importers, forms, etc., please use one of
287    the derived interfaces below, which set more fields to required
288    state, depending on use-case.
289
290    This base interface is also implemented by the StudentApplication
291    class in the students package. Thus, these are the data which are saved
292    after admission.
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'Activation Code',
309    #    required = False,
310    #    readonly = True,
311    #    )
312    firstname = schema.TextLine(
313        title = u'First Name',
314        required = True,
315        )
316    middlename = schema.TextLine(
317        title = u'Middle Name',
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 Address',
342        required = True,
343        constraint=validate_email,
344        )
345    phone = schema.TextLine(
346        title = u'Phone',
347        description = u'',
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    # Data to be imported after screening
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
383class IApplicantProcessData(IApplicantBaseData):
384    """An applicant.
385
386    Here we add process attributes and methods to the base data.
387    """
388
389    history = Attribute('Object history, a list of messages.')
390    state = Attribute('The application state of an applicant')
391    display_fullname = Attribute('The fullname of an applicant')
392    application_date = Attribute('Date of submission, used for export only')
393    password = Attribute('Encrypted password of a applicant')
394    application_number = Attribute('The key under which the record is stored')
395
396    def loggerInfo(ob_class, comment):
397        """Adds an INFO message to the log file
398        """
399
400    student_id = schema.TextLine(
401        title = u'Student Id',
402        required = False,
403        readonly = False,
404        )
405    locked = schema.Bool(
406        title = u'Form locked',
407        default = False,
408        )
409
410class IApplicant(IApplicantProcessData):
411    """An applicant.
412
413    This is basically the applicant base data. Here we repeat the
414    fields from base data if we have to set the `required` attribute
415    to True (which is the default).
416    """
417
418class IApplicantEdit(IApplicantProcessData):
419    """An applicant.
420
421    Here we can repeat the fields from base data and set the
422    `required` and `readonly` attributes to True to further restrict
423    the data access. Or we can allow only certain certificates to be
424    selected by choosing the appropriate source.
425
426    We cannot omit fields here. This has to be done in the
427    respective form page.
428    """
429
430    course1 = schema.Choice(
431        title = u'1st Choice Course of Study',
432        source = AppCatCertificateSource(),
433        required = True,
434        )
435    course2 = schema.Choice(
436        title = u'2nd Choice Course of Study',
437        source = AppCatCertificateSource(),
438        required = False,
439        )
440    screening_score = schema.Int(
441        title = u'Screening Score',
442        required = False,
443        readonly = True,
444        )
445    screening_venue = schema.TextLine(
446        title = u'Screening Venue',
447        required = False,
448        readonly = True,
449        )
450    course_admitted = schema.Choice(
451        title = u'Admitted Course of Study',
452        source = CertificateSource(),
453        default = None,
454        required = False,
455        readonly = True,
456        )
457    notice = schema.Text(
458        title = u'Notice',
459        required = False,
460        readonly = True,
461        )
462
463    def createStudent():
464        """Create a student object from applicatnt data
465        and copy applicant object.
466        """
467
468class IApplicantUpdateByRegNo(IApplicant):
469    """Representation of an applicant.
470
471    Skip regular reg_number validation if reg_number is used for finding
472    the applicant object.
473    """
474    reg_number = schema.TextLine(
475        title = u'Registration Number',
476        default = None,
477        required = False,
478        )
479
480class IApplicantOnlinePayment(IOnlinePayment):
481    """An applicant payment via payment gateways.
482
483    """
484    p_year = schema.Choice(
485        title = u'Payment Session',
486        source = academic_sessions_vocab,
487        required = False,
488        )
489
490IApplicantOnlinePayment['p_year'].order = IApplicantOnlinePayment[
491    'p_year'].order
492
493class IApplicantsContainerProvider(Interface):
494    """A provider for applicants containers.
495
496    Applicants container providers are meant to be looked up as
497    utilities. This way we can find all applicant container types
498    defined somewhere.
499
500    Each applicants container provider registered as utility provides
501    one container type and one should be able to call the `factory`
502    attribute to create an instance of the requested container type.
503
504    .. THE FOLLOWING SHOULD GO INTO SPHINX DOCS (and be tested)
505
506    Samples:
507
508    Given, you had an IApplicantsContainer implementation somewhere
509    and you would like to make it findable on request, then you would
510    normally create an appropriate provider utility like this::
511
512      import grok
513      from waeup.sirp.applicants.interfaces import IApplicantsContainerProvider
514
515      class MyContainerProvider(grok.GlobalUtility):
516          grok.implements(IApplicantsContainerProvider)
517          grok.name('MyContainerProvider') # Must be unique
518          factory = MyContainer # A class implementing IApplicantsContainer
519                                # or derivations thereof.
520
521    This utility would be registered on startup and could then be used
522    like this:
523
524      >>> from zope.component import getAllUtilitiesRegisteredFor
525      >>> from waeup.sirp.applicants.interfaces import (
526      ...     IApplicantsContainerProvider)
527      >>> all_providers = getAllUtilitiesRegisteredFor(
528      ...     IApplicantsContainerProvider)
529      >>> all_providers
530      [<MyContainerProvider object at 0x...>]
531
532    You could look up this specific provider by name:
533
534      >>> from zope.component import getUtility
535      >>> p = getUtility(IApplicantsContainerProvider, name='MyProvider')
536      >>> p
537      <MyContainerProvider object at 0x...>
538
539    An applicants container would then be created like this:
540
541      >>> provider = all_providers[0]
542      >>> container = provider.factory()
543      >>> container
544      <MyContainer object at 0x...>
545
546    """
547    factory = Attribute("A class that can create instances of the "
548                        "requested container type")
Note: See TracBrowser for help on using the repository browser.