~replaceafill/ubuntu/trusty/schooltool/2.8_custom-css

« back to all changes in this revision

Viewing changes to src/schooltool/relationship/temporal.py

  • Committer: Gediminas Paulauskas
  • Date: 2014-04-18 16:25:33 UTC
  • mfrom: (1.1.33)
  • Revision ID: menesis@pov.lt-20140418162533-noklnc6b89w2epee
Tags: 1:2.7.0-0ubuntu1
New upstream release.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#
 
2
# SchoolTool - common information systems platform for school administration
 
3
# Copyright (c) 2013 Shuttleworth Foundation
 
4
#
 
5
# This program is free software; you can redistribute it and/or modify
 
6
# it under the terms of the GNU General Public License as published by
 
7
# the Free Software Foundation; either version 2 of the License, or
 
8
# (at your option) any later version.
 
9
#
 
10
# This program is distributed in the hope that it will be useful,
 
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
13
# GNU General Public License for more details.
 
14
#
 
15
# You should have received a copy of the GNU General Public License
 
16
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
17
#
 
18
 
 
19
import datetime
 
20
 
 
21
from persistent import Persistent
 
22
from zope.component import queryUtility
 
23
from zope.container.contained import Contained
 
24
from zope.event import notify
 
25
from zope.interface import implementer
 
26
from zope.interface import Interface
 
27
 
 
28
from schooltool.term.interfaces import IDateManager
 
29
from schooltool.relationship.interfaces import IRelationshipLinks
 
30
from schooltool.relationship.relationship import BoundRelationshipProperty
 
31
from schooltool.relationship.relationship import relate, unrelate
 
32
from schooltool.relationship.relationship import RelationshipInfo
 
33
from schooltool.relationship.uri import URIObject
 
34
 
 
35
ACTIVE = 'a'
 
36
INACTIVE = 'i'
 
37
ACTIVE_CODE = 'a'
 
38
INACTIVE_CODE = 'i'
 
39
 
 
40
 
 
41
class TemporalStateAccessor(object):
 
42
 
 
43
    def __init__(self, state):
 
44
        self.state = state
 
45
        if 'tmp' not in state:
 
46
            state['tmp'] = ()
 
47
 
 
48
    def __iter__(self):
 
49
        all = self.state['tmp']
 
50
        for date, (meaning, code) in reversed(all):
 
51
            yield date, meaning, code
 
52
 
 
53
    def __delitem__(self, date):
 
54
        data = dict(self.state['tmp'])
 
55
        del data[date]
 
56
        self.state['tmp'] = tuple(sorted(data.items(), reverse=True))
 
57
 
 
58
    def all(self):
 
59
        return list(self)
 
60
 
 
61
    def set(self, date, meaning=ACTIVE, code=ACTIVE_CODE):
 
62
        meaning = ''.join(sorted(set(meaning)))
 
63
        data = dict(self.state['tmp'])
 
64
        data[date] = meaning, code
 
65
        self.state['tmp'] = tuple(sorted(data.items(), reverse=True))
 
66
 
 
67
    def replace(self, states):
 
68
        data = dict(states)
 
69
        self.state['tmp'] = tuple(sorted(data.items(), reverse=True))
 
70
 
 
71
    def closest(self, date):
 
72
        data = self.state['tmp']
 
73
        if not data:
 
74
            return ACTIVE, ACTIVE_CODE
 
75
        for sd, result in data:
 
76
            if sd <= date:
 
77
                return sd
 
78
        return None
 
79
 
 
80
    def get(self, date):
 
81
        data = self.state['tmp']
 
82
        if not data:
 
83
            return ACTIVE, ACTIVE_CODE
 
84
        for sd, result in data:
 
85
            if sd <= date:
 
86
                return result
 
87
        return None
 
88
 
 
89
    def has(self, date=None, states=(), meanings=()):
 
90
        if not self.state['tmp']:
 
91
            if ACTIVE in meanings:
 
92
                return (ACTIVE_CODE in states or
 
93
                        not states)
 
94
            else:
 
95
                return False
 
96
        if date is not None:
 
97
            state = self.get(date)
 
98
        else:
 
99
            state = self.latest
 
100
        if state is None:
 
101
            return False
 
102
        meaning = state[0]
 
103
        code = state[1]
 
104
        if states and code not in states:
 
105
            return False
 
106
        if not meanings:
 
107
            return True
 
108
        for val in meanings:
 
109
            if val in meaning:
 
110
                return True
 
111
        return False
 
112
 
 
113
    @property
 
114
    def latest(self):
 
115
        data = self.state['tmp']
 
116
        if not data:
 
117
            return ACTIVE, ACTIVE_CODE
 
118
        day, state = data[0]
 
119
        return state
 
120
 
 
121
    @property
 
122
    def today(self):
 
