Projet

Général

Profil

0001-implement-more-natural-natural-keys-16514.patch

Benjamin Dauvergne, 05 avril 2018 10:12

Télécharger (8,48 ko)

Voir les différences:

Subject: [PATCH] implement more natural natural keys (#16514)

 src/authentic2/a2_rbac/models.py |  9 ++++
 src/authentic2/models.py         |  2 +
 src/authentic2/natural_key.py    | 99 ++++++++++++++++++++++++++++++++++++++++
 src/django_rbac/models.py        |  3 ++
 tests/test_natural_key.py        | 44 ++++++++++++++++++
 5 files changed, 157 insertions(+)
 create mode 100644 src/authentic2/natural_key.py
 create mode 100644 tests/test_natural_key.py
src/authentic2/a2_rbac/models.py
93 93
        return cls.objects.all()
94 94

  
95 95

  
96
OrganizationalUnit._meta.natural_key = [['uuid'], ['slug'], ['name']]
97

  
98

  
96 99
class Permission(PermissionAbstractBase):
97 100
    class Meta:
98 101
        verbose_name = _('permission')
......
103 106
                                   object_id_field='admin_scope_id')
104 107

  
105 108

  
109
Permission._meta.natural_key = ['operation', 'ou', 'target']
110

  
111

  
106 112
class Role(RoleAbstractBase):
107 113
    admin_scope_ct = models.ForeignKey(
108 114
        to='contenttypes.ContentType',
......
208 214
        }
209 215

  
210 216

  
217
Role._meta.natural_key = [['uuid'], ['slug', 'ou'], ['name', 'ou']]
218

  
219

  
211 220
class RoleParenting(RoleParentingAbstractBase):
212 221
    class Meta(RoleParentingAbstractBase.Meta):
213 222
        verbose_name = _('role parenting relation')
src/authentic2/models.py
23 23
from django.contrib.contenttypes.models import ContentType
24 24

  
25 25
from . import managers
26
# install our natural_key implementation
27
from . import natural_key
26 28
from .utils import ServiceAccessDenied
27 29

  
28 30

  
src/authentic2/natural_key.py
1
from django.db import models
2

  
3
from django.contrib.contenttypes.models import ContentType
4
from django.contrib.contenttypes.fields import GenericForeignKey
5

  
6

  
7
def get_natural_keys(model):
8
    if not getattr(model._meta, 'natural_key', None):
9
        raise ValueError('model %s has no natural key defined in its Meta' % model.__name__)
10
    natural_key = model._meta.natural_key
11
    if not hasattr(natural_key, '__iter__'):
12
        raise ValueError('natural_key must be an iterable')
13
    if hasattr(natural_key[0], 'lower'):
14
        natural_key = [natural_key]
15
    return natural_key
16

  
17

  
18
def natural_key_json(self):
19
    natural_keys = get_natural_keys(self.__class__)
20
    d = {}
21
    names = set()
22
    for keys in natural_keys:
23
        for key in keys:
24
            names.add(key)
25

  
26
    for name in names:
27
        field = self._meta.get_field(name)
28
        if not (field.concrete or isinstance(field, GenericForeignKey)):
29
            raise ValueError('field %s is not concrete' % name)
30
        if field.is_relation and not field.many_to_one:
31
            raise ValueError('field %s is a relation but not a ForeignKey' % name)
32
        value = getattr(self, name)
33
        if isinstance(field, GenericForeignKey):
34
            ct_field_value = getattr(self, field.ct_field)
35
            d[field.ct_field] = ct_field_value and ct_field_value.natural_key_json()
36
            d[name] = value and value.natural_key_json()
37
        elif field.is_relation:
38
            d[name] = value and value.natural_key_json()
39
        else:
40
            d[name] = value
41
    return d
42

  
43

  
44
def get_by_natural_key_json(self, d):
45
    model = self.model
46
    natural_keys = get_natural_keys(model)
47
    if not isinstance(d, dict):
48
        raise ValueError('a natural_key must be a dictionnary')
49
    for natural_key in natural_keys:
50
        get_kwargs = {}
51
        for name in natural_key:
52
            field = model._meta.get_field(name)
53
            if not (field.concrete or isinstance(field, GenericForeignKey)):
