Projet

Général

Profil

0006-apply-soft-deletion-logic-when-retrieving-roleparent.patch

Paul Marillonnet, 12 octobre 2021 10:52

Télécharger (13,1 ko)

Voir les différences:

Subject: [PATCH 6/6] apply soft-deletion logic when retrieving roleparenting
 objects (#57500)

 src/authentic2/custom_user/models.py          | 10 ++-
 .../management/commands/check-and-repair.py   |  7 +-
 src/authentic2/manager/resources.py           |  2 +-
 src/authentic2/manager/role_views.py          | 12 +++-
 src/authentic2/manager/tables.py              |  2 +-
 src/authentic2/manager/user_views.py          |  2 +-
 src/django_rbac/managers.py                   | 23 +++++-
 src/django_rbac/models.py                     |  7 +-
 tests_rbac/test_rbac.py                       | 71 ++++++++++++++++++-
 9 files changed, 120 insertions(+), 16 deletions(-)
src/authentic2/custom_user/models.py
22 22
from django.core.exceptions import MultipleObjectsReturned, ValidationError
23 23
from django.core.mail import send_mail
24 24
from django.db import models, transaction
25
from django.db.models import F
26
from django.db.models.query import Q
25 27
from django.utils import timezone
26 28
from django.utils.translation import ugettext_lazy as _
27 29

  
......
219 221

  
220 222
    def roles_and_parents(self):
221 223
        qs1 = self.roles.all()
222
        qs2 = qs1.model.objects.filter(child_relation__child__in=qs1)
224
        qs2 = qs1.model.objects.filter(
225
            Q(child_relation__deleted__isnull=True)
226
            | Q(child_relation__deleted__lte=F('child_relation__created')),
227
            child_relation__child__in=qs1,
228
        )
223 229
        qs = (qs1 | qs2).order_by('name').distinct()
224 230
        RoleParenting = get_role_parenting_model()
225
        rp_qs = RoleParenting.objects.filter(child__in=qs1)
231
        rp_qs = RoleParenting.objects.filter_out_soft_deleted().filter(child__in=qs1)
226 232
        qs = qs.prefetch_related(models.Prefetch('child_relation', queryset=rp_qs), 'child_relation__parent')
227 233
        qs = qs.prefetch_related(
228 234
            models.Prefetch('members', queryset=self.__class__.objects.filter(pk=self.pk), to_attr='member')
src/authentic2/management/commands/check-and-repair.py
23 23
from django.core.exceptions import ObjectDoesNotExist
24 24
from django.core.management.base import BaseCommand
25 25
from django.db import connection
26
from django.db.models import Count, Q
26
from django.db.models import Count, F, Q
27 27
from django.db.models.functions import Lower
28 28
from django.db.transaction import atomic
29 29
from django.utils.timezone import localtime
......
388 388
                    direct_members = manager_role.members.all()
389 389
                    direct_members_count = direct_members.count()
390 390
                    direct_children = Role.objects.filter(
391
                        parent_relation__parent=manager_role, parent_relation__direct=True
391
                        Q(parent_relation__deleted__isnull=True)
392
                        | Q(parent_relation__deleted__lte=F('parent_relation__created')),
393
                        parent_relation__parent=manager_role,
394
                        parent_relation__direct=True,
392 395
                    )
393 396
                    direct_children_count = direct_children.count()
394 397
                    show = members_count or self.verbosity > 1
src/authentic2/manager/resources.py
39 39
        result = set()
40 40
        for role in instance.roles.all():
41 41
            result.add(role)
42
            for pr in role.parent_relation.all():
42
            for pr in role.parent_relation.filter_out_soft_deleted():
43 43
                result.add(pr.parent)
44 44
        return ', '.join(str(x) for x in result)
45 45

  
src/authentic2/manager/role_views.py
396 396
            )
397 397
        )
398 398
        RoleParenting = get_role_parenting_model()
399
        rp_qs = RoleParenting.objects.filter(parent__in=children).annotate(name=F('parent__name'))
