1 |
1 |
import contextlib
|
2 |
|
import functools
|
3 |
2 |
import threading
|
4 |
3 |
|
5 |
4 |
from django.contrib.auth import get_user_model
|
6 |
5 |
from django.contrib.contenttypes.models import ContentType
|
7 |
|
from django.db import models
|
|
6 |
from django.db import connection, models
|
8 |
7 |
from django.db.models import query
|
9 |
8 |
from django.db.models.query import Prefetch, Q
|
|
9 |
from django.db.transaction import atomic
|
10 |
10 |
|
11 |
11 |
from . import utils
|
12 |
12 |
|
... | ... | |
177 |
177 |
self.tls.CLOSURE_UPDATED = True
|
178 |
178 |
return
|
179 |
179 |
|
180 |
|
# existing indirect paths
|
181 |
|
old = set(self.filter(direct=False).values_list('parent_id', 'child_id'))
|
182 |
|
# existing direct paths
|
183 |
|
ris = set(self.filter(direct=True).values_list('parent_id', 'child_id'))
|
184 |
|
add = set()
|
185 |
|
new = set()
|
186 |
|
old_new = ris
|
187 |
|
|
188 |
|
# Start computing new indirect paths
|
189 |
|
while True:
|
190 |
|
for (i, j) in ris:
|
191 |
|
for (k, l) in old_new:
|
192 |
|
if j == k and (i, l) not in ris:
|
193 |
|
new.add((i, l))
|
194 |
|
if old_new != ris:
|
195 |
|
for (i, j) in old_new:
|
196 |
|
for (k, l) in ris:
|
197 |
|
if j == k and (i, l) not in ris:
|
198 |
|
new.add((i, l))
|
199 |
|
if not new:
|
200 |
|
break
|
201 |
|
add.update(new)
|
202 |
|
ris.update(new)
|
203 |
|
old_new = new
|
204 |
|
new = set()
|
205 |
|
# Create new relations
|
206 |
|
self.model.objects.bulk_create(
|
207 |
|
self.model(parent_id=a, child_id=b, direct=False) for a, b in add - old
|
208 |
|
)
|
209 |
|
# Delete old ones
|
210 |
|
obsolete = old - add
|
211 |
|
if obsolete:
|
212 |
|
queries = (query.Q(parent_id=a, child_id=b, direct=False) for a, b in obsolete)
|
213 |
|
self.model.objects.filter(functools.reduce(query.Q.__or__, queries)).delete()
|
|
180 |
with atomic(savepoint=False):
|
|
181 |
# existing direct paths
|
|
182 |
direct = set(self.filter(direct=True).values_list('parent_id', 'child_id'))
|
|
183 |
old_indirects = set(self.filter(direct=False).values_list('parent_id', 'child_id'))
|
|
184 |
indirects = set(direct)
|
|
185 |
|
|
186 |
while True:
|
|
187 |
changed = False
|
|
188 |
for (i, j) in list(indirects):
|
|
189 |
for (k, l) in direct:
|
|
190 |
if j == k and i != l and (i, l) not in indirects:
|
|
191 |
indirects.add((i, l))
|
|
192 |
changed = True
|
|
193 |
if not changed:
|
|
194 |
break
|
|
195 |
|
|
196 |
with connection.cursor() as cur:
|
|
197 |
# Delete old ones
|
|
198 |
obsolete = old_indirects - indirects - direct
|
|
199 |
if obsolete:
|
|
200 |
obsolete_values = ', '.join('(%s, %s)' % (a, b) for a, b in obsolete)
|
|
201 |
sql = '''DELETE FROM "%s" AS relation \
|
|
202 |
USING (VALUES %s) AS dead(parent_id, child_id) \
|
|
203 |
WHERE relation.direct = 'false' AND relation.parent_id = dead.parent_id \
|
|
204 |
AND relation.child_id = dead.child_id''' % (
|
|
205 |
self.model._meta.db_table,
|
|
206 |
obsolete_values,
|
|
207 |
)
|
|
208 |
cur.execute(sql)
|
|
209 |
# Create new indirect relations
|
|
210 |
new = indirects - old_indirects - direct
|
|
211 |
if new:
|
|
212 |
new_values = ', '.join(
|
|
213 |
("(%s, %s, 'false')" % (parent_id, child_id) for parent_id, child_id in new)
|
|
214 |
)
|
|
215 |
sql = '''INSERT INTO "%s" (parent_id, child_id, direct) VALUES %s''' % (
|
|
216 |
self.model._meta.db_table,
|
|
217 |
new_values,
|
|
218 |
)
|
|
219 |
cur.execute(sql)
|
214 |
220 |
|
215 |
221 |
|
216 |
222 |
@contextlib.contextmanager
|