source: main/waeup.sirp/branches/ulif-schoolgrades/src/waeup/sirp/applicants/interfaces.py @ 7773

Last change on this file since 7773 was 7773, checked in by uli, 13 years ago

Move IResultEntry to central interfaces modules.

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