123
        dateman = queryUtility(IDateManager)
 
124
        if dateman is not None:
 
125
            today = dateman.today
 
126
        else:
 
127
            today = datetime.date.today()
 
128
        return self.get(today)
 
129
 
 
130
 
 
131
_today = object()
 
132
 
 
133
 
 
134
class ILinkStateModifiedEvent(Interface):
 
135
 
 
136
    pass
 
137
 
 
138
 
 
139
@implementer(ILinkStateModifiedEvent)
 
140
class LinkStateModifiedEvent(object):
 
141
 
 
142
    def __init__(self, link, this, other, date, meaning, code):
 
143
        self.link = link
 
144
        self.this = this
 
145
        self.other = other
 
146
        self.date = date
 
147
        self.meaning = meaning
 
148
        self.code = code
 
149
 
 
150
 
 
151
class BoundTemporalRelationshipProperty(BoundRelationshipProperty):
 
152
    """Temporal relationship property bound to an object."""
 
153
 
 
154
    def __init__(self, this, rel_type, my_role, other_role,
 
155
                 filter_meanings=(ACTIVE,), filter_date=_today,
 
156
                 filter_codes=()):
 
157
        BoundRelationshipProperty.__init__(
 
158
            self, this, rel_type, my_role, other_role)
 
159
        if filter_date is _today:
 
160
            self.filter_date = self.today
 
161
        else:
 
162
            self.filter_date = filter_date
 
163
        self.filter_codes = set(filter_codes)
 
164
        self.filter_meanings = filter_meanings
 
165
        self.init_filter()
 
166
 
 
167
    @property
 
168
    def today(self):
 
169
        dateman = queryUtility(IDateManager)
 
170
        if dateman is not None:
 
171
            return dateman.today
 
172
        return datetime.date.today()
 
173
 
 
174
    def _filter_nothing(self, link):
 
175
        return link.rel_type_hash == hash(self.rel_type)
 
176
 
 
177
    def _filter_latest_meanings(self, link):
 
178
        if link.rel_type_hash != hash(self.rel_type):
 
179
            return False
 
180
        for meaning in link.state.latest[0]:
 
181
            for val in self.filter_meanings:
 
182
                if val in meaning:
 
183
                    return True
 
184
        return False
 
185
 
 
186
    def _filter_everything(self, link):
 
187
        if link.rel_type_hash != hash(self.rel_type):
 
188
            return False
 
189
        return link.state.has(
 
190
            date=self.filter_date, states=self.filter_codes,
 
191
            meanings=self.filter_meanings)
 
192
 
 
193
    def init_filter(self):
 
194
        on_date = self.filter_date
 
195
        any_code = self.filter_codes
 
196
        is_active = self.filter_meanings
 
197
        if not any_code and on_date is None:
 
198
            if not is_active:
 
199
                self._filter = self._filter_nothing
 
200
            else:
 
201
                self._filter = self._filter_latest_meanings
 
202
        else:
 
203
            self._filter = self._filter_everything
 
204
 
 
205
    def filter(self, links):
 
206
        for link in links:
 
207
            if (link.rel_type_hash == hash(self.rel_type) and self._filter(link)):
 
208
                yield link
 
209
 
 
210
    def __contains__(self, other):
 
211
        if other is None:
 
212
            return False
 
213
        linkset = IRelationshipLinks(self.this)
 
214
        for link in linkset.getCachedLinksByTarget(other):
 
215
            if (link.rel_type_hash == hash(self.rel_type) and
 
216
                link.my_role_hash == hash(self.my_role) and
 
217
                link.role_hash == hash(self.other_role) and
 
218
                self._filter(link)):
 
219
                return True
 
220
        return False
 
221
 
 
222
    def _iter_filtered_links(self):
 
223
        links = IRelationshipLinks(self.this).getCachedLinksByRole(self.other_role)
 
224
        for link in links:
 
225
            if self._filter(link):
 
226
                yield link
 
227
 
 
228
    def __nonzero__(self):
 
229
        for link in self._iter_filtered_links():
 
230
            return True
 
231
        return False
 
232
 
 
233
    def __len__(self):
 
234
        n = 0
 
235
        for link in self._iter_filtered_links():
 
236
            n += 1
 
237
        return n
 
238
 
 
239
    def __iter__(self):
 
240
        for link in self._iter_filtered_links():
 
241
            if self._filter(link):
 
242
                yield link.target
 
243
 
 
244
    @property
 
245
    def relationships(self):
 
246
        for link in self._iter_filtered_links():
 
247
            yield RelationshipInfo(self.this, link)
 
248
 
 
249
    def on(self, date):
 
250
        return self.__class__(
 
251
            self.this, self.rel_type, self.my_role, self.other_role,
 
252
            filter_meanings=self.filter_meanings,
 
253
            filter_date=date,
 
254
            filter_codes=self.filter_codes)
 
