natural_key: fix get_by_natural_key_json for objects with partial unique indexes (fixes #23857)

Unicity on NULL column must be explicitely stated in the list of natural
keys.
This commit is contained in:
Benjamin Dauvergne 2018-05-17 15:22:27 +02:00
parent 07865e418e
commit 818737a7c6
3 changed files with 61 additions and 27 deletions

View File

@ -115,7 +115,10 @@ class Permission(PermissionAbstractBase):
object_id_field='admin_scope_id')
Permission._meta.natural_key = ['operation', 'ou', 'target']
Permission._meta.natural_key = [
['operation', 'ou', 'target'],
['operation', 'ou__isnull', 'target'],
]
class Role(RoleAbstractBase):
@ -247,7 +250,13 @@ class Role(RoleAbstractBase):
Role._meta.natural_key = [
['uuid'], ['slug', 'ou'], ['name', 'ou'], ['slug', 'service'], ['name', 'service']
['uuid'],
['slug', 'ou__isnull', 'service__isnull'],
['name', 'ou__isnull', 'service__isnull'],
['slug', 'ou', 'service'],
['name', 'ou', 'service'],
['slug', 'ou', 'service__isnull'],
['name', 'ou', 'service__isnull'],
]

View File

@ -24,6 +24,8 @@ def natural_key_json(self):
names.add(key)
for name in names:
if name.endswith('__isnull'):
name = name.split('__isnull')[0]
field = self._meta.get_field(name)
if not (field.concrete or isinstance(field, GenericForeignKey)):
raise ValueError('field %s is not concrete' % name)
@ -49,6 +51,9 @@ def get_by_natural_key_json(self, d):
for natural_key in natural_keys:
get_kwargs = {}
for name in natural_key:
isnull = name.endswith('__isnull')
if isnull:
name = name.split('__isnull')[0]
field = model._meta.get_field(name)
if not (field.concrete or isinstance(field, GenericForeignKey)):
raise ValueError('field %s is not concrete' % name)
@ -57,34 +62,44 @@ def get_by_natural_key_json(self, d):
try:
value = d[name]
except KeyError:
break
if not isnull:
break
value = None
if isinstance(field, GenericForeignKey):
try:
ct_nk = d[field.ct_field]
except KeyError:
if isnull and value:
break
try:
ct = ContentType.objects.get_by_natural_key_json(ct_nk)
except ContentType.DoesNotExist:
break
related_model = ct.model_class()
try:
value = related_model._default_manager.get_by_natural_key_json(value)
except related_model.DoesNotExist:
break
get_kwargs[field.ct_field] = ct
name = field.fk_field
value = value.pk
elif not isnull:
if not value:
break
try:
ct_nk = d[field.ct_field]
except KeyError:
break
try:
ct = ContentType.objects.get_by_natural_key_json(ct_nk)
except ContentType.DoesNotExist:
break
related_model = ct.model_class()
try:
value = related_model._default_manager.get_by_natural_key_json(value)
except related_model.DoesNotExist:
break
name = field.fk_field
value = value.pk
elif field.is_relation:
if value is None:
name = '%s__isnull' % name
value = True
else:
if isnull and value:
break
elif not isnull:
if not value:
break
try:
value = field.related_model._default_manager.get_by_natural_key_json(value)
except field.related_model.DoesNotExist:
break
get_kwargs[name] = value
if isnull:
get_kwargs[name + '__isnull'] = True
else:
get_kwargs[name] = value
else:
try:
return self.get(**get_kwargs)

View File

@ -1,3 +1,5 @@
import pytest
from django.contrib.contenttypes.models import ContentType
from authentic2.a2_rbac.models import Role, OrganizationalUnit as OU, Permission
@ -29,12 +31,20 @@ def test_natural_key_json(db, ou1):
}
assert role == Role.objects.get_by_natural_key_json(nk)
assert role == Role.objects.get_by_natural_key_json({'uuid': role.uuid})
assert role == Role.objects.get_by_natural_key_json({'slug': role.slug, 'ou': ou_nk})
assert role == Role.objects.get_by_natural_key_json({'name': role.name, 'ou': ou_nk})
if service_nk:
with pytest.raises(Role.DoesNotExist):
Role.objects.get_by_natural_key_json({'slug': role.slug, 'ou': ou_nk})
else:
assert Role.objects.get_by_natural_key_json({'slug': role.slug, 'ou': ou_nk}) == role
if service_nk:
with pytest.raises(Role.DoesNotExist):
assert Role.objects.get_by_natural_key_json({'name': role.name, 'ou': ou_nk})
else:
assert Role.objects.get_by_natural_key_json({'name': role.name, 'ou': ou_nk}) == role
assert role == Role.objects.get_by_natural_key_json(
{'slug': role.slug, 'service': service_nk})
{'slug': role.slug, 'ou': ou_nk, 'service': service_nk})
assert role == Role.objects.get_by_natural_key_json(
{'name': role.name, 'service': service_nk})
{'name': role.name, 'ou': ou_nk, 'service': service_nk})
for permission in Permission.objects.all():
ou_nk = permission.ou and permission.ou.natural_key_json()