Projet

Général

Profil

0005-django_rbac-new-update_transitive_closure-algorithm-.patch

Benjamin Dauvergne, 01 février 2022 00:48

Télécharger (5,31 ko)

Voir les différences:

Subject: [PATCH 5/6] django_rbac: new update_transitive_closure algorithm
 (#57500)

 src/django_rbac/managers.py | 78 ++++++++++++++++++++-----------------
 src/django_rbac/models.py   |  4 +-
 2 files changed, 44 insertions(+), 38 deletions(-)
src/django_rbac/managers.py
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
src/django_rbac/models.py
186 186

  
187 187
    def add_child(self, child):
188 188
        RoleParenting = utils.get_role_parenting_model()
189
        RoleParenting.objects.get_or_create(parent=self, child=child)
189
        RoleParenting.objects.get_or_create(parent=self, child=child, direct=True)
190 190

  
191 191
    def remove_child(self, child):
192 192
        RoleParenting = utils.get_role_parenting_model()
......
194 194

  
195 195
    def add_parent(self, parent):
196 196
        RoleParenting = utils.get_role_parenting_model()
197
        RoleParenting.objects.get_or_create(parent=parent, child=self)
197
        RoleParenting.objects.get_or_create(parent=parent, child=self, direct=True)
198 198

  
199 199
    def remove_parent(self, parent):
200 200
        RoleParenting = utils.get_role_parenting_model()
201
-