source: main/waeup.kofa/branches/henrik-transcript-workflow/src/waeup/kofa/students/vocabularies.py @ 15127

Last change on this file since 15127 was 14480, checked in by Henrik Bettermann, 8 years ago

We use this source also for various reports.
In such a case the context of the source will be the
certificate itself.

  • Property svn:keywords set to Id
File size: 8.6 KB
Line 
1## $Id: vocabularies.py 14480 2017-01-31 11:33:00Z 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 students 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
32from waeup.kofa.university.certificate import Certificate
33
34
35#: a tuple of tuples (<COUNTRY-NAME>, <ISO-CODE>) with Nigeria first.
36COUNTRIES = get_sorted_preferred(COUNTRIES, ['NG'])
37nats_vocab = SimpleKofaVocabulary(*COUNTRIES)
38
39def levels_from_range(start_level=None, end_level=None):
40    """Get a list of valid levels for given start/end level.
41
42    Start and end level must be numbers (or ``None``).
43
44    If no start/end level is given, return the default list of levels.
45
46    If only one of start_level or end_level is given, the result is
47    undefined.
48    """
49    if 999 in (start_level, end_level):
50        return [999]
51    levels = [10,] + [
52        level for level in range(100,1000,10) if level % 100 < 30]
53    if start_level is None and end_level is None:
54        return levels + [999]
55    if start_level == 10 == end_level:
56        return [10,]
57    start_level, end_level = (start_level or 10, end_level or 900)
58    levels = [x for x in levels
59              if x >= start_level and x <= (end_level + 120)]
60    return levels
61
62def study_levels(context):
63    if isinstance(context, Certificate):
64        certificate = context
65    else:
66        certificate = getattr(context, 'certificate', None)
67    start_level, end_level = (None, None)
68    if  certificate is not None:
69        start_level = int(certificate.start_level)
70        end_level = int(certificate.end_level)
71    return levels_from_range(start_level, end_level)
72
73class StudyLevelSource(BasicContextualSourceFactory):
74    """The StudyLevelSource is based on and extends the
75    course_levels vocabulary defined in the university package.
76    Repeating study levels are denoted by increments of 10, the
77    first spillover level by the certificate's end level plus 100
78    and the second spillover level by the end level plus 110.
79    """
80    def getValues(self, context):
81        return study_levels(context)
82
83    def getToken(self, context, value):
84        return str(value)
85
86    def getTitle(self, context, value):
87        if isinstance(context, Certificate):
88            # We use this source also for various reports.
89            # In such a case the context of the source will be the
90            # certificate itself.
91            certificate = context
92        else:
93            certificate = getattr(context, 'certificate', None)
94        if certificate is not None:
95            start_level = int(certificate.start_level)
96            end_level = int(certificate.end_level)
97        else:
98            # default level range
99            start_level = 10
100            end_level = 1000
101        if 999 in (start_level, end_level):
102            if value != 999:
103                return _('Error: wrong level id ${value}',
104                    mapping={'value': value})
105        if value == 999:
106            return course_levels.by_value[999].title
107        if start_level == 10 and end_level == 10 and value != 10:
108            return _('Error: level id ${value} out of range',
109                mapping={'value': value})
110        if value < start_level or value > end_level + 120:
111            return _('Error: level id ${value} out of range',
112                mapping={'value': value})
113        # Special treatment for pre-studies level
114        if value == 10:
115            return course_levels.by_value[value].title
116        level,repeat = divmod(value, 100)
117        level = level * 100
118        repeat = repeat//10
119        title = course_levels.by_value[level].title
120        if level > end_level and repeat == 1:
121            title = course_levels.by_value[level - 100].title
122            return _('${title} 2nd spillover', mapping={'title': title})
123        if level > end_level and repeat == 2:
124            title = course_levels.by_value[level - 100].title
125            return _('${title} 3rd spillover', mapping={'title': title})
126        if level > end_level:
127            title = course_levels.by_value[level - 100].title
128            return  _('${title} 1st spillover', mapping={'title': title})
129        if repeat == 1:
130            return _('${title} on 1st probation', mapping={'title': title})
131        if repeat == 2:
132            return _('${title} on 2nd probation', mapping={'title': title})
133        return title
134
135class GenderSource(BasicSourceFactory):
136    """A gender source delivers basically a mapping
137       ``{'m': 'Male', 'f': 'Female'}``
138
139       Using a source, we make sure that the tokens (which are
140       stored/expected for instance from CSV files) are something one
141       can expect and not cryptic IntIDs.
142    """
143    def getValues(self):
144        return ['m', 'f']
145
146    def getToken(self, value):
147        return value[0].lower()
148
149    def getTitle(self, value):
150        if value == 'm':
151            return _('male')
152        if value == 'f':
153            return _('female')
154
155class RegNumNotInSource(ValidationError):
156    """Registration number exists already
157    """
158    # The docstring of ValidationErrors is used as error description
159    # by zope.formlib.
160    pass
161
162class MatNumNotInSource(ValidationError):
163    """Matriculation number exists already
164    """
165    # The docstring of ValidationErrors is used as error description
166    # by zope.formlib.
167    pass
168
169class RegNumberSource(object):
170    """A source that accepts any entry for a certain field if not used
171    already.
172
173    Using this kind of source means a way of setting an invariant.
174
175    We accept a value iff:
176    - the value cannot be found in catalog or
177    - the value can be found as part of some item but the bound item
178    is the context object itself.
179    """
180    implements(ISource)
181    cat_name = 'students_catalog'
182    field_name = 'reg_number'
183    validation_error = RegNumNotInSource
184    comp_field = 'student_id'
185    def __init__(self, context):
186        self.context = context
187        return
188
189    def __contains__(self, value):
190        """We accept all values not already given to other students.
191        """
192        cat = queryUtility(ICatalog, self.cat_name)
193        if cat is None:
194            return True
195        kw = {self.field_name: (value, value)}
196        results = cat.searchResults(**kw)
197        for entry in results:
198            if not hasattr(self.context, self.comp_field):
199                # we have no context with comp_field (most probably
200                # while adding a new object, where the container is
201                # the context) which means that the value was given
202                # already to another object (as _something_ was found in
203                # the catalog with that value). Fail on first round.
204                raise self.validation_error(value)
205            if getattr(entry, self.comp_field) != getattr(
206                self.context, self.comp_field):
207                # An entry already given to another student is not in our
208                # range of acceptable values.
209                raise self.validation_error(value)
210                #return False
211        return True
212
213def contextual_reg_num_source(context):
214    source = RegNumberSource(context)
215    return source
216directlyProvides(contextual_reg_num_source, IContextSourceBinder)
217
218class MatNumberSource(RegNumberSource):
219    field_name = 'matric_number'
220    validation_error = MatNumNotInSource
221
222def contextual_mat_num_source(context):
223    source = MatNumberSource(context)
224    return source
225directlyProvides(contextual_mat_num_source, IContextSourceBinder)
Note: See TracBrowser for help on using the repository browser.