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

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

Remove whitespace.
Fix overlong lines.
Rename yeararrange to year_range (what should a 'yeararrange' be?)

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