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

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

Implement local role assignment and removal in application section.

browser.py line 341: Use correct context for the IPrincipalRoleManager adapter.

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        #max_size = 20480,
368        )
369    aos = schema.TextLine(
370        # XXX: should be choice
371        title = u'Area of Specialisation',
372        required = False,
373        )
374    subj1 = schema.TextLine(
375        # XXX: should be choice
376        title = u'1st Choice of Study',
377        required = False,
378        )
379    subj2 = schema.TextLine(
380        # XXX: should be choice
381        title = u'2nd Choice of Study',
382        required = False,
383        )
384    subj3 = schema.TextLine(
385        # XXX: should be choice
386        title = u'3rd Choice of Study',
387        required = False,
388        )
389    #
390    # Higher Educational Data
391    #
392    hq_matric_no = schema.TextLine(
393        title = u'Former Matric Number',
394        required = False,
395        )
396    hq_type = schema.TextLine(
397        title = u'Higher Qualification',
398        required = False,
399        )
400    hq_grade = schema.TextLine(
401        title = u'Higher Qualification Grade',
402        required = False,
403        )
404    hq_school = schema.TextLine(
405        title = u'School Attended',
406        required = False,
407        )
408    hq_session = schema.TextLine(
409        title = u'Session Obtained',
410        required = False,
411        )
412    hq_disc = schema.TextLine(
413        title = u'Discipline',
414        required = False,
415        )
416    #
417    # First sitting data
418    #
419    fst_sit_fname = schema.TextLine(
420        title = u'Full Name',
421        required = False,
422        )
423    fst_sit_no = schema.TextLine(
424        title = u'Exam Number',
425        required = False,
426        )
427    fst_sit_date = schema.Date(
428        title = u'Exam Date (dd/mm/yyyy)',
429        required = False,
430        )
431    fst_sit_type = schema.TextLine(
432        # XXX: Should be choice
433        title = u'Exam Type',
434        required = False,
435        )
436    fst_sit_results = schema.List(
437        title = u'Results',
438        required = False,
439        value_type = schema.Object(
440            title = u'Entries',
441            schema = IResultEntry,
442            required = False,
443            )
444        )
445    scd_sit_fname = schema.TextLine(
446        title = u'Full Name',
447        required = False,
448        )
449    scd_sit_no = schema.TextLine(
450        title = u'Exam Number',
451        required = False,
452        )
453    scd_sit_date = schema.Date(
454        title = u'Exam Date (dd/mm/yyyy)',
455        required = False,
456        )
457    scd_sit_type = schema.TextLine(
458        # XXX: Should be choice
459        title = u'Exam Type',
460        required = False,
461        )
462    scd_sit_results = schema.TextLine(
463        # XXX: Should be nested list of choices
464        title = u'Results',
465        required = False,
466        )
467    #
468    # JAMB scores
469    #
470    eng_score = schema.TextLine(
471        title = u"'English' score",
472        required = False,
473        )
474    subj1score = schema.TextLine(
475        title = u'1st Choice of Study Score',
476        required = False,
477        )
478    subj2score = schema.TextLine(
479        title = u'2nd Choice of Study Score',
480        required = False,
481        )
482    subj3score = schema.TextLine(
483        title = u'3rd Choice of Study Score',
484        required = False,
485        )
486    # XXX: Total score???
487
488    #
489    # Application Data
490    #
491    application_date = schema.Date(
492        title = u'Application Date',
493        required = False,
494        )
495    status = schema.TextLine(
496        # XXX: should be 'status' type
497        title = u'Application Status',
498        required = False,
499        )
500    screening_date = schema.Date(
501        title = u'Screening Date',
502        required = False,
503        )
504    screening_type = schema.TextLine(
505        # XXX: schould be choice
506        title = u'Screening Type',
507        required = False,
508        )
509    screening_score = schema.TextLine(
510        title = u'Screening Score',
511        required = False,
512        )
513    screening_venue = schema.TextLine(
514        title = u'Screening Venue',
515        required = False,
516        )
517    total_score = schema.TextLine(
518        title = u'Total Score',
519        required = False,
520        )
521    course_admitted = schema.TextLine(
522        # XXX: should be choice
523        title = u'Admitted Course of Study',
524        required = False,
525        )
526    department = schema.TextLine(
527        # XXX: if we have a course, dept. is not necessary
528        title = u'Department',
529        required = False,
530        )
531    faculty = schema.TextLine(
532        # XXX: if we have a course, faculty is not necessary
533        title = u'Faculty',
534        required = False,
535        )
536    entry_session = schema.TextLine(
537        # XXX: should be choice, should have sensible default: upcoming session
538        title = u'Entry Session',
539        required = False,
540        )
541    notice = schema.Text(
542        title = u'Notice',
543        required = False,
544        )
545    student_id = schema.TextLine(
546        title = u'Student ID',
547        required = False,
548        )
549    import_record_no = schema.TextLine(
550        title = u'Import Record No.',
551        required = False,
552        )
553    imported_by = schema.TextLine(
554        title = u'Imported By',
555        required = False,
556        )
557    import_date = schema.Datetime(
558        title = u'Import Date',
559        required = False,
560        )
561    import_from = schema.TextLine(
562        title = u'Import Source',
563        required = False,
564        )
565    confirm_passport = schema.Bool(
566        title = u"Confirmation that photograph represents applicant ticked.",
567        default = False,
568        required = True,
569        )
570
571
572class IApplicant(IApplicantBaseData):
573    """An applicant.
574
575    This is basically the applicant base data. Here we repeat the
576    fields from base data only with the `required` attribute of
577    required attributes set to True (which is the default).
578    """
579    locked = schema.Bool(
580        title = u'Form locked',
581        default = False,
582        readonly = True,
583        )
584    access_code = schema.TextLine(
585        title = u'Access Code',
586        )
587    course1 = schema.TextLine(
588        title = u'1st Choice Course of Study',
589        )
590    firstname = schema.TextLine(
591        title = u'First Name',
592        )
593    middlenames = schema.TextLine(
594        title = u'Middle Names',
595        )
596    lastname = schema.TextLine(
597        title = u'Surname/Full Name',
598        )
599    date_of_birth = schema.Date(
600        title = u'Date of Birth',
601        )
602    jamb_state = schema.TextLine(
603        title = u'State (provided by JAMB)',
604        )
605    jamb_lga = schema.TextLine(
606        title = u'LGA (provided by JAMB)',
607        )
608    lga = schema.TextLine(
609        # XXX: should be choice
610        title = u'State/LGA (confirmed by applicant)',
611        )
612    sex = schema.Choice(
613        title = u'Sex',
614        source = GenderSource(),
615        default = u'm',
616        )
617    #passport = schema.Bool(
618    #    title = u'Passport Photograph',
619    #    default = True,
620    #    )
621    passport = ImageFile(
622        title = u'Passport Photograph',
623        default = DEFAULT_PASSPORT_IMAGE_MALE,
624        required = True ,
625        #max_size = 20480,
626        )
627    #
628    # Higher Educational Data
629    #
630
631    #
632    # First sitting data
633    #
634    fst_sit_fname = schema.TextLine(
635        title = u'Full Name',
636        )
637
638    #
639    # Second sitting data
640    #
641    scd_sit_fname = schema.TextLine(
642        title = u'Full Name',
643        )
644    #
645    # Application Data
646    #
647    application_date = schema.Date(
648        title = u'Application Date',
649        )
650    status = schema.TextLine(
651        # XXX: should be 'status' type
652        title = u'Application Status',
653        )
654    screening_date = schema.Date(
655        title = u'Screening Date',
656        )
657    screening_type = schema.TextLine(
658        # XXX: schould be choice
659        title = u'Screening Type',
660        )
661    screening_score = schema.TextLine(
662        title = u'Screening Score',
663        )
664    entry_session = schema.TextLine(
665        # XXX: should be choice
666        # XXX: should have sensible default: upcoming session
667        title = u'Entry Session',
668        )
669    import_record_no = schema.TextLine(
670        title = u'Import Record No.',
671        )
672    imported_by = schema.TextLine(
673        title = u'Imported By',
674        )
675    import_date = schema.Datetime(
676        title = u'Import Date',
677        )
678    import_from = schema.TextLine(
679        title = u'Import Source',
680        )
681
682class IApplicantPDEEditData(IWAeUPObject):
683    """The data set presented to PDE applicants.
684    """
685    locked = schema.Bool(
686        title = u'Form locked',
687        default = False,
688        readonly = True,
689        )
690    access_code = schema.TextLine(
691        title = u'Access Code',
692        readonly = True,
693        )
694    course1 = schema.TextLine(
695        title = u'1st Choice Course of Study',
696        readonly = True,
697        )
698    course2 = schema.TextLine(
699        title = u'2nd Choice Course of Study',
700        required = False,
701        )
702    course3 = schema.TextLine(
703        title = u'3rd Choice Course of Study',
704        required = False,
705        )
706    lastname = schema.TextLine(
707        title = u'Name',
708        readonly = True,
709        )
710    date_of_birth = schema.Date(
711        title = u'Date of Birth',
712        required = True,
713        )
714    lga = schema.TextLine(
715        # XXX: should be choice
716        title = u'State/LGA (confirmed by applicant)',
717        required = False,
718        )
719    email = schema.TextLine(
720        title = u'Email',
721        required = False,
722        )
723    phone = schema.TextLine(
724        title = u'Phone',
725        required = False,
726        )
727    aos = schema.TextLine(
728        # XXX: should be choice
729        title = u'Area of Specialisation',
730        required = False,
731        )
732    subj1 = schema.TextLine(
733        # XXX: should be choice
734        title = u'1st Choice of Study',
735        readonly = True,
736        )
737    subj2 = schema.TextLine(
738        # XXX: should be choice
739        title = u'2nd Choice of Study',
740        required = False,
741        )
742    subj3 = schema.TextLine(
743        # XXX: should be choice
744        title = u'3rd Choice of Study',
745        required = False,
746        )
747
748    #
749    # Application Data
750    #
751    application_date = schema.Date(
752        title = u'Application Date',
753        readonly = True,
754        )
755    status = schema.TextLine(
756        # XXX: should be 'status' type
757        title = u'Application Status',
758        readonly = True,
759        )
760    screening_date = schema.Date(
761        title = u'Screening Date',
762        readonly = True,
763        )
764    passport = ImageFile(
765        title = u'Passport Photograph',
766        default = DEFAULT_PASSPORT_IMAGE_MALE,
767        required = True,
768        )
769    confirm_passport = schema.Bool(
770        title = u"Confirmation that photograph represents applicant ticked.",
771        default = False,
772        required = True,
773        )
774
775
776class IApplicantPrincipalInfo(IPrincipalInfo):
777    """Infos about principals that are applicants.
778    """
779    access_code = Attribute("The Access Code the user purchased")
780
781class IApplicantPrincipal(IPrincipal):
782    """A principal that is an applicant.
783
784    This interface extends zope.security.interfaces.IPrincipal and
785    requires also an `id` and other attributes defined there.
786    """
787    access_code = schema.TextLine(
788        title = u'Access Code',
789        description = u'The access code purchased by the user.',
790        required = True,
791        readonly = True)
792
793class IApplicantsFormChallenger(Interface):
794    """A challenger that uses a browser form to collect applicant
795       credentials.
796    """
797    loginpagename = schema.TextLine(
798        title = u'Loginpagename',
799        description = u"""Name of the login form used by challenger.
800
801        The form must provide an ``access_code`` input field.
802        """)
803
804    accesscode_field = schema.TextLine(
805        title = u'Access code field',
806        description = u'''Field of the login page which is looked up for
807                          access_code''',
808        default = u'access_code',
809        )
810
811
812class IApplicantSessionCredentials(Interface):
813    """Interface for storing and accessing applicant credentials in a
814       session.
815    """
816
817    def __init__(access_code):
818        """Create applicant session credentials."""
819
820    def getAccessCode():
821        """Return the access code."""
822
823
824class IApplicantsContainerProvider(Interface):
825    """A provider for applicants containers.
826
827    Applicants container providers are meant to be looked up as
828    utilities. This way we can find all applicant container types
829    defined somewhere.
830
831    Each applicants container provider registered as utility provides
832    one container type and one should be able to call the `factory`
833    attribute to create an instance of the requested container type.
834
835    .. THE FOLLOWING SHOULD GO INTO SPHINX DOCS (and be tested)
836
837    Samples
838    *******
839
840    Given, you had an IApplicantsContainer implementation somewhere
841    and you would like to make it findable on request, then you would
842    normally create an appropriate provider utility like this::
843
844      import grok
845      from waeup.sirp.applicants.interfaces import IApplicantsContainerProvider
846
847      class MyContainerProvider(grok.GlobalUtility):
848          grok.implements(IApplicantsContainerProvider)
849          grok.name('MyContainerProvider') # Must be unique
850          factory = MyContainer # A class implementing IApplicantsContainer
851                                # or derivations thereof.
852
853    This utility would be registered on startup and could then be used
854    like this:
855
856      >>> from zope.component import getAllUtilitiesRegisteredFor
857      >>> from waeup.sirp.applicants.interfaces import (
858      ...     IApplicantsContainerProvider)
859      >>> all_providers = getAllUtilitiesRegisteredFor(
860      ...     IApplicantsContainerProvider)
861      >>> all_providers
862      [<MyContainerProvider object at 0x...>]
863
864    You could look up this specific provider by name:
865
866      >>> from zope.component import getUtility
867      >>> p = getUtility(IApplicantsContainerProvider, name='MyProvider')
868      >>> p
869      <MyContainerProvider object at 0x...>
870
871    An applicants container would then be created like this:
872
873      >>> provider = all_providers[0]
874      >>> container = provider.factory()
875      >>> container
876      <MyContainer object at 0x...>
877
878    """
879    factory = Attribute("A class that can create instances of the "
880                        "requested container type")
Note: See TracBrowser for help on using the repository browser.