255
 
 
256
    def any(self, *args, **kw):
 
257
        meanings = tuple(
 
258
            [''.join(sorted(set(meaning))) for meaning in args] +
 
259
            [''.join(sorted(set(meaning))) for meaning in kw.values()]
 
260
            )
 
261
        return self.__class__(
 
262
            self.this, self.rel_type, self.my_role, self.other_role,
 
263
            filter_meanings=meanings, filter_date=self.filter_date,
 
264
            filter_codes=self.filter_codes)
 
265
 
 
266
    def coded(self, *codes):
 
267
        return self.__class__(
 
268
            self.this, self.rel_type, self.my_role, self.other_role,
 
269
            filter_meanings=self.filter_meanings, filter_date=self.filter_date,
 
270
            filter_codes=codes)
 
271
 
 
272
    def all(self):
 
273
        return self.__class__(
 
274
            self.this, self.rel_type, self.my_role, self.other_role,
 
275
            filter_meanings=(), filter_date=None,
 
276
            filter_codes=())
 
277
 
 
278
    def relate(self, other, meaning=ACTIVE, code=ACTIVE_CODE):
 
279
        links = IRelationshipLinks(self.this)
 
280
        try:
 
281
            link = links.find(self.my_role, other, self.other_role, self.rel_type)
 
282
        except ValueError:
 
283
            relate(self.rel_type,
 
284
                   (self.this, self.my_role),
 
285
                   (other, self.other_role))
 
286
            link = links.find(self.my_role, other, self.other_role, self.rel_type)
 
287
        link.state.set(self.filter_date, meaning=meaning, code=code)
 
288
        notify(LinkStateModifiedEvent(
 
289
                link, self.this, other, self.filter_date, meaning, code))
 
290
 
 
291
    def unrelate(self, other):
 
292
        """Delete state on filtered date or unrelate completely if
 
293
        no states left or filtered date is .all()
 
294
        """
 
295
        links = IRelationshipLinks(self.this)
 
296
        link = links.find(self.my_role, other, self.other_role, self.rel_type)
 
297
        if self.filter_date is None:
 
298
            unrelate(self.rel_type,
 
299
                     (self.this, self.my_role),
 
300
                     (other, self.other_role))
 
301
            return
 
302
        state = link.state
 
303
        date = state.closest(self.filter_date)
 
304
        if date is None:
 
305
            raise KeyError(self.filter_date)
 
306
        del state[date]
 
307
        try:
 
308
            iter(state).next()
 
309
        except StopIteration:
 
310
            unrelate(self.rel_type,
 
311
                     (self.this, self.my_role),
 
312
                     (other, self.other_role))
 
313
 
 
314
    def add(self, other, code=ACTIVE_CODE):
 
315
        self.relate(other, meaning=ACTIVE, code=code)
 
316
 
 
317
    def remove(self, other, code=INACTIVE_CODE):
 
318
        self.relate(other, meaning=INACTIVE, code=code)
 
319
 
 
320
    def state(self, other):
 
321
        links = IRelationshipLinks(self.this)
 
322
        try:
 
323
            link = links.find(self.my_role, other, self.other_role, self.rel_type)
 
324
        except ValueError:
 
325
            return None
 
326
        return link.state
 
327
 
 
328
 
 
329
class TemporalURIObject(URIObject):
 
330
 
 
331
    def persist(self):
 
332
        return PersistentTemporalURIObject(
 
333
            self, self._uri, name=self._name, description=self._description)
 
334
 
 
335
    def access(self, state):
 
336
        return TemporalStateAccessor(state)
 
337
 
 
338
    def bind(self, instance, my_role, rel_type, other_role):
 
339
        return BoundTemporalRelationshipProperty(
 
340
            instance, rel_type, my_role, other_role)
 
341
 
 
342
    @property
 
343
    def filter(self):
 
344
        dateman = queryUtility(IDateManager)
 
345
        if dateman is not None:
 
346
            today = dateman.today
 
347
        else:
 
348
            today = datetime.date.today()
 
349
        def filter(link):
 
350
            if link.rel_type_hash != hash(self):
 
351
                return False
 
352
            state = self.access(link.shared_state)
 
353
            return state.has(date=today, meanings=(ACTIVE,))
 
354
        return filter
 
355
 
 
356
 
 
357
class PersistentTemporalURIObject(Persistent, Contained, TemporalURIObject):
 
358
 
 
359
    __name__ = None
 
360
    __parent__ = None
 
361
 
 
362
 
 
363
def shareTemporalState(event):
 
364
    if not isinstance(event.rel_type, TemporalURIObject):
 
365
        return
 
366
    if 'tmp' not in event.shared:
 
367
        event.shared['tmp'] = ()