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

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

Do not store a reference to a certificate but a dictionary with the code, title, department title and faculty title of certificates. In this way we can preserve the information even if the certificate has been removed.

The assignment of dynamic roles is not necessary in the application section. We can assign local roles in applicants containers. That's sufficient.

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