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

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

Add pre-studies level.

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