54
                raise ValueError('field %s is not concrete' % name)
55
            if field.is_relation and not field.many_to_one:
56
                raise ValueError('field %s is a relation but not a ForeignKey' % name)
57
            try:
58
                value = d[name]
59
            except KeyError:
60
                break
61
            if isinstance(field, GenericForeignKey):
62
                try:
63
                    ct_nk = d[field.ct_field]
64
                except KeyError:
65
                    break
66
                try:
67
                    ct = ContentType.objects.get_by_natural_key_json(ct_nk)
68
                except ContentType.DoesNotExist:
69
                    break
70
                related_model = ct.model_class()
71
                try:
72
                    value = related_model._default_manager.get_by_natural_key_json(value)
73
                except related_model.DoesNotExist:
74
                    break
75
                get_kwargs[field.ct_field] = ct
76
                name = field.fk_field
77
                value = value.pk
78
            elif field.is_relation:
79
                if value is None:
80
                    name = '%s__isnull' % name
81
                    value = True
82
                else:
83
                    try:
84
                        value = field.related_model._default_manager.get_by_natural_key_json(value)
85
                    except field.related_model.DoesNotExist:
86
                        break
87
            get_kwargs[name] = value
88
        else:
89
            try:
90
                return self.get(**get_kwargs)
91
            except model.DoesNotExist:
92
                pass
93
    raise model.DoesNotExist
94

  
95

  
96
models.Model.natural_key_json = natural_key_json
97
models.Manager.get_by_natural_key_json = get_by_natural_key_json
98

  
99
ContentType._meta.natural_key = ['app_label', 'model']
src/django_rbac/models.py
117 117
    objects = managers.OperationManager()
118 118

  
119 119

  
120
Operation._meta.natural_key = ['slug']
121

  
122

  
120 123
class PermissionAbstractBase(models.Model):
121 124
    operation = models.ForeignKey(
122 125
        to='Operation',
tests/test_natural_key.py
1
from django.contrib.contenttypes.models import ContentType
2
from authentic2.a2_rbac.models import Role, OrganizationalUnit as OU, Permission
3

  
4

  
5
def test_natural_key_json(db, ou1):
6
    role = Role.objects.create(slug='role1', name='Role1', ou=ou1)
7

  
8
    for ou in OU.objects.all():
9
        nk = ou.natural_key_json()
10
        assert nk == {'uuid': ou.uuid, 'slug': ou.slug, 'name': ou.name}
11

  
12
        assert ou == OU.objects.get_by_natural_key_json(nk)
13

  
14
    for ct in ContentType.objects.all():
15
        nk = ct.natural_key_json()
16
        assert nk == {'app_label': ct.app_label, 'model': ct.model}
17
        assert ct == ContentType.objects.get_by_natural_key_json(nk)
18

  
19
    # test is not useful if there are no FK set
20
    assert Role.objects.filter(ou__isnull=False).exists()
21

  
22
    for role in Role.objects.all():
23
        nk = role.natural_key_json()
24
        ou_nk = role.ou and role.ou.natural_key_json()
25
        assert nk == {'uuid': role.uuid, 'slug': role.slug, 'name': role.name, 'ou': ou_nk}
26
        assert role == Role.objects.get_by_natural_key_json(nk)
27
        assert role == Role.objects.get_by_natural_key_json({'uuid': role.uuid})
28
        assert role == Role.objects.get_by_natural_key_json({'slug': role.slug, 'ou': ou_nk})
29
        assert role == Role.objects.get_by_natural_key_json({'name': role.name, 'ou': ou_nk})
30

  
31
    for permission in Permission.objects.all():
32
        ou_nk = permission.ou and permission.ou.natural_key_json()
33
        target_ct_nk = permission.target_ct.natural_key_json()
34
        target_nk = permission.target.natural_key_json()
35
        op_nk = permission.operation.natural_key_json()
36

  
37
        nk = permission.natural_key_json()
38
        assert nk == {
39
            'operation': op_nk,
40
            'ou': ou_nk,
41
            'target_ct': target_ct_nk,
42
            'target': target_nk,
43
        }
44
        assert permission == Permission.objects.get_by_natural_key_json(nk)
0
-