source: main/waeup.kofa/trunk/src/waeup/kofa/students/vocabularies.py @ 9777

Last change on this file since 9777 was 9776, checked in by uli, 12 years ago

Lists of levels are computed at several places, everywhere following
the same algorithm. I think it's good to have this algorithm in
exactly one place.

  • Property svn:keywords set to Id
File size: 8.0 KB
Line 
1## $Id: vocabularies.py 9776 2012-12-06 11:23:03Z uli $
2##
3## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
4## This program is free software; you can redistribute it and/or modify
5## it under the terms of the GNU General Public License as published by
6## the Free Software Foundation; either version 2 of the License, or
7## (at your option) any later version.
8##
9## This program is distributed in the hope that it will be useful,
10## but WITHOUT ANY WARRANTY; without even the implied warranty of
11## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12## GNU General Public License for more details.
13##
14## You should have received a copy of the GNU General Public License
15## along with this program; if not, write to the Free Software
16## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17##
18"""Vocabularies and sources for the student section.
19"""
20from zope.component import getUtility, queryUtility
21from zope.catalog.interfaces import ICatalog
22from zope.interface import implements, directlyProvides
23from zope.schema.interfaces import ISource, IContextSourceBinder
24from zope.schema.interfaces import ValidationError
25from zc.sourcefactory.basic import BasicSourceFactory
26from zc.sourcefactory.contextual import BasicContextualSourceFactory
27from waeup.kofa.interfaces import SimpleKofaVocabulary
28from waeup.kofa.interfaces import MessageFactory as _
29from waeup.kofa.utils.helpers import get_sorted_preferred
30from waeup.kofa.utils.countries import COUNTRIES
31from waeup.kofa.university.vocabularies import course_levels
32
33
34#: a tuple of tuples (<COUNTRY-NAME>, <ISO-CODE>) with Nigeria first.
35COUNTRIES = get_sorted_preferred(COUNTRIES, ['NG'])
36nats_vocab = SimpleKofaVocabulary(*COUNTRIES)
37
38def levels_from_range(start_level=None, end_level=None):
39    """Get a list of valid levels for given start/end level.
40
41    Start and end level must be numbers (or ``None``).
42
43    If no start/end level is given, return the default list of levels.
44
45    If only one of start_level or end_level is given, the result is
46    undefined.
47    """
48    if 999 in (start_level, end_level):
49        return [999]
50    levels = [10,] + [
51        level for level in range(100,1000,10) if level % 100 < 30]
52    if start_level is None and end_level is None:
53        return levels + [999]
54    if start_level == 10 == end_level:
55        return [10,]
56    start_level, end_level = (start_level or 10, end_level or 900)
57    levels = [x for x in levels
58              if x >= start_level and x <= (end_level + 120)]
59    return levels
60
61def study_levels(context):
62    certificate = getattr(context, 'certificate', None)
63    start_level, end_level = (None, None)
64    if  certificate is not None:
65        start_level = int(certificate.start_level)
66        end_level = int(certificate.end_level)
67    return levels_from_range(start_level, end_level)
68
69class StudyLevelSource(BasicContextualSourceFactory):
70    """The StudyLevelSource is based on and extends the
71    course_levels vocabulary defined in the university package.
72    Repeating study levels are denoted by increments of 10, the
73    first spillover level by the certificate's end level plus 100
74    and the second spillover level by the end level plus 110.
75    """
76    def getValues(self, context):
77        return study_levels(context)
78
79    def getToken(self, context, value):
80        return str(value)
81
82    def getTitle(self, context, value):
83        certificate = getattr(context, 'certificate', None)
84        if certificate is not None:
85            start_level = int(certificate.start_level)
86            end_level = int(certificate.end_level)
87        else:
88            # default level range
89            start_level = 10
90            end_level = 1000
91        if start_level == 999 or end_level == 999:
92            if value != 999:
93                return _('Error: wrong level id ${value}',
94                    mapping={'value': value})
95        if value == 999:
96            return course_levels.by_value[999].title
97        if value < start_level or value > end_level + 120:
98            return _('Error: level id ${value} out of range',
99                mapping={'value': value})
100        # Special treatment for pre-studies level
101        if value == 10:
102            return course_levels.by_value[value].title
103        level,repeat = divmod(value, 100)
104        level = level * 100
105        repeat = repeat//10
106        title = course_levels.by_value[level].title
107        if level > end_level and repeat == 1:
108            title = course_levels.by_value[level - 100].title
109            return _('${title} 2nd spillover', mapping={'title': title})
110        if level > end_level and repeat == 2:
111            title = course_levels.by_value[level - 100].title
112            return _('${title} 3rd spillover', mapping={'title': title})
113        if level > end_level:
114            title = course_levels.by_value[level - 100].title
115            return  _('${title} 1st spillover', mapping={'title': title})
116        if repeat == 1:
117            return _('${title} on 1st probation', mapping={'title': title})
118        if repeat == 2:
119            return _('${title} on 2nd probation', mapping={'title': title})
120        return title
121
122class GenderSource(BasicSourceFactory):
123    """A gender source delivers basically a mapping
124       ``{'m': 'Male', 'f': 'Female'}``
125
126       Using a source, we make sure that the tokens (which are
127       stored/expected for instance from CSV files) are something one
128       can expect and not cryptic IntIDs.
129    """
130    def getValues(self):
131        return ['m', 'f']
132
133    def getToken(self, value):
134        return value[0].lower()
135
136    def getTitle(self, value):
137        if value == 'm':
138            return _('male')
139        if value == 'f':
140            return _('female')
141
142class RegNumNotInSource(ValidationError):
143    """Registration number exists already
144    """
145    # The docstring of ValidationErrors is used as error description
146    # by zope.formlib.
147    pass
148
149class MatNumNotInSource(ValidationError):
150    """Matriculation number exists already
151    """
152    # The docstring of ValidationErrors is used as error description
153    # by zope.formlib.
154    pass
155
156class RegNumberSource(object):
157    """A source that accepts any entry for a certain field if not used
158    already.
159
160    Using this kind of source means a way of setting an invariant.
161
162    We accept a value iff:
163    - the value cannot be found in catalog or
164    - the value can be found as part of some item but the bound item
165      is the context object itself.
166    """
167    implements(ISource)
168    cat_name = 'students_catalog'
169    field_name = 'reg_number'
170    validation_error = RegNumNotInSource
171    comp_field = 'student_id'
172    def __init__(self, context):
173        self.context = context
174        return
175
176    def __contains__(self, value):
177        """We accept all values not already given to other students.
178        """
179        cat = queryUtility(ICatalog, self.cat_name)
180        if cat is None:
181            return True
182        kw = {self.field_name: (value, value)}
183        results = cat.searchResults(**kw)
184        for entry in results:
185            if not hasattr(self.context, self.comp_field):
186                # we have no context with comp_field (most probably
187                # while adding a new object, where the container is
188                # the context) which means that the value was given
189                # already to another object (as _something_ was found in
190                # the catalog with that value). Fail on first round.
191                raise self.validation_error(value)
192            if getattr(entry, self.comp_field) != getattr(
193                self.context, self.comp_field):
194                # An entry already given to another student is not in our
195                # range of acceptable values.
196                raise self.validation_error(value)
197                #return False
198        return True
199
200def contextual_reg_num_source(context):
201    source = RegNumberSource(context)
202    return source
203directlyProvides(contextual_reg_num_source, IContextSourceBinder)
204
205class MatNumberSource(RegNumberSource):
206    field_name = 'matric_number'
207    validation_error = MatNumNotInSource
208
209def contextual_mat_num_source(context):
210    source = MatNumberSource(context)
211    return source
212directlyProvides(contextual_mat_num_source, IContextSourceBinder)
Note: See TracBrowser for help on using the repository browser.