2
# SchoolTool - common information systems platform for school administration
3
# Copyright (c) 2013 Shuttleworth Foundation
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.
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.
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/>.
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
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
41
class TemporalStateAccessor(object):
43
def __init__(self, state):
45
if 'tmp' not in state:
49
all = self.state['tmp']
50
for date, (meaning, code) in reversed(all):
51
yield date, meaning, code
53
def __delitem__(self, date):
54
data = dict(self.state['tmp'])
56
self.state['tmp'] = tuple(sorted(data.items(), reverse=True))
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))
67
def replace(self, states):
69
self.state['tmp'] = tuple(sorted(data.items(), reverse=True))
71
def closest(self, date):
72
data = self.state['tmp']
74
return ACTIVE, ACTIVE_CODE
75
for sd, result in data:
81
data = self.state['tmp']
83
return ACTIVE, ACTIVE_CODE
84
for sd, result in data:
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
97
state = self.get(date)
104
if states and code not in states:
115
data = self.state['tmp']
117
return ACTIVE, ACTIVE_CODE
123
dateman = queryUtility(IDateManager)
124
if dateman is not None:
125
today = dateman.today
127
today = datetime.date.today()
128
return self.get(today)
134
class ILinkStateModifiedEvent(Interface):
139
@implementer(ILinkStateModifiedEvent)
140
class LinkStateModifiedEvent(object):
142
def __init__(self, link, this, other, date, meaning, code):
147
self.meaning = meaning
151
class BoundTemporalRelationshipProperty(BoundRelationshipProperty):
152
"""Temporal relationship property bound to an object."""
154
def __init__(self, this, rel_type, my_role, other_role,
155
filter_meanings=(ACTIVE,), filter_date=_today,
157
BoundRelationshipProperty.__init__(
158
self, this, rel_type, my_role, other_role)
159
if filter_date is _today:
160
self.filter_date = self.today
162
self.filter_date = filter_date
163
self.filter_codes = set(filter_codes)
164
self.filter_meanings = filter_meanings
169
dateman = queryUtility(IDateManager)
170
if dateman is not None:
172
return datetime.date.today()
174
def _filter_nothing(self, link):
175
return link.rel_type_hash == hash(self.rel_type)
177
def _filter_latest_meanings(self, link):
178
if link.rel_type_hash != hash(self.rel_type):
180
for meaning in link.state.latest[0]:
181
for val in self.filter_meanings:
186
def _filter_everything(self, link):
187
if link.rel_type_hash != hash(self.rel_type):
189
return link.state.has(
190
date=self.filter_date, states=self.filter_codes,
191
meanings=self.filter_meanings)
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:
199
self._filter = self._filter_nothing
201
self._filter = self._filter_latest_meanings
203
self._filter = self._filter_everything
205
def filter(self, links):
207
if (link.rel_type_hash == hash(self.rel_type) and self._filter(link)):
210
def __contains__(self, other):
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
222
def _iter_filtered_links(self):
223
links = IRelationshipLinks(self.this).getCachedLinksByRole(self.other_role)
225
if self._filter(link):
228
def __nonzero__(self):
229
for link in self._iter_filtered_links():
235
for link in self._iter_filtered_links():
240
for link in self._iter_filtered_links():
241
if self._filter(link):
245
def relationships(self):
246
for link in self._iter_filtered_links():
247
yield RelationshipInfo(self.this, link)
250
return self.__class__(
251
self.this, self.rel_type, self.my_role, self.other_role,
252
filter_meanings=self.filter_meanings,
254
filter_codes=self.filter_codes)
256
def any(self, *args, **kw):
258
[''.join(sorted(set(meaning))) for meaning in args] +
259
[''.join(sorted(set(meaning))) for meaning in kw.values()]
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)
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,
273
return self.__class__(
274
self.this, self.rel_type, self.my_role, self.other_role,
275
filter_meanings=(), filter_date=None,
278
def relate(self, other, meaning=ACTIVE, code=ACTIVE_CODE):
279
links = IRelationshipLinks(self.this)
281
link = links.find(self.my_role, other, self.other_role, self.rel_type)
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))
291
def unrelate(self, other):
292
"""Delete state on filtered date or unrelate completely if
293
no states left or filtered date is .all()
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))
303
date = state.closest(self.filter_date)
305
raise KeyError(self.filter_date)
309
except StopIteration:
310
unrelate(self.rel_type,
311
(self.this, self.my_role),
312
(other, self.other_role))
314
def add(self, other, code=ACTIVE_CODE):
315
self.relate(other, meaning=ACTIVE, code=code)
317
def remove(self, other, code=INACTIVE_CODE):
318
self.relate(other, meaning=INACTIVE, code=code)
320
def state(self, other):
321
links = IRelationshipLinks(self.this)
323
link = links.find(self.my_role, other, self.other_role, self.rel_type)
329
class TemporalURIObject(URIObject):
332
return PersistentTemporalURIObject(
333
self, self._uri, name=self._name, description=self._description)
335
def access(self, state):
336
return TemporalStateAccessor(state)
338
def bind(self, instance, my_role, rel_type, other_role):
339
return BoundTemporalRelationshipProperty(
340
instance, rel_type, my_role, other_role)
344
dateman = queryUtility(IDateManager)
345
if dateman is not None:
346
today = dateman.today
348
today = datetime.date.today()
350
if link.rel_type_hash != hash(self):
352
state = self.access(link.shared_state)
353
return state.has(date=today, meanings=(ACTIVE,))
357
class PersistentTemporalURIObject(Persistent, Contained, TemporalURIObject):
363
def shareTemporalState(event):
364
if not isinstance(event.rel_type, TemporalURIObject):
366
if 'tmp' not in event.shared:
367
event.shared['tmp'] = ()