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

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

Define two different (convenience?) methods: fullname and display_fullname. The first one is a hyphen-separated string of all name parts and is meant for indexing only. The second one uses the SIRPUtils fullname method and is meant for displaying the fullname in the UI. It can be easily customized according to the requirements of the school.

  • Property svn:keywords set to Id
File size: 16.3 KB
Line 
1## $Id: interfaces.py 7364 2011-12-17 12:54:39Z 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 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(ISIRPObject, IContainer):
121    """A container for university applicants containers.
122    """
123    pass
124
125class IApplicantsContainer(ISIRPObject):
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(ISIRPObject):
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    This base interface is also implemented by the StudentApplication
286    class in the students package. Thus, these are the data which are saved
287    after admission.
288    """
289
290    applicant_id = schema.TextLine(
291        title = u'Applicant Id',
292        required = False,
293        readonly = False,
294        )
295    reg_number = TextLineChoice(
296        title = u'JAMB Registration Number',
297        readonly = False,
298        required = True,
299        default = None,
300        source = contextual_reg_num_source,
301        )
302    access_code = schema.TextLine(
303        title = u'Access Code',
304        required = False,
305        readonly = True,
306        )
307    firstname = schema.TextLine(
308        title = u'First Name',
309        required = True,
310        )
311    middlename = schema.TextLine(
312        title = u'Middle Name',
313        required = False,
314        )
315    lastname = schema.TextLine(
316        title = u'Last Name (Surname)',
317        required = True,
318        )
319    date_of_birth = schema.Date(
320        title = u'Date of Birth',
321        required = True,
322        )
323    lga = schema.Choice(
324        source = lgas_vocab,
325        title = u'State/LGA',
326        default = 'foreigner',
327        required = True,
328        )
329    sex = schema.Choice(
330        title = u'Sex',
331        source = GenderSource(),
332        default = u'm',
333        required = True,
334        )
335    email = schema.ASCIILine(
336        title = u'Email',
337        required = True,
338        constraint=validate_email,
339        )
340    phone = schema.TextLine(
341        title = u'Phone',
342        description = u'',
343        required = False,
344        )
345    course1 = schema.Choice(
346        title = u'1st Choice Course of Study',
347        source = CertificateSource(),
348        required = True,
349        )
350    course2 = schema.Choice(
351        title = u'2nd Choice Course of Study',
352        source = CertificateSource(),
353        required = False,
354        )
355
356    #
357    # Data to be imported after screening
358    #
359    screening_score = schema.Int(
360        title = u'Screening Score',
361        required = False,
362        )
363    screening_venue = schema.TextLine(
364        title = u'Screening Venue',
365        required = False,
366        )
367    course_admitted = schema.Choice(
368        title = u'Admitted Course of Study',
369        source = CertificateSource(),
370        default = None,
371        required = False,
372        )
373    notice = schema.Text(
374        title = u'Notice',
375        required = False,
376        )
377
378class IApplicantProcessData(IApplicantBaseData):
379    """An applicant.
380
381    Here we add process attributes and methods to the base data.
382    """
383
384    history = Attribute('Object history, a list of messages.')
385    state = Attribute('The application state of an applicant')
386    display_fullname = Attribute('The fullname of an applicant')
387    application_date = Attribute('Date of submission, used for export only')
388    password = Attribute('Encrypted password of a applicant')
389    application_number = Attribute('The key under which the record is stored')
390
391    def loggerInfo(ob_class, comment):
392        """Adds an INFO message to the log file
393        """
394
395    student_id = schema.TextLine(
396        title = u'Student Id',
397        required = False,
398        readonly = False,
399        )
400    locked = schema.Bool(
401        title = u'Form locked',
402        default = False,
403        )
404
405class IApplicant(IApplicantProcessData):
406    """An applicant.
407
408    This is basically the applicant base data. Here we repeat the
409    fields from base data if we have to set the `required` attribute
410    to True (which is the default).
411    """
412
413class IApplicantEdit(IApplicantProcessData):
414    """An applicant.
415
416    Here we can repeat the fields from base data and set the
417    `required` and `readonly` attributes to True to further restrict
418    the data access. Or we can allow only certain certificates to be
419    selected by choosing the appropriate source.
420
421    We cannot omit fields here. This has to be done in the
422    respective form page.
423    """
424
425    course1 = schema.Choice(
426        title = u'1st Choice Course of Study',
427        source = AppCatCertificateSource(),
428        required = True,
429        )
430    course2 = schema.Choice(
431        title = u'2nd Choice Course of Study',
432        source = AppCatCertificateSource(),
433        required = False,
434        )
435    screening_score = schema.Int(
436        title = u'Screening Score',
437        required = False,
438        readonly = True,
439        )
440    screening_venue = schema.TextLine(
441        title = u'Screening Venue',
442        required = False,
443        readonly = True,
444        )
445    course_admitted = schema.Choice(
446        title = u'Admitted Course of Study',
447        source = CertificateSource(),
448        default = None,
449        required = False,
450        readonly = True,
451        )
452    notice = schema.Text(
453        title = u'Notice',
454        required = False,
455        readonly = True,
456        )
457
458    def createStudent():
459        """Create a student object from applicatnt data
460        and copy applicant object.
461        """
462
463class IApplicantUpdateByRegNo(IApplicant):
464    """Representation of an applicant.
465
466    Skip regular reg_number validation if reg_number is used for finding
467    the applicant object.
468    """
469    reg_number = schema.TextLine(
470        title = u'Registration Number',
471        default = None,
472        required = False,
473        )
474
475class IApplicantOnlinePayment(IOnlinePayment):
476    """An applicant payment via payment gateways.
477
478    """
479    p_year = schema.Choice(
480        title = u'Payment Session',
481        source = academic_sessions_vocab,
482        required = False,
483        )
484
485IApplicantOnlinePayment['p_year'].order = IApplicantOnlinePayment[
486    'p_year'].order
487
488class IApplicantsContainerProvider(Interface):
489    """A provider for applicants containers.
490
491    Applicants container providers are meant to be looked up as
492    utilities. This way we can find all applicant container types
493    defined somewhere.
494
495    Each applicants container provider registered as utility provides
496    one container type and one should be able to call the `factory`
497    attribute to create an instance of the requested container type.
498
499    .. THE FOLLOWING SHOULD GO INTO SPHINX DOCS (and be tested)
500
501    Samples:
502
503    Given, you had an IApplicantsContainer implementation somewhere
504    and you would like to make it findable on request, then you would
505    normally create an appropriate provider utility like this::
506
507      import grok
508      from waeup.sirp.applicants.interfaces import IApplicantsContainerProvider
509
510      class MyContainerProvider(grok.GlobalUtility):
511          grok.implements(IApplicantsContainerProvider)
512          grok.name('MyContainerProvider') # Must be unique
513          factory = MyContainer # A class implementing IApplicantsContainer
514                                # or derivations thereof.
515
516    This utility would be registered on startup and could then be used
517    like this:
518
519      >>> from zope.component import getAllUtilitiesRegisteredFor
520      >>> from waeup.sirp.applicants.interfaces import (
521      ...     IApplicantsContainerProvider)
522      >>> all_providers = getAllUtilitiesRegisteredFor(
523      ...     IApplicantsContainerProvider)
524      >>> all_providers
525      [<MyContainerProvider object at 0x...>]
526
527    You could look up this specific provider by name:
528
529      >>> from zope.component import getUtility
530      >>> p = getUtility(IApplicantsContainerProvider, name='MyProvider')
531      >>> p
532      <MyContainerProvider object at 0x...>
533
534    An applicants container would then be created like this:
535
536      >>> provider = all_providers[0]
537      >>> container = provider.factory()
538      >>> container
539      <MyContainer object at 0x...>
540
541    """
542    factory = Attribute("A class that can create instances of the "
543                        "requested container type")
Note: See TracBrowser for help on using the repository browser.