source: main/waeup.ikoba/trunk/src/waeup/ikoba/fees.py @ 12349

Last change on this file since 12349 was 11997, checked in by Henrik Bettermann, 10 years ago

propset svn:keywords "Id"

  • Property svn:keywords set to Id
File size: 8.6 KB
RevLine 
[9390]1## $Id: fees.py 11997 2014-11-19 17:02:48Z henrik $
2##
3## Copyright (C) 2012 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"""Components for fee management.
19
20Fees here are considered as a pair <PARAMS, VALUES> with both being a
21nested tuple. This is a way to describe a table with nested columns
22and its values.
23
24For example let's have a look at the following table:
25
26  +------+------------------+----------------------+
27  |      | locals           | non-locals           |
28  +------+--------+---------+------------+---------+
29  |      | art    | science | art        | science |
30  +------+--------+---------+------------+---------+
31  |fac1  |    1.1 |     1.2 |        1.3 |     1.4 |
32  +------+--------+---------+------------+---------+
33  |fac2  |    2.1 |     2.2 |        2.3 |     2.4 |
34  +------+--------+---------+------------+---------+
35
36This table contains parameters (all the headings) and numbers. The
37numbers are our VALUES while the headings are our PARAMS.
38
39Now the parameters are organized in a specific way: each value
40represents exactly one heading out of the following sets: {fac1,
41fac2}, {locals, non-locals}, {art, science}.
42
43The value 1.2, for instance, stands for 'fac1' and 'locals' and
44'science' (and it is the only value for this combination of
45parameters).
46
47We can therefore write the set of parameters as a tuple of tuples like
48this::
49
50  (('locals', 'non-locals'), ('art', 'science'), ('fac1', 'fac2'))
51
52This way we define how parameters must be used with an instance of the
53:class:`FeeTable` class below and its methods. Please note that order
54of elements matters in tuples!
55
56We can set whatever headers we like and the order is not important,
57until we want to set/get the single values.
58
59The values then must be set accordingly. If the above tuple defines
60the parameters, then the following nested tuple represents respective
61values that can be assigned to a fixed set of parameters::
62
63  (
64   ((1.1, 2.1),   # locals, art, fac1/fac2
65    (1.2, 2.2)),  # locals, science, fac1/fac2
66   ((1.3, 2.3),   # non-locals, art, fac1/fac2
67    (1.4, 2.4)),  # non-locals, science, fac1/fac2
68  )
69
70This is complicated. Let's have a closer look. If we have N param
71tuples, then the values must be delivered with a nesting level of N as
72well:
73
74  params := (A, B, C)  ->  N=3 -> (((...)))  [tuples with N levels]
75
76Each inner tuple must contain as much elements as the last param. So,
77if C contains three headings, then all inner tuples must be triples:
78
79  params := (A, B, C), C:= ('f1', 'f2', 'f3') -> (((v1, v2, v3), ...))
80
81For practical purposes you can imagine the above values as a table as
82well:
83
84  +-----------+---------+-------+--------+
85  |           |         |  fac1 |   fac2 |
86  +-----------+---------+-------+--------+
87  |locals     | art     |   1.1 |    2.1 |
88  |           | science |   1.2 |    2.2 |
89  |non-locals | art     |   1.3 |    2.3 |
90  |           | science |   1.4 |    2.4 |
91  +-----------+---------+-------+--------+
92
93Now, if you look closely and read the table from left to right
94column-wise and from top to bottom, you will notice that the order of
95appearances of headings is exactly like in the `params` tuple defined
96above:
97
98  ('locals', 'non-locals'), ('art', 'science'), ('fac1', 'fac2')
99
100The only condition is, that column headings are not nested. If they
101are, but row headings are not, then simply 'turn' the table by 90
102degrees and you have a table that can be used as a pattern for input.
103
104This should help to create fee tables out of tuples.
105
106We'll try to provide more convenient input methods in future.
107
108"""
109import grok
110import itertools
111from zope import schema
112from zope.interface import Interface, implementer, Attribute
[11949]113from waeup.ikoba.utils.helpers import product
[9390]114
115class IFees(Interface):
116    """A utility storing fee tables for different academic sessions.
117    """
118    tables = Attribute("""A dict of fee tables with session as key.""")
119
120class IFeeTable(Interface):
121
122    values = Attribute("""Nested tuples containing cell values.""")
123    params = Attribute("""Parameter names required to get"""
124                            """or set the fee table values.""")
125
126    def as_table():
127        """Return fees as a tuple of tuples (of tuples).
128        """
129
130    def as_dict():
131        """Return fees as dict.
132        """
133
134    def import_values(params, values):
135        """Set fees with parameter names and value dictionary.
136        """
137
138    def set_fee(params_tuple, value):
139        """Set a single entry in fee table.
140        """
141
142    def get_fee(params_tuple):
143        """Get single value in fee table denoted by `params_tuple`.
144        """
145
146def nested_list(nested_tuple):
147    """Turn nested tuples in nested lists: ((A,B),(C,)) -> [[A,B],[C,]]
148    """
149    if isinstance(nested_tuple, tuple):
150        return [nested_list(x) for x in nested_tuple]
151    return nested_tuple
152
153def nested_tuple(nested_list):
154    """Turn nested lists in nested tuples: [[A,B],[C,]] -> ((A,B),(C,))
155    """
156    if isinstance(nested_list, list):
157        return tuple([nested_tuple(x) for x in nested_list])
158    return nested_list
159
160@implementer(IFeeTable)
161class FeeTable(grok.Model):
162
163    params = ()
164    values = ()
165
166    def __init__(self, params=(), values=None):
167        if params and values is not None:
168            self.import_values(params=params, values=values)
169        return
170
171    def as_table(self, row_index=None):
172        pass
173
174    def as_dict(self):
175        """Get all fees as dict with param tuples as keys.
176
177        Example:
178
179          {
180            ('locals', 'art'): 1,
181            ('locals', 'science'): 2,
182            ('non-locals', 'art'): 3,
183            ('non-locals', 'science'): 4,
184          }
185        """
186        result = dict()
187        combinations = list(itertools.product(*self.params))
188        for item in combinations:
189            result[item] = self.get_fee(item)
190        return result
191
192    def import_values(self, params=(), values=()):
193        if params and values:
194            self._check(params, values)
195        self.values = values
196        self.params = params
197        return
198
199    def set_fee(self, params_tuple, value):
200        """Set a single entry in fee table.
201
202        The `params_tuple` is described more thoroughly in
203        :meth:`get_fee()`.
204        """
205        values_list = nested_list(self.values)
206        sublist = values_list
207        for num, param in enumerate(params_tuple):
208            col_name_tuple = self.params[num]
209            if param not in col_name_tuple:
210                raise KeyError("%s not in %s" % (param, col_name_tuple))
211            if num == len(self.params) - 1:
212                sublist[col_name_tuple.index(param)] = value
213            else:
214                sublist = sublist[col_name_tuple.index(param)]
215        self.values = nested_tuple(values_list)
216        pass
217
218    def get_fee(self, params_tuple):
219        """Get value in fee table denoted by `param_tuple`.
220
221        The `params_tuple` must contain exactly one value for each
222        tuple in instance `params`. For instance, if we have instance
223        tuples (row and col names)
224
225          (('locals', 'non-locals'), ('art', 'science')),
226
227        then `params_tuple` must be a 2-length tuple with
228        exactly one of `locals`/`non-locals` and exactly one of
229        `art`/`science` in exactly that order (locals/non-locals
230        before art/science).
231        """
232        result = self.values
233        for num, param in enumerate(params_tuple):
234            col_name_tuple = self.params[num]
235            if param not in col_name_tuple:
236                raise KeyError("%s not in %s" % (param, col_name_tuple))
237            result = result[col_name_tuple.index(param)]
238        return result
239
240    def _check(self, params, values):
241        # XXX: something useful here, please
242        return
243
244DEFAULT_FEE_TABLE = FeeTable(
245    params = (('locals', 'non-locals'), ('art', 'science'),
246              ('fac1', 'fac2')),
247    values = (((1.1, 2.1), (1.2, 2.2)), ((1.3, 2.3), (1.4, 2.4)))
248    )
249
250
251@implementer(IFees)
252class Fees(grok.GlobalUtility):
253
254    grok.name('default')
255
256    def __init__(self):
257        self.tables = {
258            '2012': DEFAULT_FEE_TABLE,
259            }
Note: See TracBrowser for help on using the repository browser.