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

Last change on this file since 6101 was 6101, checked in by uli, 14 years ago

Remove source methods that seem not to be necessary.

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