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

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

Use a simple static list for year pull-down menus instead of source.

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