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

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

Fix missing import.
Minor fixes.

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