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

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

When transferring students StudyLevelSource? will be called in the context of a student. Thus study_levels is not necessarily a function of a studycourse.

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