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/>.
19
Catalog relationship links.
20
Make some relationships temporal.
26
from BTrees import IFBTree
27
from BTrees.OOBTree import OOBTree
28
from zope.annotation.interfaces import IAnnotations
29
from zope.app.generations.utility import getRootFolder
30
from zope.component.hooks import getSite, setSite
31
from zope.event import notify
32
from zope.intid import IIntIds
33
from zope.intid.interfaces import IntIdAddedEvent
34
from zope.lifecycleevent import ObjectAddedEvent, ObjectModifiedEvent
35
from zope.keyreference.interfaces import IKeyReference
36
from zope.component import getUtility
38
from schooltool.app.interfaces import ISchoolToolApplication
39
from schooltool.app.catalog import VersionedCatalog
40
from schooltool.app.membership import Membership
41
from schooltool.app.relationships import Leadership, Instruction
42
from schooltool.basicperson.advisor import Advising
43
from schooltool.contact.contact import URIContactRelationship, URIContact, URIPerson
44
from schooltool.relationship.relationship import getLinkCatalog
45
from schooltool.relationship.catalog import LinkCatalog, URICache
46
from schooltool.relationship.temporal import TemporalURIObject
47
from schooltool.relationship.interfaces import IRelationshipLinks
48
from schooltool.app.membership import URIMembership, URIGroup, URIMember
49
from schooltool.app.overlay import URICalendarSubscription
50
from schooltool.app.overlay import URICalendarProvider
51
from schooltool.app.overlay import URICalendarSubscriber
52
from schooltool.app.relationships import URIInstruction
53
from schooltool.app.relationships import URIInstructor
54
from schooltool.app.relationships import URISection
55
from schooltool.app.relationships import URICourseSections
56
from schooltool.app.relationships import URICourse
57
from schooltool.app.relationships import URISectionOfCourse
58
from schooltool.app.relationships import URILeadership
59
from schooltool.app.relationships import URILeader
60
from schooltool.app.relationships import URIAsset
61
from schooltool.basicperson.advisor import URIAdvising
62
from schooltool.basicperson.advisor import URIStudent
63
from schooltool.basicperson.advisor import URIAdvisor
64
from schooltool.course.booking import URISectionBooking
65
from schooltool.course.booking import URIResource
66
from schooltool.level.level import URILevelCourses
67
from schooltool.level.level import URILevel
70
PERSON_CONTACT_KEY = 'schooltool.contact.basicperson'
72
CONTACT_RELATIONSHIPS_MAP = {
73
'parent': ('ap', 'p'),
74
'step_parent': ('ap', 'sp'),
75
'foster_parent': ('ap', 'fp'),
76
'guardian': ('ap', 'g'),
77
'sibling': ('a', 's'),
80
def addLinkCatalog(app):
81
catalogs = app['schooltool.app.catalog:Catalogs']
82
factory = LinkCatalog(app)
83
version = factory.getVersion()
84
catalog = factory.createCatalog()
85
catalogs[factory.key()] = VersionedCatalog(catalog, version)
86
factory.setIndexes(catalog)
89
def collectOIDs(connection):
90
storage = connection._storage
96
oid, tid, data, next_oid = storage.record_iternext(next_oid)
98
if data.startswith('cschooltool.relationship.relationship\nLink\n'):
99
if oid not in link_oids:
100
link_oids.append(oid)
101
elif data.startswith('cschooltool.relationship.relationship\nLinkSet\n'):
102
if oid not in linkset_oids:
103
linkset_oids.append(oid)
107
return link_oids, linkset_oids
110
def evolveLinks(connection, oids):
111
int_ids = getSite()._sm.getUtility(IIntIds)
114
for n, oid in enumerate(oids):
116
link = connection.get(oid)
119
links = link.__parent__._links
120
if (link.__name__ not in links or
121
links[link.__name__] is not link):
122
# this is a record of a replaced link, skip.
124
key = IKeyReference(link)
126
int_ids: int_ids.register(key)}
128
this = link.__parent__.__parent__
130
hash(IKeyReference(this)), hash(link.my_role),
131
hash(IKeyReference(link.target)), hash(link.role),
135
hash(IKeyReference(link.target)), hash(link.role),
136
hash(IKeyReference(this)), hash(link.my_role),
140
if backhash in states:
141
link.shared = states[thishash] = states[backhash]
143
assert thishash not in states
144
states[thishash] = link.shared = OOBTree()
145
link.shared['X'] = link.__dict__.get('extra_info')
146
if 'extra_info' in link.__dict__:
147
del link.__dict__['extra_info']
148
if isinstance(link.rel_type, TemporalURIObject):
149
link.shared['tmp'] = ()
151
notify(IntIdAddedEvent(link, ObjectAddedEvent(link), idmap))
153
if n % 10000 == 9999:
154
transaction.savepoint(optimistic=True)
157
def evolveLinkSets(connection, oids):
158
int_ids = getSite()._sm.getUtility(IIntIds)
162
linkset = connection.get(oid)
165
if getattr(linkset, '_lids', None) is None:
166
linkset._lids = IFBTree.TreeSet()
167
linkset._lids.clear()
168
for link in linkset._links.values():
169
linkset._lids.add(int_ids.getId(link))
172
def evolveRelationships(date, target, rel_type, other_role, new_type):
173
int_ids = getUtility(IIntIds)
174
links = IRelationshipLinks(target).iterLinksByRole(other_role)
176
link = int_ids.getObject(link.lid)
177
if link.rel_type != rel_type:
179
backlink = IRelationshipLinks(link.target).find(
180
link.role, target, link.my_role, link.rel_type)
181
link.rel_type = backlink.rel_type = new_type
182
if 'tmp' not in link.shared:
183
link.shared['tmp'] = ()
185
notify(ObjectModifiedEvent(link))
186
notify(ObjectModifiedEvent(backlink))
189
def evolveSectionsRelationships(app):
190
int_ids = getUtility(IIntIds)
191
containers = app['schooltool.course.section']
192
for term_id, container in containers.items():
193
term = int_ids.queryObject(int(term_id))
195
del containers[term_id]
197
for section in container.values():
198
members = section.members
200
term.first, section, members.rel_type, members.other_role,
202
instructors = section.instructors
204
term.first, section, instructors.rel_type, instructors.other_role,
205
Instruction.rel_type)
208
def evolveGroupRelationships(app):
209
int_ids = getUtility(IIntIds)
210
containers = app['schooltool.group']
211
for term_id, container in containers.items():
212
term = int_ids.getObject(int(term_id))
213
for group in container.values():
214
members = group.members
216
term.first, group, members.rel_type, members.other_role,
218
leaders = group.leaders
220
term.first, group, leaders.rel_type, leaders.other_role,
224
def evolveCourseRelationships(app):
225
int_ids = getUtility(IIntIds)
226
containers = app['schooltool.course.course']
227
for year_id, container in containers.items():
228
year = int_ids.queryObject(int(year_id))
230
del containers[year_id]
232
for course in container.values():
233
leaders = course.leaders
235
year.first, course, leaders.rel_type, leaders.other_role,
239
def evolveResourceRelationships(app):
240
if app['schooltool.schoolyear']:
241
date = min([year.first for year in app['schooltool.schoolyear'].values()])
243
date = datetime.datetime.today().date()
245
container = app['resources']
246
for name, resource in container.items():
247
leaders = resource.leaders
249
date, resource, leaders.rel_type, leaders.other_role,
253
def evolveAdvisorRelationships(app):
254
if app['schooltool.schoolyear']:
255
date = min([year.first for year in app['schooltool.schoolyear'].values()])
257
date = datetime.datetime.today().date()
258
for person in app['persons'].values():
259
advisors = person.advisors
261
date, person, advisors.rel_type, advisors.other_role,
265
def evolveContactRelationships(app):
266
if app['schooltool.schoolyear']:
267
date = min([year.first for year in app['schooltool.schoolyear'].values()])
269
date = datetime.datetime.today().date()
271
contacts = app['schooltool.contact'].values()
272
for contact in contacts:
274
date, contact, URIContactRelationship, URIPerson,
275
URIContactRelationship)
276
# Also evolve pre gen-31 relationships
278
date, contact, URIContact, URIPerson,
279
URIContactRelationship)
281
persons = app['persons'].values()
282
for person in persons:
283
annotations = IAnnotations(person)
284
bound = annotations.get(PERSON_CONTACT_KEY, None)
288
date, bound, URIContactRelationship, URIPerson,
289
URIContactRelationship)
290
# Also evolve pre gen-31 relationships
292
date, bound, URIContact, URIPerson,
293
URIContactRelationship)
295
catalog = getLinkCatalog()
296
int_ids = getUtility(IIntIds)
298
for n, iid in enumerate(catalog.extent):
299
if (n+1) % 10000 == 0:
300
transaction.savepoint(optimistic=True)
302
link = int_ids.getObject(iid)
303
if link.rel_type != URIContactRelationship:
305
shared = link.shared_state
306
extra_info = shared['X']
307
if extra_info is None:
309
meaning, code = CONTACT_RELATIONSHIPS_MAP.get(
310
extra_info.relationship, ('a', 'a'))
311
link.state.set(date, meaning=meaning, code=code)
315
def requireURICache(app):
316
if 'schooltool.relationship.uri' not in app:
317
cache = app['schooltool.relationship.uri'] = URICache()
319
URIMembership, URIGroup, URIMember, URICalendarSubscription,
320
URICalendarProvider, URICalendarSubscriber, URIInstruction,
321
URIInstructor, URISection, URICourseSections, URICourse,
322
URISectionOfCourse, URILeadership, URILeader, URIAsset,
323
URIAdvising, URIStudent, URIAdvisor, URIContactRelationship,
324
URIContact, URIPerson, URISectionBooking, URISection,
325
URIResource, URILevelCourses, URICourse, URILevel]
326
for uri in standard_uris:
331
root = getRootFolder(context)
334
assert ISchoolToolApplication.providedBy(root)
337
requireURICache(root)
339
connection = context.connection
341
link_oids, linkset_oids = collectOIDs(connection)
343
evolveLinks(connection, link_oids)
344
transaction.savepoint(optimistic=True)
346
evolveLinkSets(connection, linkset_oids)
347
transaction.savepoint(optimistic=True)
350
transaction.savepoint(optimistic=True)
352
evolveGroupRelationships(root)
353
evolveCourseRelationships(root)
354
evolveResourceRelationships(root)
355
evolveAdvisorRelationships(root)
356
evolveSectionsRelationships(root)
358
transaction.savepoint(optimistic=True)
360
evolveContactRelationships(root)
365
def ensureEvolved(context):
366
root = getRootFolder(context)
367
assert ISchoolToolApplication.providedBy(root)
368
catalogs = root['schooltool.app.catalog:Catalogs']
369
if LinkCatalog.key() in catalogs: