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

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

Set default for passport image via new
IContextAwareDefaultFactory. This bit is new in latest zope.schema and
enables definition of default values for interface fields, that should
be computed based on specific contexts. A side effect is, that the
default values for such fields are not set 'hardwired' during startup
but only, when a new object has to be created (after startup). So with
an IContextAwareDefaultFactory you can also use utilities and adapters
when defining a default value for some field.

The new zope.interface version we require enables the @provider
decorator we use when defining the new default passport image factory.

File size: 16.6 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
26
27from grokcore.content.interfaces import IContainer
28
29from zope import schema
30from zope.interface import Interface, Attribute, provider
31from zope.component import getUtility, getUtilitiesFor
32from zope.pluggableauth.interfaces import IPrincipalInfo
33from zope.security.interfaces import IGroupClosureAwarePrincipal as IPrincipal
34from zc.sourcefactory.basic import BasicSourceFactory
35from waeup.sirp.image.schema import ImageFile
36from waeup.sirp.image.image import WAeUPImageFile
37from waeup.sirp.interfaces import IWAeUPObject
38from waeup.sirp.university.vocabularies import application_categories
39from waeup.sirp.applicants.vocabularies import (
40  year_range, application_types_vocab, application_pins_vocab,
41  lgas_vocab, CertificateSource, AppCatCertificateSource,
42  GenderSource, entry_session_vocab
43  )
44
45IMAGE_PATH = os.path.join(
46    os.path.dirname(waeup.sirp.browser.__file__),
47    'static'
48    )
49DEFAULT_PASSPORT_IMAGE_MALE = WAeUPImageFile(
50    'passport.jpg',
51    open(os.path.join(IMAGE_PATH, 'placeholder_m.jpg')).read(),
52    )
53DEFAULT_PASSPORT_IMAGE_FEMALE = WAeUPImageFile(
54    'passport.jpg',
55    open(os.path.join(IMAGE_PATH, 'placeholder_f.jpg')).read(),
56    )
57
58@provider(schema.interfaces.IContextAwareDefaultFactory)
59def default_passport_image(context):
60    """A default value factory for ImageFile fields.
61
62    Returns some default image as WAeUPImageFile. We cannot set the
63    default directly in ImageFile fields, as, if we want to set
64    max_size or min_size as well, some utility lookups are needed
65    which are not possible during startup.
66
67    Developers which use IContextAwareDefaultFactories like this one
68    always should make sure that the delivered default meets all
69    constraints of the field that makes use of this default value
70    provider.
71    """
72    return DEFAULT_PASSPORT_IMAGE_MALE
73
74class ApplicantContainerProviderSource(BasicSourceFactory):
75    """A source offering all available applicants container types.
76
77    The values returned by this source are names of utilities that can
78    create :class:`ApplicantContainer` instances. So, if you get a
79    name like ``'myactype'`` from this source, then you can do:
80
81      >>> from zope.component import getUtility
82      >>> p = getUtility(IApplicantsContainerProvider, name=myactype)
83      >>> my_applicants_container = p.factory()
84
85    Or you can access class-attributes like
86
87      >>> my_applicants_container.container_title
88      'Pretty'
89
90    """
91    def getValues(self):
92        """Returns a list of ``(<name>, <provider>)`` tuples.
93
94        Here ``<name>`` is the name under which an
95        :class:``ApplicantContainerProvider`` was registered as a
96        utility and ``<provider>`` is the utility itself.
97        """
98        return getUtilitiesFor(IApplicantsContainerProvider)
99
100    def getToken(self, value):
101        """Return the name of the ``(<name>, <provider>)`` tuple.
102        """
103        return value[0]
104
105    def getTitle(self, value):
106        """Get a 'title - description' string for a container type.
107        """
108        factory = value[1].factory
109        return "%s - %s" % (
110            factory.container_title, factory.container_description)
111
112class IResultEntry(IWAeUPObject):
113    subject = schema.TextLine(
114        title = u'Subject',
115        description = u'The subject',
116        required=False,
117        )
118    score = schema.TextLine(
119        title = u'Score',
120        description = u'The score',
121        required=False,
122        )
123
124class IApplicantsRoot(IWAeUPObject, IContainer):
125    """A container for university applicants containers.
126    """
127    pass
128
129class IApplicantsContainer(IWAeUPObject):
130    """An applicants container contains university applicants.
131
132    """
133
134    container_title = Attribute(
135        u'classattribute: title for type of container')
136    container_description = Attribute(
137        u'classattribute: description for type of container')
138
139
140    code = schema.TextLine(
141        title = u'Code',
142        default = u'-',
143        required = True,
144        readonly = True,
145        )
146
147    title = schema.TextLine(
148        title = u'Title',
149        required = True,
150        default = u'-',
151        readonly = True,
152        )
153
154    prefix = schema.Choice(
155        title = u'Application target',
156        required = True,
157        default = None,
158        source = application_types_vocab,
159        readonly = True,
160        )
161
162    year = schema.Choice(
163        title = u'Year of entrance',
164        required = True,
165        default = None,
166        values = year_range(),
167        readonly = True,
168        )
169
170    provider = schema.Choice(
171        title = u'Applicants container type',
172        required = True,
173        default = None,
174        source = ApplicantContainerProviderSource(),
175        readonly = True,
176        )
177
178    ac_prefix = schema.Choice(
179        title = u'Access code prefix',
180        required = True,
181        default = None,
182        source = application_pins_vocab,
183        )
184
185    application_category = schema.Choice(
186        title = u'Category for the grouping of study courses',
187        required = True,
188        default = None,
189        source = application_categories,
190        )
191
192    description = schema.Text(
193        title = u'Human readable description in reST format',
194        required = False,
195        default = u'No description yet.'
196        )
197
198    startdate = schema.Date(
199        title = u'Date when the application period starts',
200        required = False,
201        default = None,
202        )
203
204    enddate = schema.Date(
205        title = u'Date when the application period ends',
206        required = False,
207        default = None,
208        )
209
210    strict_deadline = schema.Bool(
211        title = u'Forbid additions after deadline (enddate)',
212        required = True,
213        default = True,
214        )
215
216    def archive(id=None):
217        """Create on-dist archive of applicants stored in this term.
218
219        If id is `None`, all applicants are archived.
220
221        If id contains a single id string, only the respective
222        applicants are archived.
223
224        If id contains a list of id strings all of the respective
225        applicants types are saved to disk.
226        """
227
228    def clear(id=None, archive=True):
229        """Remove applicants of type given by 'id'.
230
231        Optionally archive the applicants.
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        applicant types are saved to disk.
240
241        If `archive` is ``False`` none of the archive-handling is done
242        and respective applicants are simply removed from the
243        database.
244        """
245
246class IApplicantsContainerAdd(IApplicantsContainer):
247    """An applicants container contains university applicants.
248    """
249    prefix = schema.Choice(
250        title = u'Application target',
251        required = True,
252        default = None,
253        source = application_types_vocab,
254        readonly = False,
255        )
256
257    year = schema.Choice(
258        title = u'Year of entrance',
259        required = True,
260        default = None,
261        values = year_range(),
262        readonly = False,
263        )
264
265    provider = schema.Choice(
266        title = u'Applicants container type',
267        required = True,
268        default = None,
269        source = ApplicantContainerProviderSource(),
270        readonly = False,
271        )
272
273IApplicantsContainerAdd[
274    'prefix'].order =  IApplicantsContainer['prefix'].order
275IApplicantsContainerAdd[
276    'year'].order =  IApplicantsContainer['year'].order
277IApplicantsContainerAdd[
278    'provider'].order =  IApplicantsContainer['provider'].order
279
280class IApplicantBaseData(IWAeUPObject):
281    """The data for an applicant.
282
283    This is a base interface with no field (except ``reg_no``)
284    required. For use with importers, forms, etc., please use one of
285    the derived interfaces below, which set more fields to required
286    state, depending on use-case.
287    """
288    locked = schema.Bool(
289        title = u'Form locked',
290        default = False,
291        #readonly = True,
292        )
293    reg_no = schema.TextLine(
294        title = u'JAMB Registration Number',
295        readonly = True,
296        )
297    access_code = schema.TextLine(
298        title = u'Access Code',
299        required = False,
300        readonly = True,
301        )
302    course1 = schema.Choice(
303        title = u'1st Choice Course of Study',
304        source = AppCatCertificateSource(),
305        required = False,
306        )
307    course2 = schema.Choice(
308        title = u'2nd Choice Course of Study',
309        source = AppCatCertificateSource(),
310        required = False,
311        )
312    firstname = schema.TextLine(
313        title = u'First Name',
314        required = False,
315        )
316    middlenames = schema.TextLine(
317        title = u'Middle Names',
318        required = False,
319        )
320    lastname = schema.TextLine(
321        title = u'Last Name (Surname)',
322        required = False,
323        )
324    date_of_birth = schema.Date(
325        title = u'Date of Birth',
326        required = False,
327        )
328    lga = schema.Choice(
329        source = lgas_vocab,
330        title = u'State/LGA',
331        default = 'foreigner',
332        required = True,
333        )
334    sex = schema.Choice(
335        title = u'Sex',
336        source = GenderSource(),
337        default = u'm',
338        required = False,
339        )
340    email = schema.TextLine(
341        title = u'Email',
342        required = False,
343        )
344    phone = schema.TextLine(
345        title = u'Phone',
346        required = False,
347        )
348    passport = ImageFile(
349        title = u'Passport Photograph',
350        #default = DEFAULT_PASSPORT_IMAGE_MALE,
351        defaultFactory = default_passport_image,
352        required = True,
353        max_size = 20480,
354        )
355    confirm_passport = schema.Bool(
356        title = u"Passport picture confirmed",
357        default = False,
358        required = True,
359        )
360    #
361    # Process Data
362    #
363    application_date = schema.Date(
364        title = u'Application Date',
365        required = False,
366        readonly = True,
367        )
368    status = schema.TextLine(
369        # XXX: should be 'status' type
370        title = u'Application Status',
371        required = False,
372        readonly = True,
373        )
374    screening_score = schema.Int(
375        title = u'Screening Score',
376        required = False,
377        )
378    screening_venue = schema.TextLine(
379        title = u'Screening Venue',
380        required = False,
381        )
382    course_admitted = schema.Choice(
383        title = u'Admitted Course of Study',
384        source = CertificateSource(),
385        default = None,
386        required = False,
387        )
388    entry_session = schema.Choice(
389        source = entry_session_vocab,
390        title = u'Entry Session',
391        required = False,
392        )
393    notice = schema.Text(
394        title = u'Notice',
395        required = False,
396        )
397    student_id = schema.TextLine(
398        title = u'Student ID',
399        required = False,
400        readonly = True,
401        )
402
403class IApplicant(IApplicantBaseData):
404    """An applicant.
405
406    This is basically the applicant base data. Here we repeat the
407    fields from base data if we have to set the `required` attribute
408    to True (which is the default).
409    """
410
411class IApplicantEdit(IApplicantBaseData):
412    """An applicant.
413
414    Here we can repeat the fields from base data and set the `required` and
415    `readonly` attributes to True to further restrict the data access. We cannot
416    omit fields. This has to be done in the respective form page.
417    """
418    screening_score = schema.Int(
419        title = u'Screening Score',
420        required = False,
421        readonly = True,
422        )
423    screening_venue = schema.TextLine(
424        title = u'Screening Venue',
425        required = False,
426        readonly = True,
427        )
428    course_admitted = schema.TextLine(
429        # XXX: should be choice
430        title = u'Admitted Course of Study',
431        required = False,
432        readonly = True,
433        default = None,
434        )
435    entry_session = schema.TextLine(
436        # XXX: should be choice
437        title = u'Entry Session',
438        required = False,
439        readonly = True,
440        )
441    notice = schema.Text(
442        title = u'Notice',
443        required = False,
444        readonly = True,
445        )
446    confirm_passport = schema.Bool(
447        title = u"I confirm that the Passport Photograph uploaded on this form is a true picture of me.",
448        default = False,
449        required = True,
450        )
451
452class IApplicantPrincipalInfo(IPrincipalInfo):
453    """Infos about principals that are applicants.
454    """
455    access_code = Attribute("The Access Code the user purchased")
456
457class IApplicantPrincipal(IPrincipal):
458    """A principal that is an applicant.
459
460    This interface extends zope.security.interfaces.IPrincipal and
461    requires also an `id` and other attributes defined there.
462    """
463    access_code = schema.TextLine(
464        title = u'Access Code',
465        description = u'The access code purchased by the user.',
466        required = True,
467        readonly = True)
468
469class IApplicantsFormChallenger(Interface):
470    """A challenger that uses a browser form to collect applicant
471       credentials.
472    """
473    loginpagename = schema.TextLine(
474        title = u'Loginpagename',
475        description = u"""Name of the login form used by challenger.
476
477        The form must provide an ``access_code`` input field.
478        """)
479
480    accesscode_field = schema.TextLine(
481        title = u'Access code field',
482        description = u'''Field of the login page which is looked up for
483                          access_code''',
484        default = u'access_code',
485        )
486
487
488class IApplicantSessionCredentials(Interface):
489    """Interface for storing and accessing applicant credentials in a
490       session.
491    """
492
493    def __init__(access_code):
494        """Create applicant session credentials."""
495
496    def getAccessCode():
497        """Return the access code."""
498
499
500class IApplicantsContainerProvider(Interface):
501    """A provider for applicants containers.
502
503    Applicants container providers are meant to be looked up as
504    utilities. This way we can find all applicant container types
505    defined somewhere.
506
507    Each applicants container provider registered as utility provides
508    one container type and one should be able to call the `factory`
509    attribute to create an instance of the requested container type.
510
511    .. THE FOLLOWING SHOULD GO INTO SPHINX DOCS (and be tested)
512
513    Samples
514    *******
515
516    Given, you had an IApplicantsContainer implementation somewhere
517    and you would like to make it findable on request, then you would
518    normally create an appropriate provider utility like this::
519
520      import grok
521      from waeup.sirp.applicants.interfaces import IApplicantsContainerProvider
522
523      class MyContainerProvider(grok.GlobalUtility):
524          grok.implements(IApplicantsContainerProvider)
525          grok.name('MyContainerProvider') # Must be unique
526          factory = MyContainer # A class implementing IApplicantsContainer
527                                # or derivations thereof.
528
529    This utility would be registered on startup and could then be used
530    like this:
531
532      >>> from zope.component import getAllUtilitiesRegisteredFor
533      >>> from waeup.sirp.applicants.interfaces import (
534      ...     IApplicantsContainerProvider)
535      >>> all_providers = getAllUtilitiesRegisteredFor(
536      ...     IApplicantsContainerProvider)
537      >>> all_providers
538      [<MyContainerProvider object at 0x...>]
539
540    You could look up this specific provider by name:
541
542      >>> from zope.component import getUtility
543      >>> p = getUtility(IApplicantsContainerProvider, name='MyProvider')
544      >>> p
545      <MyContainerProvider object at 0x...>
546
547    An applicants container would then be created like this:
548
549      >>> provider = all_providers[0]
550      >>> container = provider.factory()
551      >>> container
552      <MyContainer object at 0x...>
553
554    """
555    factory = Attribute("A class that can create instances of the "
556                        "requested container type")
Note: See TracBrowser for help on using the repository browser.