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

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

Add attribute 'ac_prefix' to ApplicantsContainer? to preselect ac batches. Use this ac_prefix in loginapplicant.pt.

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