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

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

Move 'future year source'. It is not used in university module at all.

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