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

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

Use APPLICATION_TYPES for both application_types_vocab and application_pins_vocab. This will ease the configuration and force officers not mix prefixes.

applicantscontainermanagepage.pt: Add missing colon.

File size: 24.7 KB
Line 
1##
2## interfaces.py
3## Login : <uli@pu.smp.net>
4## Started on  Sun Jan 16 15:30:01 2011 Uli Fouquet
5## $Id$
6##
7## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
8## This program is free software; you can redistribute it and/or modify
9## it under the terms of the GNU General Public License as published by
10## the Free Software Foundation; either version 2 of the License, or
11## (at your option) any later version.
12##
13## This program is distributed in the hope that it will be useful,
14## but WITHOUT ANY WARRANTY; without even the implied warranty of
15## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16## GNU General Public License for more details.
17##
18## You should have received a copy of the GNU General Public License
19## along with this program; if not, write to the Free Software
20## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21##
22"""Interfaces regarding student applicants and related components.
23"""
24import os
25import waeup.sirp.browser
26from datetime import datetime
27from grokcore.content.interfaces import IContainer
28from zc.sourcefactory.basic import BasicSourceFactory
29from zope import schema
30from zope.component import getUtility, getUtilitiesFor
31from zope.interface import Interface, Attribute
32from zope.pluggableauth.interfaces import IPrincipalInfo
33from zope.security.interfaces import IGroupClosureAwarePrincipal as IPrincipal
34from waeup.sirp.image.schema import ImageFile
35from waeup.sirp.image.image import WAeUPImageFile
36from waeup.sirp.interfaces import IWAeUPObject, SimpleWAeUPVocabulary
37
38
39IMAGE_PATH = os.path.join(
40    os.path.dirname(waeup.sirp.browser.__file__),
41    'static'
42    )
43DEFAULT_PASSPORT_IMAGE_MALE = WAeUPImageFile(
44    'passport.jpg',
45    open(os.path.join(IMAGE_PATH, 'placeholder_m.jpg')).read(),
46    )
47DEFAULT_PASSPORT_IMAGE_FEMALE = WAeUPImageFile(
48    'passport.jpg',
49    open(os.path.join(IMAGE_PATH, 'placeholder_f.jpg')).read(),
50    )
51
52#: Types of applications we support.
53APPLICATION_TYPES = (
54    ('General Studies', 'app','APP'),
55    ('Pre-NCE Programme', 'prence','PRE'),
56    ('Post UME Screening Test', 'pume','PUME'),
57    ('Post UDE Screening', 'pude','PUDE'),
58    ('Part Time Degree in Education', 'sandwich','SAND'),
59    ('Part-Time Degree Programmes', 'pt','PTP'),
60    ('Diploma Programmes', 'dp','DPP'),
61    ('PCE Screening', 'pce','PCE'),
62    ('Certificate Programmes', 'ct','CTP'),
63    ('Common Entry Screening Test', 'cest','CEST'),
64    )
65
66#: A :class:`waeup.sirp.interfaces.SimpleWAeUPVocabulary` of supported
67#: application or screening types.
68application_types_vocab = SimpleWAeUPVocabulary(*[(x[0],x[1]) for x in APPLICATION_TYPES])
69application_pins_vocab = SimpleWAeUPVocabulary(*[(u"%s (%s)" % (x[2],x[0]),x[2]) for x in APPLICATION_TYPES])
70
71def yeararrange():
72    curr_year = datetime.now().year
73    return range(curr_year - 2, curr_year + 5)
74
75class GenderSource(BasicSourceFactory):
76    """A gender source delivers basically a mapping
77       ``{'m': 'male', 'f': 'female'}``
78
79       Using a source, we make sure that the tokens (which are
80       stored/expected for instance from CSV files) are something one
81       can expect and not cryptic IntIDs.
82    """
83    def getValues(self):
84        return ['m', 'f']
85
86    def getToken(self, value):
87        return value[0].lower()
88
89    def getTitle(self, value):
90        if value == 'm':
91            return 'male'
92        if value == 'f':
93            return 'female'
94
95class ApplicantContainerProviderSource(BasicSourceFactory):
96    """A source offering all available applicants container types.
97
98    The values returned by this source are names of utilities that can
99    create :class:`ApplicantContainer` instances. So, if you get a
100    name like ``'myactype'`` from this source, then you can do:
101
102      >>> from zope.component import getUtility
103      >>> p = getUtility(IApplicantsContainerProvider, name=myactype)
104      >>> my_applicants_container = p.factory()
105
106    Or you can access class-attributes like
107
108      >>> my_applicants_container.container_title
109      'Pretty'
110
111    """
112    def getValues(self):
113        """Returns a list of ``(<name>, <provider>)`` tuples.
114
115        Here ``<name>`` is the name under which an
116        :class:``ApplicantContainerProvider`` was registered as a
117        utility and ``<provider>`` is the utility itself.
118        """
119        return getUtilitiesFor(IApplicantsContainerProvider)
120
121    def getToken(self, value):
122        """Return the name of the ``(<name>, <provider>)`` tuple.
123        """
124        return value[0]
125
126    def getTitle(self, value):
127        """Get a 'title - description' string for a container type.
128        """
129        factory = value[1].factory
130        return "%s - %s" % (
131            factory.container_title, factory.container_description)
132
133class IResultEntry(IWAeUPObject):
134    subject = schema.TextLine(
135        title = u'Subject',
136        description = u'The subject',
137        required=False,
138        )
139    score = schema.TextLine(
140        title = u'Score',
141        description = u'The score',
142        required=False,
143        )
144
145class IApplicantsRoot(IWAeUPObject, IContainer):
146    """A container for university applicants containers.
147    """
148    pass
149
150
151class IApplicantsContainer(IWAeUPObject):
152    """An applicants container contains university applicants.
153
154    """
155
156    container_title = Attribute(
157        u'classattribute: title for type of container')
158    container_description = Attribute(
159        u'classattribute: description for type of container')
160
161
162    code = schema.TextLine(
163        title = u'Code',
164        default = u'-',
165        required = True,
166        readonly = True,
167        )
168
169    title = schema.TextLine(
170        title = u'Title',
171        required = True,
172        default = u'-',
173        readonly = True,
174        )
175
176    prefix = schema.Choice(
177        title = u'Application target',
178        required = True,
179        default = None,
180        source = application_types_vocab,
181        readonly = True,
182        )
183
184    year = schema.Choice(
185        title = u'Year of entrance',
186        required = True,
187        default = None,
188        values = yeararrange(),
189        readonly = True,
190        )
191
192    provider = schema.Choice(
193        title = u'Applicants container type',
194        required = True,
195        default = None,
196        source = ApplicantContainerProviderSource(),
197        readonly = True,
198        )
199       
200    ac_prefix = schema.Choice(
201        title = u'Access code prefix',
202        required = True,
203        default = None,
204        source = application_pins_vocab,
205        )
206
207    description = schema.Text(
208        title = u'Human readable description in reST format',
209        required = False,
210        default = u'No description yet.'
211        )
212
213    startdate = schema.Date(
214        title = u'Date when the application period starts',
215        required = False,
216        default = None,
217        )
218
219    enddate = schema.Date(
220        title = u'Date when the application period ends',
221        required = False,
222        default = None,
223        )
224
225    strict_deadline = schema.Bool(
226        title = u'Forbid additions after deadline (enddate)',
227        required = True,
228        default = True,
229        )
230
231    def archive(id=None):
232        """Create on-dist archive of applicants stored in this term.
233
234        If id is `None`, all applicants are archived.
235
236        If id contains a single id string, only the respective
237        applicants are archived.
238
239        If id contains a list of id strings all of the respective
240        applicants types are saved to disk.
241        """
242
243    def clear(id=None, archive=True):
244        """Remove applicants of type given by 'id'.
245
246        Optionally archive the applicants.
247
248        If id is `None`, all applicants are archived.
249
250        If id contains a single id string, only the respective
251        applicants are archived.
252
253        If id contains a list of id strings all of the respective
254        applicant types are saved to disk.
255
256        If `archive` is ``False`` none of the archive-handling is done
257        and respective applicants are simply removed from the
258        database.
259        """
260
261class IApplicantsContainerAdd(IApplicantsContainer):
262    """An applicants container contains university applicants.
263    """
264    prefix = schema.Choice(
265        title = u'Application target',
266        required = True,
267        default = None,
268        source = application_types_vocab,
269        readonly = False,
270        )
271
272    year = schema.Choice(
273        title = u'Year of entrance',
274        required = True,
275        default = None,
276        values = yeararrange(),
277        readonly = False,
278        )
279
280    provider = schema.Choice(
281        title = u'Applicants container type',
282        required = True,
283        default = None,
284        source = ApplicantContainerProviderSource(),
285        readonly = False,
286        )
287
288IApplicantsContainerAdd[
289    'prefix'].order =  IApplicantsContainer['prefix'].order
290IApplicantsContainerAdd[
291    'year'].order =  IApplicantsContainer['year'].order
292IApplicantsContainerAdd[
293    'provider'].order =  IApplicantsContainer['provider'].order
294
295class IApplicantBaseData(IWAeUPObject):
296    """The data for an applicant.
297
298    This is a base interface with no field (except ``reg_no``)
299    required. For use with importers, forms, etc., please use one of
300    the derived interfaces below, which set more fields to required
301    state, depending on use-case.
302    """
303    reg_no = schema.TextLine(
304        title = u'JAMB Registration Number',
305        )
306    access_code = schema.TextLine(
307        title = u'Access Code',
308        required = False,
309        )
310    serial = schema.TextLine(
311        title = u'Serial Number',
312        required = False,
313        )
314    course1 = schema.TextLine(
315        title = u'1st Choice Course of Study',
316        required = False,
317        )
318    course2 = schema.TextLine(
319        title = u'2nd Choice Course of Study',
320        required = False,
321        )
322    course3 = schema.TextLine(
323        title = u'3rd Choice Course of Study',
324        required = False,
325        )
326    firstname = schema.TextLine(
327        title = u'First Name',
328        required = False,
329        )
330    middlenames = schema.TextLine(
331        title = u'Middle Names',
332        required = False,
333        )
334    lastname = schema.TextLine(
335        title = u'Surname/Full Name',
336        required = False,
337        )
338    date_of_birth = schema.Date(
339        title = u'Date of Birth',
340        required = False,
341        )
342    lga = schema.TextLine(
343        # XXX: should be choice
344        title = u'State/LGA (confirmed by applicant)',
345        required = False,
346        )
347    sex = schema.Choice(
348        title = u'Sex',
349        source = GenderSource(),
350        default = u'm',
351        required = False,
352        )
353    email = schema.TextLine(
354        title = u'Email',
355        required = False,
356        )
357    phone = schema.TextLine(
358        title = u'Phone',
359        required = False,
360        )
361    passport = ImageFile(
362        title = u'Passport Photograph',
363        default = DEFAULT_PASSPORT_IMAGE_MALE,
364        required = True,
365        )
366    aos = schema.TextLine(
367        # XXX: should be choice
368        title = u'Area of Specialisation',
369        required = False,
370        )
371    subj1 = schema.TextLine(
372        # XXX: should be choice
373        title = u'1st Choice of Study',
374        required = False,
375        )
376    subj2 = schema.TextLine(
377        # XXX: should be choice
378        title = u'2nd Choice of Study',
379        required = False,
380        )
381    subj3 = schema.TextLine(
382        # XXX: should be choice
383        title = u'3rd Choice of Study',
384        required = False,
385        )
386    #
387    # Higher Educational Data
388    #
389    hq_matric_no = schema.TextLine(
390        title = u'Former Matric Number',
391        required = False,
392        )
393    hq_type = schema.TextLine(
394        title = u'Higher Qualification',
395        required = False,
396        )
397    hq_grade = schema.TextLine(
398        title = u'Higher Qualification Grade',
399        required = False,
400        )
401    hq_school = schema.TextLine(
402        title = u'School Attended',
403        required = False,
404        )
405    hq_session = schema.TextLine(
406        title = u'Session Obtained',
407        required = False,
408        )
409    hq_disc = schema.TextLine(
410        title = u'Discipline',
411        required = False,
412        )
413    #
414    # First sitting data
415    #
416    fst_sit_fname = schema.TextLine(
417        title = u'Full Name',
418        required = False,
419        )
420    fst_sit_no = schema.TextLine(
421        title = u'Exam Number',
422        required = False,
423        )
424    fst_sit_date = schema.Date(
425        title = u'Exam Date (dd/mm/yyyy)',
426        required = False,
427        )
428    fst_sit_type = schema.TextLine(
429        # XXX: Should be choice
430        title = u'Exam Type',
431        required = False,
432        )
433    fst_sit_results = schema.List(
434        title = u'Results',
435        required = False,
436        value_type = schema.Object(
437            title = u'Entries',
438            schema = IResultEntry,
439            required = False,
440            )
441        )
442    scd_sit_fname = schema.TextLine(
443        title = u'Full Name',
444        required = False,
445        )
446    scd_sit_no = schema.TextLine(
447        title = u'Exam Number',
448        required = False,
449        )
450    scd_sit_date = schema.Date(
451        title = u'Exam Date (dd/mm/yyyy)',
452        required = False,
453        )
454    scd_sit_type = schema.TextLine(
455        # XXX: Should be choice
456        title = u'Exam Type',
457        required = False,
458        )
459    scd_sit_results = schema.TextLine(
460        # XXX: Should be nested list of choices
461        title = u'Results',
462        required = False,
463        )
464    #
465    # JAMB scores
466    #
467    eng_score = schema.TextLine(
468        title = u"'English' score",
469        required = False,
470        )
471    subj1score = schema.TextLine(
472        title = u'1st Choice of Study Score',
473        required = False,
474        )
475    subj2score = schema.TextLine(
476        title = u'2nd Choice of Study Score',
477        required = False,
478        )
479    subj3score = schema.TextLine(
480        title = u'3rd Choice of Study Score',
481        required = False,
482        )
483    # XXX: Total score???
484
485    #
486    # Application Data
487    #
488    application_date = schema.Date(
489        title = u'Application Date',
490        required = False,
491        )
492    status = schema.TextLine(
493        # XXX: should be 'status' type
494        title = u'Application Status',
495        required = False,
496        )
497    screening_date = schema.Date(
498        title = u'Screening Date',
499        required = False,
500        )
501    screening_type = schema.TextLine(
502        # XXX: schould be choice
503        title = u'Screening Type',
504        required = False,
505        )
506    screening_score = schema.TextLine(
507        title = u'Screening Score',
508        required = False,
509        )
510    screening_venue = schema.TextLine(
511        title = u'Screening Venue',
512        required = False,
513        )
514    total_score = schema.TextLine(
515        title = u'Total Score',
516        required = False,
517        )
518    course_admitted = schema.TextLine(
519        # XXX: should be choice
520        title = u'Admitted Course of Study',
521        required = False,
522        )
523    department = schema.TextLine(
524        # XXX: if we have a course, dept. is not necessary
525        title = u'Department',
526        required = False,
527        )
528    faculty = schema.TextLine(
529        # XXX: if we have a course, faculty is not necessary
530        title = u'Faculty',
531        required = False,
532        )
533    entry_session = schema.TextLine(
534        # XXX: should be choice, should have sensible default: upcoming session
535        title = u'Entry Session',
536        required = False,
537        )
538    notice = schema.Text(
539        title = u'Notice',
540        required = False,
541        )
542    student_id = schema.TextLine(
543        title = u'Student ID',
544        required = False,
545        )
546    import_record_no = schema.TextLine(
547        title = u'Import Record No.',
548        required = False,
549        )
550    imported_by = schema.TextLine(
551        title = u'Imported By',
552        required = False,
553        )
554    import_date = schema.Datetime(
555        title = u'Import Date',
556        required = False,
557        )
558    import_from = schema.TextLine(
559        title = u'Import Source',
560        required = False,
561        )
562    confirm_passport = schema.Bool(
563        title = u"Confirmation that photograph represents applicant ticked.",
564        default = False,
565        required = True,
566        )
567
568
569class IApplicant(IApplicantBaseData):
570    """An applicant.
571
572    This is basically the applicant base data. Here we repeat the
573    fields from base data only with the `required` attribute of
574    required attributes set to True (which is the default).
575    """
576    locked = schema.Bool(
577        title = u'Form locked',
578        default = False,
579        readonly = True,
580        )
581    access_code = schema.TextLine(
582        title = u'Access Code',
583        )
584    course1 = schema.TextLine(
585        title = u'1st Choice Course of Study',
586        )
587    firstname = schema.TextLine(
588        title = u'First Name',
589        )
590    middlenames = schema.TextLine(
591        title = u'Middle Names',
592        )
593    lastname = schema.TextLine(
594        title = u'Surname/Full Name',
595        )
596    date_of_birth = schema.Date(
597        title = u'Date of Birth',
598        )
599    jamb_state = schema.TextLine(
600        title = u'State (provided by JAMB)',
601        )
602    jamb_lga = schema.TextLine(
603        title = u'LGA (provided by JAMB)',
604        )
605    lga = schema.TextLine(
606        # XXX: should be choice
607        title = u'State/LGA (confirmed by applicant)',
608        )
609    sex = schema.Choice(
610        title = u'Sex',
611        source = GenderSource(),
612        default = u'm',
613        )
614    #passport = schema.Bool(
615    #    title = u'Passport Photograph',
616    #    default = True,
617    #    )
618    passport = ImageFile(
619        title = u'Passport Photograph',
620        default = DEFAULT_PASSPORT_IMAGE_MALE,
621        required = True ,
622        )
623    #
624    # Higher Educational Data
625    #
626
627    #
628    # First sitting data
629    #
630    fst_sit_fname = schema.TextLine(
631        title = u'Full Name',
632        )
633
634    #
635    # Second sitting data
636    #
637    scd_sit_fname = schema.TextLine(
638        title = u'Full Name',
639        )
640    #
641    # Application Data
642    #
643    application_date = schema.Date(
644        title = u'Application Date',
645        )
646    status = schema.TextLine(
647        # XXX: should be 'status' type
648        title = u'Application Status',
649        )
650    screening_date = schema.Date(
651        title = u'Screening Date',
652        )
653    screening_type = schema.TextLine(
654        # XXX: schould be choice
655        title = u'Screening Type',
656        )
657    screening_score = schema.TextLine(
658        title = u'Screening Score',
659        )
660    entry_session = schema.TextLine(
661        # XXX: should be choice
662        # XXX: should have sensible default: upcoming session
663        title = u'Entry Session',
664        )
665    import_record_no = schema.TextLine(
666        title = u'Import Record No.',
667        )
668    imported_by = schema.TextLine(
669        title = u'Imported By',
670        )
671    import_date = schema.Datetime(
672        title = u'Import Date',
673        )
674    import_from = schema.TextLine(
675        title = u'Import Source',
676        )
677
678class IApplicantPDEEditData(IWAeUPObject):
679    """The data set presented to PDE applicants.
680    """
681    locked = schema.Bool(
682        title = u'Form locked',
683        default = False,
684        readonly = True,
685        )
686    access_code = schema.TextLine(
687        title = u'Access Code',
688        readonly = True,
689        )
690    course1 = schema.TextLine(
691        title = u'1st Choice Course of Study',
692        readonly = True,
693        )
694    course2 = schema.TextLine(
695        title = u'2nd Choice Course of Study',
696        required = False,
697        )
698    course3 = schema.TextLine(
699        title = u'3rd Choice Course of Study',
700        required = False,
701        )
702    lastname = schema.TextLine(
703        title = u'Name',
704        readonly = True,
705        )
706    date_of_birth = schema.Date(
707        title = u'Date of Birth',
708        required = True,
709        )
710    lga = schema.TextLine(
711        # XXX: should be choice
712        title = u'State/LGA (confirmed by applicant)',
713        required = False,
714        )
715    email = schema.TextLine(
716        title = u'Email',
717        required = False,
718        )
719    phone = schema.TextLine(
720        title = u'Phone',
721        required = False,
722        )
723    aos = schema.TextLine(
724        # XXX: should be choice
725        title = u'Area of Specialisation',
726        required = False,
727        )
728    subj1 = schema.TextLine(
729        # XXX: should be choice
730        title = u'1st Choice of Study',
731        readonly = True,
732        )
733    subj2 = schema.TextLine(
734        # XXX: should be choice
735        title = u'2nd Choice of Study',
736        required = False,
737        )
738    subj3 = schema.TextLine(
739        # XXX: should be choice
740        title = u'3rd Choice of Study',
741        required = False,
742        )
743
744    #
745    # Application Data
746    #
747    application_date = schema.Date(
748        title = u'Application Date',
749        readonly = True,
750        )
751    status = schema.TextLine(
752        # XXX: should be 'status' type
753        title = u'Application Status',
754        readonly = True,
755        )
756    screening_date = schema.Date(
757        title = u'Screening Date',
758        readonly = True,
759        )
760    passport = ImageFile(
761        title = u'Passport Photograph',
762        default = DEFAULT_PASSPORT_IMAGE_MALE,
763        required = True,
764        )
765    confirm_passport = schema.Bool(
766        title = u"Confirmation that photograph represents applicant ticked.",
767        default = False,
768        required = True,
769        )
770
771
772class IApplicantPrincipalInfo(IPrincipalInfo):
773    """Infos about principals that are applicants.
774    """
775    access_code = Attribute("The Access Code the user purchased")
776
777class IApplicantPrincipal(IPrincipal):
778    """A principal that is an applicant.
779
780    This interface extends zope.security.interfaces.IPrincipal and
781    requires also an `id` and other attributes defined there.
782    """
783    access_code = schema.TextLine(
784        title = u'Access Code',
785        description = u'The access code purchased by the user.',
786        required = True,
787        readonly = True)
788
789class IApplicantsFormChallenger(Interface):
790    """A challenger that uses a browser form to collect applicant
791       credentials.
792    """
793    loginpagename = schema.TextLine(
794        title = u'Loginpagename',
795        description = u"""Name of the login form used by challenger.
796
797        The form must provide an ``access_code`` input field.
798        """)
799
800    accesscode_field = schema.TextLine(
801        title = u'Access code field',
802        description = u'''Field of the login page which is looked up for
803                          access_code''',
804        default = u'access_code',
805        )
806
807
808class IApplicantSessionCredentials(Interface):
809    """Interface for storing and accessing applicant credentials in a
810       session.
811    """
812
813    def __init__(access_code):
814        """Create applicant session credentials."""
815
816    def getAccessCode():
817        """Return the access code."""
818
819
820class IApplicantsContainerProvider(Interface):
821    """A provider for applicants containers.
822
823    Applicants container providers are meant to be looked up as
824    utilities. This way we can find all applicant container types
825    defined somewhere.
826
827    Each applicants container provider registered as utility provides
828    one container type and one should be able to call the `factory`
829    attribute to create an instance of the requested container type.
830
831    .. THE FOLLOWING SHOULD GO INTO SPHINX DOCS (and be tested)
832
833    Samples
834    *******
835
836    Given, you had an IApplicantsContainer implementation somewhere
837    and you would like to make it findable on request, then you would
838    normally create an appropriate provider utility like this::
839
840      import grok
841      from waeup.sirp.applicants.interfaces import IApplicantsContainerProvider
842
843      class MyContainerProvider(grok.GlobalUtility):
844          grok.implements(IApplicantsContainerProvider)
845          grok.name('MyContainerProvider') # Must be unique
846          factory = MyContainer # A class implementing IApplicantsContainer
847                                # or derivations thereof.
848
849    This utility would be registered on startup and could then be used
850    like this:
851
852      >>> from zope.component import getAllUtilitiesRegisteredFor
853      >>> from waeup.sirp.applicants.interfaces import (
854      ...     IApplicantsContainerProvider)
855      >>> all_providers = getAllUtilitiesRegisteredFor(
856      ...     IApplicantsContainerProvider)
857      >>> all_providers
858      [<MyContainerProvider object at 0x...>]
859
860    You could look up this specific provider by name:
861
862      >>> from zope.component import getUtility
863      >>> p = getUtility(IApplicantsContainerProvider, name='MyProvider')
864      >>> p
865      <MyContainerProvider object at 0x...>
866
867    An applicants container would then be created like this:
868
869      >>> provider = all_providers[0]
870      >>> container = provider.factory()
871      >>> container
872      <MyContainer object at 0x...>
873
874    """
875    factory = Attribute("A class that can create instances of the "
876                        "requested container type")
Note: See TracBrowser for help on using the repository browser.