stages/prompt: remove PolicyBindingModel from PromptStage *breaking*
This resolves issues caused by the multiple primary keys, but also requires re-creation of the model.
This commit is contained in:
parent
8db60b3e83
commit
1776b72356
|
@ -16,8 +16,6 @@ def create_default_password_change(apps: Apps, schema_editor: BaseDatabaseSchema
|
||||||
Flow = apps.get_model("passbook_flows", "Flow")
|
Flow = apps.get_model("passbook_flows", "Flow")
|
||||||
FlowStageBinding = apps.get_model("passbook_flows", "FlowStageBinding")
|
FlowStageBinding = apps.get_model("passbook_flows", "FlowStageBinding")
|
||||||
|
|
||||||
PolicyBinding = apps.get_model("passbook_policies", "PolicyBinding")
|
|
||||||
|
|
||||||
ExpressionPolicy = apps.get_model(
|
ExpressionPolicy = apps.get_model(
|
||||||
"passbook_policies_expression", "ExpressionPolicy"
|
"passbook_policies_expression", "ExpressionPolicy"
|
||||||
)
|
)
|
||||||
|
@ -58,17 +56,17 @@ def create_default_password_change(apps: Apps, schema_editor: BaseDatabaseSchema
|
||||||
"order": 1,
|
"order": 1,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
prompt_stage.fields.add(password_prompt)
|
|
||||||
prompt_stage.fields.add(password_rep_prompt)
|
|
||||||
|
|
||||||
# Policy to only trigger prompt when no username is given
|
# Policy to only trigger prompt when no username is given
|
||||||
prompt_policy, _ = ExpressionPolicy.objects.using(db_alias).update_or_create(
|
prompt_policy, _ = ExpressionPolicy.objects.using(db_alias).update_or_create(
|
||||||
name="default-password-change-password-equal",
|
name="default-password-change-password-equal",
|
||||||
defaults={"expression": PROMPT_POLICY_EXPRESSION},
|
defaults={"expression": PROMPT_POLICY_EXPRESSION},
|
||||||
)
|
)
|
||||||
PolicyBinding.objects.using(db_alias).update_or_create(
|
|
||||||
policy=prompt_policy, target=prompt_stage, defaults={"order": 0}
|
prompt_stage.fields.add(password_prompt)
|
||||||
)
|
prompt_stage.fields.add(password_rep_prompt)
|
||||||
|
prompt_stage.validation_policies.add(prompt_policy)
|
||||||
|
prompt_stage.save()
|
||||||
|
|
||||||
user_write, _ = UserWriteStage.objects.using(db_alias).update_or_create(
|
user_write, _ = UserWriteStage.objects.using(db_alias).update_or_create(
|
||||||
name="default-password-change-write"
|
name="default-password-change-write"
|
||||||
|
@ -103,9 +101,8 @@ class Migration(migrations.Migration):
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("passbook_flows", "0006_auto_20200629_0857"),
|
("passbook_flows", "0006_auto_20200629_0857"),
|
||||||
("passbook_policies_expression", "0001_initial"),
|
("passbook_policies_expression", "0001_initial"),
|
||||||
("passbook_policies", "0001_initial"),
|
|
||||||
("passbook_stages_password", "0001_initial"),
|
("passbook_stages_password", "0001_initial"),
|
||||||
("passbook_stages_prompt", "0004_auto_20200618_1735"),
|
("passbook_stages_prompt", "0001_initial"),
|
||||||
("passbook_stages_user_write", "0001_initial"),
|
("passbook_stages_user_write", "0001_initial"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ class PromptStageSerializer(ModelSerializer):
|
||||||
"pk",
|
"pk",
|
||||||
"name",
|
"name",
|
||||||
"fields",
|
"fields",
|
||||||
|
"validation_policies",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,17 @@
|
||||||
"""Prompt forms"""
|
"""Prompt forms"""
|
||||||
from typing import Callable
|
from email.policy import Policy
|
||||||
|
from typing import Callable, Iterator, List
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib.admin.widgets import FilteredSelectMultiple
|
from django.contrib.admin.widgets import FilteredSelectMultiple
|
||||||
|
from django.http import HttpRequest
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from guardian.shortcuts import get_anonymous_user
|
from guardian.shortcuts import get_anonymous_user
|
||||||
|
|
||||||
from passbook.core.models import User
|
from passbook.core.models import User
|
||||||
from passbook.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan
|
from passbook.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan
|
||||||
from passbook.policies.engine import PolicyEngine
|
from passbook.policies.engine import PolicyEngine
|
||||||
|
from passbook.policies.models import PolicyBinding, PolicyBindingModel
|
||||||
from passbook.stages.prompt.models import FieldTypes, Prompt, PromptStage
|
from passbook.stages.prompt.models import FieldTypes, Prompt, PromptStage
|
||||||
|
|
||||||
|
|
||||||
|
@ -18,7 +21,7 @@ class PromptStageForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
||||||
model = PromptStage
|
model = PromptStage
|
||||||
fields = ["name", "fields"]
|
fields = ["name", "fields", "validation_policies"]
|
||||||
widgets = {
|
widgets = {
|
||||||
"name": forms.TextInput(),
|
"name": forms.TextInput(),
|
||||||
"fields": FilteredSelectMultiple(_("prompts"), False),
|
"fields": FilteredSelectMultiple(_("prompts"), False),
|
||||||
|
@ -45,6 +48,23 @@ class PromptAdminForm(forms.ModelForm):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ListPolicyEngine(PolicyEngine):
|
||||||
|
"""Slightly modified policy engine, which uses a list instead of a PolicyBindingModel"""
|
||||||
|
|
||||||
|
__list: List[Policy]
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, policies: List[Policy], user: User, request: HttpRequest = None
|
||||||
|
) -> None:
|
||||||
|
super().__init__(PolicyBindingModel(), user, request)
|
||||||
|
self.__list = policies
|
||||||
|
self.use_cache = False
|
||||||
|
|
||||||
|
def _iter_bindings(self) -> Iterator[PolicyBinding]:
|
||||||
|
for policy in self.__list:
|
||||||
|
yield PolicyBinding(policy=policy,)
|
||||||
|
|
||||||
|
|
||||||
class PromptForm(forms.Form):
|
class PromptForm(forms.Form):
|
||||||
"""Dynamically created form based on PromptStage"""
|
"""Dynamically created form based on PromptStage"""
|
||||||
|
|
||||||
|
@ -73,7 +93,7 @@ class PromptForm(forms.Form):
|
||||||
def clean(self):
|
def clean(self):
|
||||||
cleaned_data = super().clean()
|
cleaned_data = super().clean()
|
||||||
user = self.plan.context.get(PLAN_CONTEXT_PENDING_USER, get_anonymous_user())
|
user = self.plan.context.get(PLAN_CONTEXT_PENDING_USER, get_anonymous_user())
|
||||||
engine = PolicyEngine(self.stage, user)
|
engine = ListPolicyEngine(self.stage.validation_policies.all(), user)
|
||||||
engine.request.context = cleaned_data
|
engine.request.context = cleaned_data
|
||||||
engine.build()
|
engine.build()
|
||||||
result = engine.result
|
result = engine.result
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Generated by Django 3.0.6 on 2020-05-19 22:08
|
# Generated by Django 3.1.1 on 2020-09-09 08:40
|
||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
@ -11,8 +11,8 @@ class Migration(migrations.Migration):
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("passbook_policies", "0001_initial"),
|
("passbook_flows", "0007_auto_20200703_2059"),
|
||||||
("passbook_flows", "0001_initial"),
|
("passbook_policies", "0003_auto_20200908_1542"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -39,17 +39,30 @@ class Migration(migrations.Migration):
|
||||||
"type",
|
"type",
|
||||||
models.CharField(
|
models.CharField(
|
||||||
choices=[
|
choices=[
|
||||||
("text", "Text"),
|
("text", "Text: Simple Text input"),
|
||||||
("e-mail", "Email"),
|
(
|
||||||
|
"username",
|
||||||
|
"Username: Same as Text input, but checks for and prevents duplicate usernames.",
|
||||||
|
),
|
||||||
|
("email", "Email: Text field with Email type."),
|
||||||
("password", "Password"),
|
("password", "Password"),
|
||||||
("number", "Number"),
|
("number", "Number"),
|
||||||
("hidden", "Hidden"),
|
("checkbox", "Checkbox"),
|
||||||
|
("data", "Date"),
|
||||||
|
("data-time", "Date Time"),
|
||||||
|
("separator", "Separator: Static Separator Line"),
|
||||||
|
(
|
||||||
|
"hidden",
|
||||||
|
"Hidden: Hidden field, can be used to insert data into form.",
|
||||||
|
),
|
||||||
|
("static", "Static: Static value, displayed as-is."),
|
||||||
],
|
],
|
||||||
max_length=100,
|
max_length=100,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
("required", models.BooleanField(default=True)),
|
("required", models.BooleanField(default=True)),
|
||||||
("placeholder", models.TextField()),
|
("placeholder", models.TextField(blank=True)),
|
||||||
|
("order", models.IntegerField(default=0)),
|
||||||
],
|
],
|
||||||
options={"verbose_name": "Prompt", "verbose_name_plural": "Prompts",},
|
options={"verbose_name": "Prompt", "verbose_name_plural": "Prompts",},
|
||||||
),
|
),
|
||||||
|
@ -58,30 +71,25 @@ class Migration(migrations.Migration):
|
||||||
fields=[
|
fields=[
|
||||||
(
|
(
|
||||||
"stage_ptr",
|
"stage_ptr",
|
||||||
models.OneToOneField(
|
|
||||||
auto_created=True,
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
parent_link=True,
|
|
||||||
to="passbook_flows.Stage",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"policybindingmodel_ptr",
|
|
||||||
models.OneToOneField(
|
models.OneToOneField(
|
||||||
auto_created=True,
|
auto_created=True,
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
parent_link=True,
|
parent_link=True,
|
||||||
primary_key=True,
|
primary_key=True,
|
||||||
serialize=False,
|
serialize=False,
|
||||||
to="passbook_policies.PolicyBindingModel",
|
to="passbook_flows.stage",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
("fields", models.ManyToManyField(to="passbook_stages_prompt.Prompt")),
|
("fields", models.ManyToManyField(to="passbook_stages_prompt.Prompt")),
|
||||||
|
(
|
||||||
|
"validation_policies",
|
||||||
|
models.ManyToManyField(blank=True, to="passbook_policies.Policy"),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
"verbose_name": "Prompt Stage",
|
"verbose_name": "Prompt Stage",
|
||||||
"verbose_name_plural": "Prompt Stages",
|
"verbose_name_plural": "Prompt Stages",
|
||||||
},
|
},
|
||||||
bases=("passbook_policies.policybindingmodel", "passbook_flows.stage"),
|
bases=("passbook_flows.stage",),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
# Generated by Django 3.0.6 on 2020-05-28 20:59
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("passbook_stages_prompt", "0001_initial"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="prompt", name="order", field=models.IntegerField(default=0),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="prompt",
|
|
||||||
name="type",
|
|
||||||
field=models.CharField(
|
|
||||||
choices=[
|
|
||||||
("text", "Text"),
|
|
||||||
("e-mail", "Email"),
|
|
||||||
("password", "Password"),
|
|
||||||
("number", "Number"),
|
|
||||||
("checkbox", "Checkbox"),
|
|
||||||
("data", "Date"),
|
|
||||||
("data-time", "Date Time"),
|
|
||||||
("separator", "Separator"),
|
|
||||||
("hidden", "Hidden"),
|
|
||||||
("static", "Static"),
|
|
||||||
],
|
|
||||||
max_length=100,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,33 +0,0 @@
|
||||||
# Generated by Django 3.0.7 on 2020-06-15 16:41
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("passbook_stages_prompt", "0002_auto_20200528_2059"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="prompt",
|
|
||||||
name="type",
|
|
||||||
field=models.CharField(
|
|
||||||
choices=[
|
|
||||||
("text", "Text"),
|
|
||||||
("username", "Username"),
|
|
||||||
("e-mail", "Email"),
|
|
||||||
("password", "Password"),
|
|
||||||
("number", "Number"),
|
|
||||||
("checkbox", "Checkbox"),
|
|
||||||
("data", "Date"),
|
|
||||||
("data-time", "Date Time"),
|
|
||||||
("separator", "Separator"),
|
|
||||||
("hidden", "Hidden"),
|
|
||||||
("static", "Static"),
|
|
||||||
],
|
|
||||||
max_length=100,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,33 +0,0 @@
|
||||||
# Generated by Django 3.0.7 on 2020-06-18 17:35
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("passbook_stages_prompt", "0003_auto_20200615_1641"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="prompt",
|
|
||||||
name="type",
|
|
||||||
field=models.CharField(
|
|
||||||
choices=[
|
|
||||||
("text", "Text"),
|
|
||||||
("username", "Username"),
|
|
||||||
("email", "Email"),
|
|
||||||
("password", "Password"),
|
|
||||||
("number", "Number"),
|
|
||||||
("checkbox", "Checkbox"),
|
|
||||||
("data", "Date"),
|
|
||||||
("data-time", "Date Time"),
|
|
||||||
("separator", "Separator"),
|
|
||||||
("hidden", "Hidden"),
|
|
||||||
("static", "Static"),
|
|
||||||
],
|
|
||||||
max_length=100,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,39 +0,0 @@
|
||||||
# Generated by Django 3.0.8 on 2020-07-09 16:08
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("passbook_stages_prompt", "0004_auto_20200618_1735"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="prompt",
|
|
||||||
name="type",
|
|
||||||
field=models.CharField(
|
|
||||||
choices=[
|
|
||||||
("text", "Text: Simple Text input"),
|
|
||||||
(
|
|
||||||
"username",
|
|
||||||
"Username: Same as Text input, but checks for and prevents duplicate usernames.",
|
|
||||||
),
|
|
||||||
("email", "Email: Text field with Email type."),
|
|
||||||
("password", "Password"),
|
|
||||||
("number", "Number"),
|
|
||||||
("checkbox", "Checkbox"),
|
|
||||||
("data", "Date"),
|
|
||||||
("data-time", "Date Time"),
|
|
||||||
("separator", "Separator: Static Separator Line"),
|
|
||||||
(
|
|
||||||
"hidden",
|
|
||||||
"Hidden: Hidden field, can be used to insert data into form.",
|
|
||||||
),
|
|
||||||
("static", "Static: Static value, displayed as-is."),
|
|
||||||
],
|
|
||||||
max_length=100,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,16 +0,0 @@
|
||||||
# Generated by Django 3.1 on 2020-08-23 22:46
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("passbook_stages_prompt", "0005_auto_20200709_1608"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="prompt", name="placeholder", field=models.TextField(blank=True),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -11,7 +11,7 @@ from rest_framework.serializers import BaseSerializer
|
||||||
|
|
||||||
from passbook.flows.models import Stage
|
from passbook.flows.models import Stage
|
||||||
from passbook.lib.models import SerializerModel
|
from passbook.lib.models import SerializerModel
|
||||||
from passbook.policies.models import PolicyBindingModel
|
from passbook.policies.models import Policy
|
||||||
from passbook.stages.prompt.widgets import HorizontalRuleWidget, StaticTextWidget
|
from passbook.stages.prompt.widgets import HorizontalRuleWidget, StaticTextWidget
|
||||||
|
|
||||||
|
|
||||||
|
@ -123,11 +123,13 @@ class Prompt(SerializerModel):
|
||||||
verbose_name_plural = _("Prompts")
|
verbose_name_plural = _("Prompts")
|
||||||
|
|
||||||
|
|
||||||
class PromptStage(PolicyBindingModel, Stage):
|
class PromptStage(Stage):
|
||||||
"""Define arbitrary prompts for the user."""
|
"""Define arbitrary prompts for the user."""
|
||||||
|
|
||||||
fields = models.ManyToManyField(Prompt)
|
fields = models.ManyToManyField(Prompt)
|
||||||
|
|
||||||
|
validation_policies = models.ManyToManyField(Policy, blank=True)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def serializer(self) -> BaseSerializer:
|
def serializer(self) -> BaseSerializer:
|
||||||
from passbook.stages.prompt.api import PromptStageSerializer
|
from passbook.stages.prompt.api import PromptStageSerializer
|
||||||
|
|
|
@ -11,7 +11,6 @@ from passbook.flows.models import Flow, FlowDesignation, FlowStageBinding
|
||||||
from passbook.flows.planner import FlowPlan
|
from passbook.flows.planner import FlowPlan
|
||||||
from passbook.flows.views import SESSION_KEY_PLAN
|
from passbook.flows.views import SESSION_KEY_PLAN
|
||||||
from passbook.policies.expression.models import ExpressionPolicy
|
from passbook.policies.expression.models import ExpressionPolicy
|
||||||
from passbook.policies.models import PolicyBinding
|
|
||||||
from passbook.stages.prompt.forms import PromptForm
|
from passbook.stages.prompt.forms import PromptForm
|
||||||
from passbook.stages.prompt.models import FieldTypes, Prompt, PromptStage
|
from passbook.stages.prompt.models import FieldTypes, Prompt, PromptStage
|
||||||
from passbook.stages.prompt.stage import PLAN_CONTEXT_PROMPT
|
from passbook.stages.prompt.stage import PLAN_CONTEXT_PROMPT
|
||||||
|
@ -124,7 +123,8 @@ class TestPromptStage(TestCase):
|
||||||
expr_policy = ExpressionPolicy.objects.create(
|
expr_policy = ExpressionPolicy.objects.create(
|
||||||
name="validate-form", expression=expr
|
name="validate-form", expression=expr
|
||||||
)
|
)
|
||||||
PolicyBinding.objects.create(policy=expr_policy, target=self.stage, order=0)
|
self.stage.validation_policies.set([expr_policy])
|
||||||
|
self.stage.save()
|
||||||
form = PromptForm(stage=self.stage, plan=plan, data=self.prompt_data)
|
form = PromptForm(stage=self.stage, plan=plan, data=self.prompt_data)
|
||||||
self.assertEqual(form.is_valid(), True)
|
self.assertEqual(form.is_valid(), True)
|
||||||
return form
|
return form
|
||||||
|
@ -138,7 +138,8 @@ class TestPromptStage(TestCase):
|
||||||
expr_policy = ExpressionPolicy.objects.create(
|
expr_policy = ExpressionPolicy.objects.create(
|
||||||
name="validate-form", expression=expr
|
name="validate-form", expression=expr
|
||||||
)
|
)
|
||||||
PolicyBinding.objects.create(policy=expr_policy, target=self.stage, order=0)
|
self.stage.validation_policies.set([expr_policy])
|
||||||
|
self.stage.save()
|
||||||
form = PromptForm(stage=self.stage, plan=plan, data=self.prompt_data)
|
form = PromptForm(stage=self.stage, plan=plan, data=self.prompt_data)
|
||||||
self.assertEqual(form.is_valid(), False)
|
self.assertEqual(form.is_valid(), False)
|
||||||
return form
|
return form
|
||||||
|
|
Reference in New Issue