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

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

If end_level == 10 students can't continue with regular levels (test will follow).

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