399
        rp_qs = (
400
            RoleParenting.objects.filter_out_soft_deleted()
401
            .filter(parent__in=children)
402
            .annotate(name=F('parent__name'))
403
        )
400 404
        qs = qs.prefetch_related(Prefetch('parent_relation', queryset=rp_qs, to_attr='via'))
401 405
        return qs
402 406

  
......
461 465
            )
462 466
        )
463 467
        RoleParenting = get_role_parenting_model()
464
        rp_qs = RoleParenting.objects.filter(child__in=parents).annotate(name=F('child__name'))
468
        rp_qs = (
469
            RoleParenting.objects.filter_out_soft_deleted()
470
            .filter(child__in=parents)
471
            .annotate(name=F('child__name'))
472
        )
465 473
        qs = qs.prefetch_related(Prefetch('child_relation', queryset=rp_qs, to_attr='via'))
466 474
        return qs
467 475

  
src/authentic2/manager/tables.py
182 182
    )
183 183
    ou = tables.Column()
184 184
    via = tables.TemplateColumn(
185
        '{% if not record.member %}{% for rel in record.child_relation.all %}{{ rel.child }} {% if not'
185
        '{% if not record.member %}{% for rel in record.child_relation.filter_out_soft_deleted %}{{ rel.child }} {% if not'
186 186
        ' forloop.last %}, {% endif %}{% endfor %}{% endif %}',
187 187
        verbose_name=_('Inherited from'),
188 188
        orderable=False,
src/authentic2/manager/user_views.py
643 643
            User = get_user_model()
644 644
            Role = get_role_model()
645 645
            RoleParenting = get_role_parenting_model()
646
            rp_qs = RoleParenting.objects.filter(child__in=roles)
646
            rp_qs = RoleParenting.objects.filter_out_soft_deleted().filter(child__in=roles)
647 647
            qs = Role.objects.all()
648 648
            qs = qs.prefetch_related(models.Prefetch('child_relation', queryset=rp_qs, to_attr='via'))
649 649
            qs = qs.prefetch_related(
src/django_rbac/managers.py
107 107
        return self.filter(members=user).parents().distinct()
108 108

  
109 109
    def parents(self, include_self=True, annotate=False):
110
        qs = self.model.objects.filter(child_relation__child__in=self)
110
        qs = self.model.objects.filter(
111
            Q(child_relation__deleted__isnull=True)
112
            | Q(child_relation__deleted__lte=F('child_relation__created')),
113
            child_relation__child__in=self,
114
        )
111 115
        if include_self:
112 116
            qs = self | qs
113 117
        qs = qs.distinct()
......
116 120
        return qs
117 121

  
118 122
    def children(self, include_self=True, annotate=False):
119
        qs = self.model.objects.filter(parent_relation__parent__in=self)
123
        qs = self.model.objects.filter(
124
            Q(parent_relation__deleted__isnull=True)
125
            | Q(parent_relation__deleted__lte=F('child_relation__created')),
126
            parent_relation__parent__in=self,
127
        )
120 128
        if include_self:
121 129
            qs = self | qs
122 130
        qs = qs.distinct()
......
128 136
        User = get_user_model()
129 137
        prefetch = Prefetch('roles', queryset=self, to_attr='direct')
130 138
        return (
131
            User.objects.filter(Q(roles__in=self) | Q(roles__parent_relation__parent__in=self))
139
            User.objects.filter(
140
                Q(roles__in=self)
141
                | (
142
                    Q(roles__parent_relation__parent__in=self)
143
                    & (
144
                        Q(roles__parent_relation__deleted__isnull=True)
145
                        | Q(roles__parent_relation__deleted__lte=F('roles__parent_relation__created'))
146
                    )
147
                )
148
            )
132 149
            .distinct()
133 150
            .prefetch_related(prefetch)
134 151
        )
src/django_rbac/models.py
222 222
    def all_members(self):
223 223
        User = get_user_model()
224 224
        prefetch = Prefetch('roles', queryset=self.__class__.objects.filter(pk=self.pk), to_attr='direct')
225

  
226
        soft_deleted_rps = utils.get_role_parenting_model().objects.filter_out_soft_created()
225 227
        return (
226
            User.objects.filter(Q(roles=self) | Q(roles__parent_relation__parent=self))
228
            User.objects.filter(
229
                Q(roles=self)
230
                | Q(roles__parent_relation__parent=self) & ~Q(roles__parent_relation__in=soft_deleted_rps)
231
            )
227 232
            .distinct()
228 233
            .prefetch_related(prefetch)
229 234
        )
tests_rbac/test_rbac.py
66 66
        assert role.parents().count() == i + 1
67 67
        assert role.children(False).count() == 6 - i - 1
68 68
        assert role.parents(False).count() == i
69
    RoleParenting.objects.filter(parent=roles[2], child=roles[3], direct=True).delete()
70
    assert RoleParenting.objects.filter(direct=True).count() == 4
71
    assert RoleParenting.objects.filter(direct=False).count() == 2
69
    for rp in RoleParenting.objects.filter(parent=roles[2], child=roles[3], direct=True):
70
        rp.soft_delete()
71
    assert (
72
        RoleParenting.objects.filter_out_soft_deleted()
73
        .filter(
74
            direct=True,
75
        )
76
        .count()
77
        == 4
78
    )
79
    assert (
80
        RoleParenting.objects.filter_out_soft_deleted()
81
        .filter(
82
            direct=False,
83
        )
84
        .count()
85
        == 2
86
    )
72 87
    # test that it works with cycles
73 88
    RoleParenting.objects.create(parent=roles[2], child=roles[3])
74 89
    RoleParenting.objects.create(parent=roles[5], child=roles[0])
......
77 92
        assert role.parents().count() == 6
78 93

  
79 94

  
95
def test_role_parenting_soft_delete_children(db):
96
    OrganizationalUnit = utils.get_ou_model()
97
    Role = utils.get_role_model()
98
    RoleParenting = utils.get_role_parenting_model()
99

  
100
    ou = OrganizationalUnit.objects.create(name='ou')
101
    roles = []
102
    for i in range(10):
103
        roles.append(Role.objects.create(name='r%d' % i, ou=ou))
104
    assert not len(RoleParenting.objects.all())
105

  
106
    rps = []
107
    for i in range(5):
108
        rps.append(RoleParenting.objects.create(parent=roles[9 - i], child=roles[i]))
109
    assert len(RoleParenting.objects.all()) == 5
110
    for i in range(5):
111
        roles[9 - i].remove_child(roles[i])
112
        assert len(RoleParenting.objects.all()) == 5
113
        assert len(RoleParenting.objects.filter_out_soft_deleted()) == 4 - i
114
    for i in range(5):
115
        roles[9 - i].add_child(roles[i])
116
        assert len(RoleParenting.objects.all()) == 5
117
        assert len(RoleParenting.objects.filter_out_soft_deleted()) == i + 1
118

  
119

  
120
def test_role_parenting_soft_delete_parents(db):
121
    OrganizationalUnit = utils.get_ou_model()
122
    Role = utils.get_role_model()
123
    RoleParenting = utils.get_role_parenting_model()
124

  
125
    ou = OrganizationalUnit.objects.create(name='ou')
126
    roles = []
127
    for i in range(10):
128
        roles.append(Role.objects.create(name='r%d' % i, ou=ou))
129
    assert not len(RoleParenting.objects.all())
130

  
131
    rps = []
132
    for i in range(5):
133
        rps.append(RoleParenting.objects.create(child=roles[9 - i], parent=roles[i]))
134
    assert len(RoleParenting.objects.all()) == 5
135
    for i in range(5):
136
        roles[9 - i].remove_parent(roles[i])
137
        assert len(RoleParenting.objects.all()) == 5
138
        assert len(RoleParenting.objects.filter_out_soft_deleted()) == 4 - i
139
    for i in range(5):
140
        roles[9 - i].add_parent(roles[i])
141
        assert len(RoleParenting.objects.all()) == 5
142
        assert len(RoleParenting.objects.filter_out_soft_deleted()) == i + 1
143

  
144

  
80 145
SIZE = 1000
81 146
SPAN = 50
82 147

  
83
-