source: main/waeup.kofa/trunk/src/waeup/kofa/applicants/interfaces.py @ 7997

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

Remove unneeded default values in interfaces. Adjust tests.

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