policies/api: fix target returning pbm_uuid instead of proper primary key of the object
This commit is contained in:
parent
f0b5e8143e
commit
9712be847c
|
@ -33,6 +33,7 @@ class FlowForm(forms.ModelForm):
|
||||||
}
|
}
|
||||||
widgets = {
|
widgets = {
|
||||||
"name": forms.TextInput(),
|
"name": forms.TextInput(),
|
||||||
|
"title": forms.TextInput(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
# Generated by Django 3.1 on 2020-08-30 10:56
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("passbook_flows", "0011_flow_title"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="flow",
|
|
||||||
name="title",
|
|
||||||
field=models.TextField(blank=True, default=""),
|
|
||||||
),
|
|
||||||
]
|
|
26
passbook/flows/migrations/0012_auto_20200908_1542.py
Normal file
26
passbook/flows/migrations/0012_auto_20200908_1542.py
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
# Generated by Django 3.1.1 on 2020-09-08 15:42
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
import passbook.lib.models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("passbook_flows", "0011_flow_title"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="flowstagebinding",
|
||||||
|
name="stage",
|
||||||
|
field=passbook.lib.models.InheritanceForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE, to="passbook_flows.stage"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="stage", name="name", field=models.TextField(unique=True),
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,16 +0,0 @@
|
||||||
# Generated by Django 3.1.1 on 2020-09-05 21:42
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("passbook_flows", "0012_auto_20200830_1056"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="stage", name="name", field=models.TextField(unique=True),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -93,7 +93,7 @@ class Flow(SerializerModel, PolicyBindingModel):
|
||||||
name = models.TextField()
|
name = models.TextField()
|
||||||
slug = models.SlugField(unique=True)
|
slug = models.SlugField(unique=True)
|
||||||
|
|
||||||
title = models.TextField(default="", blank=True)
|
title = models.TextField()
|
||||||
|
|
||||||
designation = models.CharField(max_length=100, choices=FlowDesignation.choices)
|
designation = models.CharField(max_length=100, choices=FlowDesignation.choices)
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,7 @@ class TestFlowTransfer(TransactionTestCase):
|
||||||
slug="test",
|
slug="test",
|
||||||
designation=FlowDesignation.AUTHENTICATION,
|
designation=FlowDesignation.AUTHENTICATION,
|
||||||
name="Welcome to passbook!",
|
name="Welcome to passbook!",
|
||||||
|
title="test",
|
||||||
)
|
)
|
||||||
FlowStageBinding.objects.update_or_create(
|
FlowStageBinding.objects.update_or_create(
|
||||||
target=flow, stage=login_stage, order=0,
|
target=flow, stage=login_stage, order=0,
|
||||||
|
@ -64,6 +65,7 @@ class TestFlowTransfer(TransactionTestCase):
|
||||||
slug="default-source-authentication-test",
|
slug="default-source-authentication-test",
|
||||||
designation=FlowDesignation.AUTHENTICATION,
|
designation=FlowDesignation.AUTHENTICATION,
|
||||||
name="Welcome to passbook!",
|
name="Welcome to passbook!",
|
||||||
|
title="test",
|
||||||
)
|
)
|
||||||
PolicyBinding.objects.create(policy=flow_policy, target=flow, order=0)
|
PolicyBinding.objects.create(policy=flow_policy, target=flow, order=0)
|
||||||
|
|
||||||
|
@ -122,6 +124,7 @@ class TestFlowTransfer(TransactionTestCase):
|
||||||
name="default-enrollment-flow",
|
name="default-enrollment-flow",
|
||||||
slug="default-enrollment-flow-test",
|
slug="default-enrollment-flow-test",
|
||||||
designation=FlowDesignation.ENROLLMENT,
|
designation=FlowDesignation.ENROLLMENT,
|
||||||
|
title="test",
|
||||||
)
|
)
|
||||||
|
|
||||||
FlowStageBinding.objects.create(target=flow, stage=first_stage, order=0)
|
FlowStageBinding.objects.create(target=flow, stage=first_stage, order=0)
|
||||||
|
|
|
@ -47,6 +47,7 @@ class FlowExporter:
|
||||||
pbm_uuids += FlowStageBinding.objects.filter(target=self.flow).values_list(
|
pbm_uuids += FlowStageBinding.objects.filter(target=self.flow).values_list(
|
||||||
"pbm_uuid", flat=True
|
"pbm_uuid", flat=True
|
||||||
)
|
)
|
||||||
|
# Add policy objects first, so they are created first
|
||||||
policies = Policy.objects.filter(bindings__in=pbm_uuids).select_related()
|
policies = Policy.objects.filter(bindings__in=pbm_uuids).select_related()
|
||||||
for policy in policies:
|
for policy in policies:
|
||||||
yield FlowBundleEntry.from_model(policy)
|
yield FlowBundleEntry.from_model(policy)
|
||||||
|
|
36
passbook/lib/api.py
Normal file
36
passbook/lib/api.py
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
"""passbook API Helpers"""
|
||||||
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
from django.db.models.query import QuerySet
|
||||||
|
from model_utils.managers import InheritanceQuerySet
|
||||||
|
from rest_framework.serializers import ModelSerializer, PrimaryKeyRelatedField
|
||||||
|
|
||||||
|
|
||||||
|
class InheritancePrimaryKeyRelatedField(PrimaryKeyRelatedField):
|
||||||
|
"""rest_framework PrimaryKeyRelatedField which resolves
|
||||||
|
model_manager's InheritanceQuerySet"""
|
||||||
|
|
||||||
|
def get_queryset(self) -> QuerySet:
|
||||||
|
queryset = super().get_queryset()
|
||||||
|
if isinstance(queryset, InheritanceQuerySet):
|
||||||
|
return queryset.select_subclasses()
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
def to_internal_value(self, data):
|
||||||
|
if self.pk_field is not None:
|
||||||
|
data = self.pk_field.to_internal_value(data)
|
||||||
|
try:
|
||||||
|
queryset = self.get_queryset()
|
||||||
|
if isinstance(queryset, InheritanceQuerySet):
|
||||||
|
return queryset.get_subclass(pk=data)
|
||||||
|
return queryset.get(pk=data)
|
||||||
|
except ObjectDoesNotExist:
|
||||||
|
self.fail("does_not_exist", pk_value=data)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
self.fail("incorrect_type", data_type=type(data).__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class InheritanceModelSerializer(ModelSerializer):
|
||||||
|
"""rest_framework ModelSerializer which automatically uses InheritancePrimaryKeyRelatedField
|
||||||
|
for every primary key"""
|
||||||
|
|
||||||
|
serializer_related_field = InheritancePrimaryKeyRelatedField
|
|
@ -1,14 +1,52 @@
|
||||||
"""policy API Views"""
|
"""policy API Views"""
|
||||||
from rest_framework.serializers import ModelSerializer, SerializerMethodField
|
from rest_framework.serializers import ModelSerializer, SerializerMethodField
|
||||||
|
from rest_framework.utils.model_meta import get_field_info
|
||||||
from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet
|
from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet
|
||||||
|
|
||||||
|
from passbook.lib.api import InheritancePrimaryKeyRelatedField
|
||||||
from passbook.policies.forms import GENERAL_FIELDS
|
from passbook.policies.forms import GENERAL_FIELDS
|
||||||
from passbook.policies.models import Policy, PolicyBinding
|
from passbook.policies.models import Policy, PolicyBinding, PolicyBindingModel
|
||||||
|
|
||||||
|
|
||||||
class PolicyBindingSerializer(ModelSerializer):
|
class PolicyBindingSerializer(ModelSerializer):
|
||||||
"""PolicyBinding Serializer"""
|
"""PolicyBinding Serializer"""
|
||||||
|
|
||||||
|
# Because we're not interested in the PolicyBindingModel's PK but rather the subclasses PK,
|
||||||
|
# we have to manually declare this field
|
||||||
|
target = InheritancePrimaryKeyRelatedField(
|
||||||
|
queryset=PolicyBindingModel.objects.all().select_subclasses(),
|
||||||
|
source="target.pk",
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
def update(self, instance, validated_data):
|
||||||
|
info = get_field_info(instance)
|
||||||
|
|
||||||
|
# Simply set each attribute on the instance, and then save it.
|
||||||
|
# Note that unlike `.create()` we don't need to treat many-to-many
|
||||||
|
# relationships as being a special case. During updates we already
|
||||||
|
# have an instance pk for the relationships to be associated with.
|
||||||
|
m2m_fields = []
|
||||||
|
for attr, value in validated_data.items():
|
||||||
|
if attr in info.relations and info.relations[attr].to_many:
|
||||||
|
m2m_fields.append((attr, value))
|
||||||
|
else:
|
||||||
|
if attr == "target":
|
||||||
|
instance.target_pk = value["pk"].pbm_uuid
|
||||||
|
else:
|
||||||
|
setattr(instance, attr, value)
|
||||||
|
|
||||||
|
instance.save()
|
||||||
|
|
||||||
|
# Note that many-to-many fields are set after updating instance.
|
||||||
|
# Setting m2m fields triggers signals which could potentially change
|
||||||
|
# updated instance and we do not want it to collide with .update()
|
||||||
|
for attr, value in m2m_fields:
|
||||||
|
field = getattr(instance, attr)
|
||||||
|
field.set(value)
|
||||||
|
|
||||||
|
return instance
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
||||||
model = PolicyBinding
|
model = PolicyBinding
|
||||||
|
|
25
passbook/policies/migrations/0003_auto_20200908_1542.py
Normal file
25
passbook/policies/migrations/0003_auto_20200908_1542.py
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
# Generated by Django 3.1.1 on 2020-09-08 15:42
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
import passbook.lib.models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("passbook_policies", "0002_auto_20200528_1647"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="policybinding",
|
||||||
|
name="target",
|
||||||
|
field=passbook.lib.models.InheritanceForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="+",
|
||||||
|
to="passbook_policies.policybindingmodel",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -6041,6 +6041,7 @@ definitions:
|
||||||
required:
|
required:
|
||||||
- name
|
- name
|
||||||
- slug
|
- slug
|
||||||
|
- title
|
||||||
- designation
|
- designation
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
@ -6063,6 +6064,7 @@ definitions:
|
||||||
title:
|
title:
|
||||||
title: Title
|
title: Title
|
||||||
type: string
|
type: string
|
||||||
|
minLength: 1
|
||||||
designation:
|
designation:
|
||||||
title: Designation
|
title: Designation
|
||||||
type: string
|
type: string
|
||||||
|
|
Reference in a new issue