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

Last change on this file since 10553 was 9778, checked in by Henrik Bettermann, 12 years ago

Catch error.

  • Property svn:keywords set to Id
File size: 8.2 KB
Line 
1## $Id: vocabularies.py 9778 2012-12-06 15:45:03Z henrik $
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 999 in (start_level, end_level):
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 start_level == 10 and end_level == 10 and value != 10:
98            return _('Error: level id ${value} out of range',
99                mapping={'value': value})
100        if value < start_level or value > end_level + 120:
101            return _('Error: level id ${value} out of range',
102                mapping={'value': value})
103        # Special treatment for pre-studies level
104        if value == 10:
105            return course_levels.by_value[value].title
106        level,repeat = divmod(value, 100)
107        level = level * 100
108        repeat = repeat//10
109        title = course_levels.by_value[level].title
110        if level > end_level and repeat == 1:
111            title = course_levels.by_value[level - 100].title
112            return _('${title} 2nd spillover', mapping={'title': title})
113        if level > end_level and repeat == 2:
114            title = course_levels.by_value[level - 100].title
115            return _('${title} 3rd spillover', mapping={'title': title})
116        if level > end_level:
117            title = course_levels.by_value[level - 100].title
118            return  _('${title} 1st spillover', mapping={'title': title})
119        if repeat == 1:
120            return _('${title} on 1st probation', mapping={'title': title})
121        if repeat == 2:
122            return _('${title} on 2nd probation', mapping={'title': title})
123        return title
124
125class GenderSource(BasicSourceFactory):
126    """A gender source delivers basically a mapping
127       ``{'m': 'Male', 'f': 'Female'}``
128
129       Using a source, we make sure that the tokens (which are
130       stored/expected for instance from CSV files) are something one
131       can expect and not cryptic IntIDs.
132    """
133    def getValues(self):
134        return ['m', 'f']
135
136    def getToken(self, value):
137        return value[0].lower()
138
139    def getTitle(self, value):
140        if value == 'm':
141            return _('male')
142        if value == 'f':
143            return _('female')
144
145class RegNumNotInSource(ValidationError):
146    """Registration number exists already
147    """
148    # The docstring of ValidationErrors is used as error description
149    # by zope.formlib.
150    pass
151
152class MatNumNotInSource(ValidationError):
153    """Matriculation number exists already
154    """
155    # The docstring of ValidationErrors is used as error description
156    # by zope.formlib.
157    pass
158
159class RegNumberSource(object):
160    """A source that accepts any entry for a certain field if not used
161    already.
162
163    Using this kind of source means a way of setting an invariant.
164
165    We accept a value iff:
166    - the value cannot be found in catalog or
167    - the value can be found as part of some item but the bound item
168      is the context object itself.
169    """
170    implements(ISource)
171    cat_name = 'students_catalog'
172    field_name = 'reg_number'
173    validation_error = RegNumNotInSource
174    comp_field = 'student_id'
175    def __init__(self, context):
176        self.context = context
177        return
178
179    def __contains__(self, value):
180        """We accept all values not already given to other students.
181        """
182        cat = queryUtility(ICatalog, self.cat_name)
183        if cat is None:
184            return True
185        kw = {self.field_name: (value, value)}
186        results = cat.searchResults(**kw)
187        for entry in results:
188            if not hasattr(self.context, self.comp_field):
189                # we have no context with comp_field (most probably
190                # while adding a new object, where the container is
191                # the context) which means that the value was given
192                # already to another object (as _something_ was found in
193                # the catalog with that value). Fail on first round.
194                raise self.validation_error(value)
195            if getattr(entry, self.comp_field) != getattr(
196                self.context, self.comp_field):
197                # An entry already given to another student is not in our
198                # range of acceptable values.
199                raise self.validation_error(value)
200                #return False
201        return True
202
203def contextual_reg_num_source(context):
204    source = RegNumberSource(context)
205    return source
206directlyProvides(contextual_reg_num_source, IContextSourceBinder)
207
208class MatNumberSource(RegNumberSource):
209    field_name = 'matric_number'
210    validation_error = MatNumNotInSource
211
212def contextual_mat_num_source(context):
213    source = MatNumberSource(context)
214    return source
215directlyProvides(contextual_mat_num_source, IContextSourceBinder)
Note: See TracBrowser for help on using the repository browser.