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

« back to all changes in this revision

Viewing changes to src/schooltool/generations/linkcatalogs.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
Catalog relationship links.
 
20
Make some relationships temporal.
 
21
"""
 
22
 
 
23
import datetime
 
24
import transaction
 
25
 
 
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
 
37
 
 
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
 
68
 
 
69
 
 
70
PERSON_CONTACT_KEY = 'schooltool.contact.basicperson'
 
71
 
 
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'),
 
78
}
 
79
 
 
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)
 
87
 
 
88
 
 
89
def collectOIDs(connection):
 
90
    storage = connection._storage
 
91
    next_oid = None
 
92
    link_oids = []
 
93
    linkset_oids = []
 
94
    total = 0
 
95
    while True:
 
96
        oid, tid, data, next_oid = storage.record_iternext(next_oid)
 
97
        total += 1
 
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)
 
104
        if next_oid is None:
 
105
            break
 
106
 
 
107
    return link_oids, linkset_oids
 
108
 
 
109
 
 
110
def evolveLinks(connection, oids):
 
111
    int_ids = getSite()._sm.getUtility(IIntIds)
 
112
 
 
113
    states = {}
 
114
    for n, oid in enumerate(oids):
 
115
        try:
 
116
            link = connection.get(oid)
 
117
        except KeyError:
 
118
            continue
 
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.
 
123
            continue
 
124
        key = IKeyReference(link)
 
125
        idmap = {
 
126
            int_ids: int_ids.register(key)}
 
127
 
 
128
        this = link.__parent__.__parent__
 
129
        thishash = (
 
130
            hash(IKeyReference(this)), hash(link.my_role),
 
131
            hash(IKeyReference(link.target)), hash(link.role),
 
132
            hash(link.rel_type),
 
133
            )
 
134
        backhash = (
 
135
            hash(IKeyReference(link.target)), hash(link.role),
 
136
            hash(IKeyReference(this)), hash(link.my_role),
 
137
            hash(link.rel_type),
 
138
            )
 
139
 
 
140
        if backhash in states:
 
141
            link.shared = states[thishash] = states[backhash]
 
142
        else:
 
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'] = ()
 
150
 
 
151
        notify(IntIdAddedEvent(link, ObjectAddedEvent(link), idmap))
 
152
 
 
153
        if n % 10000 == 9999:
 
154
            transaction.savepoint(optimistic=True)
 
155
 
 
156
 
 
157
def evolveLinkSets(connection, oids):
 
158
    int_ids = getSite()._sm.getUtility(IIntIds)
 
159
 
 
160
    for oid in oids:
 
161
        try:
 
162
            linkset = connection.get(oid)
 
163
        except KeyError:
 
164
            continue
 
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))
 
170
 
 
171
 
 
172
def evolveRelationships(date, target, rel_type, other_role, new_type):
 
173
    int_ids = getUtility(IIntIds)
 
174
    links = IRelationshipLinks(target).iterLinksByRole(other_role)
 
175
    for link in links:
 
176
        link = int_ids.getObject(link.lid)
 
177
        if link.rel_type != rel_type:
 
178
            continue
 
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'] = ()
 
184
        link.state.set(date)
 
185
        notify(ObjectModifiedEvent(link))
 
186
        notify(ObjectModifiedEvent(backlink))
 
187
 
 
188
 
 
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))
 
194
        if not term:
 
195
            del containers[term_id]
 
196
            continue
 
197
        for section in container.values():
 
198
            members = section.members
 
199
            evolveRelationships(
 
200
                term.first, section, members.rel_type, members.other_role,
 
201
                Membership.rel_type)
 
202
            instructors = section.instructors
 
203
            evolveRelationships(
 
204
                term.first, section, instructors.rel_type, instructors.other_role,
 
205
                Instruction.rel_type)
 
206
 
 
207
 
 
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
 
215
            evolveRelationships(
 
216
                term.first, group, members.rel_type, members.other_role,
 
217
                Membership.rel_type)
 
218
            leaders = group.leaders
 
219
            evolveRelationships(
 
220
                term.first, group, leaders.rel_type, leaders.other_role,
 
221
                Leadership.rel_type)
 
222
 
 
223
 
 
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))
 
229
        if not year:
 
230
            del containers[year_id]
 
231
            continue
 
232
        for course in container.values():
 
233
            leaders = course.leaders
 
234
            evolveRelationships(
 
235
                year.first, course, leaders.rel_type, leaders.other_role,
 
236
                Leadership.rel_type)
 
237
 
 
238
 
 
239
def evolveResourceRelationships(app):
 
240
    if app['schooltool.schoolyear']:
 
241
        date = min([year.first for year in app['schooltool.schoolyear'].values()])
 
242
    else:
 
243
        date = datetime.datetime.today().date()
 
244
 
 
245
    container = app['resources']
 
246
    for name, resource in container.items():
 
247
        leaders = resource.leaders
 
248
        evolveRelationships(
 
249
            date, resource, leaders.rel_type, leaders.other_role,
 
250
                Leadership.rel_type)
 
251
 
 
252
 
 
253
def evolveAdvisorRelationships(app):
 
254
    if app['schooltool.schoolyear']:
 
255
        date = min([year.first for year in app['schooltool.schoolyear'].values()])
 
256
    else:
 
257
        date = datetime.datetime.today().date()
 
258
    for person in app['persons'].values():
 
259
        advisors = person.advisors
 
260
        evolveRelationships(
 
261
            date, person, advisors.rel_type, advisors.other_role,
 
262
            Advising.rel_type)
 
263
 
 
264
 
 
265
def evolveContactRelationships(app):
 
266
    if app['schooltool.schoolyear']:
 
267
        date = min([year.first for year in app['schooltool.schoolyear'].values()])
 
268
    else:
 
269
        date = datetime.datetime.today().date()
 
270
 
 
271
    contacts = app['schooltool.contact'].values()
 
272
    for contact in contacts:
 
273
        evolveRelationships(
 
274
            date, contact, URIContactRelationship, URIPerson,
 
275
            URIContactRelationship)
 
276
        # Also evolve pre gen-31 relationships
 
277
        evolveRelationships(
 
278
            date, contact, URIContact, URIPerson,
 
279
            URIContactRelationship)
 
280
 
 
281
    persons = app['persons'].values()
 
282
    for person in persons:
 
283
        annotations = IAnnotations(person)
 
284
        bound = annotations.get(PERSON_CONTACT_KEY, None)
 
285
        if bound is None:
 
286
            continue
 
287
        evolveRelationships(
 
288
            date, bound, URIContactRelationship, URIPerson,
 
289
            URIContactRelationship)
 
290
        # Also evolve pre gen-31 relationships
 
291
        evolveRelationships(
 
292
            date, bound, URIContact, URIPerson,
 
293
            URIContactRelationship)
 
294
 
 
295
    catalog = getLinkCatalog()
 
296
    int_ids = getUtility(IIntIds)
 
297
 
 
298
    for n, iid in enumerate(catalog.extent):
 
299
        if (n+1) % 10000 == 0:
 
300
            transaction.savepoint(optimistic=True)
 
301
 
 
302
        link = int_ids.getObject(iid)
 
303
        if link.rel_type != URIContactRelationship:
 
304
            continue
 
305
        shared = link.shared_state
 
306
        extra_info = shared['X']
 
307
        if extra_info is None:
 
308
            continue
 
309
        meaning, code = CONTACT_RELATIONSHIPS_MAP.get(
 
310
            extra_info.relationship, ('a', 'a'))
 
311
        link.state.set(date, meaning=meaning, code=code)
 
312
        shared['X'] = None
 
313
 
 
314
 
 
315
def requireURICache(app):
 
316
    if 'schooltool.relationship.uri' not in app:
 
317
        cache = app['schooltool.relationship.uri'] = URICache()
 
318
        standard_uris = [
 
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:
 
327
            cache.cache(uri)
 
328
 
 
329
 
 
330
def evolve(context):
 
331
    root = getRootFolder(context)
 
332
    old_site = getSite()
 
333
 
 
334
    assert ISchoolToolApplication.providedBy(root)
 
335
    setSite(root)
 
336
 
 
337
    requireURICache(root)
 
338
 
 
339
    connection = context.connection
 
340
 
 
341
    link_oids, linkset_oids = collectOIDs(connection)
 
342
 
 
343
    evolveLinks(connection, link_oids)
 
344
    transaction.savepoint(optimistic=True)
 
345
 
 
346
    evolveLinkSets(connection, linkset_oids)
 
347
    transaction.savepoint(optimistic=True)
 
348
 
 
349
    addLinkCatalog(root)
 
350
    transaction.savepoint(optimistic=True)
 
351
 
 
352
    evolveGroupRelationships(root)
 
353
    evolveCourseRelationships(root)
 
354
    evolveResourceRelationships(root)
 
355
    evolveAdvisorRelationships(root)
 
356
    evolveSectionsRelationships(root)
 
357
 
 
358
    transaction.savepoint(optimistic=True)
 
359
 
 
360
    evolveContactRelationships(root)
 
361
 
 
362
    setSite(old_site)
 
363
 
 
364
 
 
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:
 
370
        return
 
371
    evolve(context)
 
372
    transaction.commit()