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:
Jens Langhammer 2020-09-09 17:16:43 +02:00
parent 8db60b3e83
commit 1776b72356
11 changed files with 64 additions and 191 deletions

View file

@ -16,8 +16,6 @@ def create_default_password_change(apps: Apps, schema_editor: BaseDatabaseSchema
Flow = apps.get_model("passbook_flows", "Flow")
FlowStageBinding = apps.get_model("passbook_flows", "FlowStageBinding")
PolicyBinding = apps.get_model("passbook_policies", "PolicyBinding")
ExpressionPolicy = apps.get_model(
"passbook_policies_expression", "ExpressionPolicy"
)
@ -58,17 +56,17 @@ def create_default_password_change(apps: Apps, schema_editor: BaseDatabaseSchema
"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
prompt_policy, _ = ExpressionPolicy.objects.using(db_alias).update_or_create(
name="default-password-change-password-equal",
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(
name="default-password-change-write"
@ -103,9 +101,8 @@ class Migration(migrations.Migration):
dependencies = [
("passbook_flows", "0006_auto_20200629_0857"),
("passbook_policies_expression", "0001_initial"),
("passbook_policies", "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"),
]

View file

@ -18,6 +18,7 @@ class PromptStageSerializer(ModelSerializer):
"pk",
"name",
"fields",
"validation_policies",
]

View file

@ -1,14 +1,17 @@
"""Prompt forms"""
from typing import Callable
from email.policy import Policy
from typing import Callable, Iterator, List
from django import forms
from django.contrib.admin.widgets import FilteredSelectMultiple
from django.http import HttpRequest
from django.utils.translation import gettext_lazy as _
from guardian.shortcuts import get_anonymous_user
from passbook.core.models import User
from passbook.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan
from passbook.policies.engine import PolicyEngine
from passbook.policies.models import PolicyBinding, PolicyBindingModel
from passbook.stages.prompt.models import FieldTypes, Prompt, PromptStage
@ -18,7 +21,7 @@ class PromptStageForm(forms.ModelForm):
class Meta:
model = PromptStage
fields = ["name", "fields"]
fields = ["name", "fields", "validation_policies"]
widgets = {
"name": forms.TextInput(),
"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):
"""Dynamically created form based on PromptStage"""
@ -73,7 +93,7 @@ class PromptForm(forms.Form):
def clean(self):
cleaned_data = super().clean()
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.build()
result = engine.result

View file

@ -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
@ -11,8 +11,8 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
("passbook_policies", "0001_initial"),
("passbook_flows", "0001_initial"),
("passbook_flows", "0007_auto_20200703_2059"),
("passbook_policies", "0003_auto_20200908_1542"),
]
operations = [
@ -39,17 +39,30 @@ class Migration(migrations.Migration):
"type",
models.CharField(
choices=[
("text", "Text"),
("e-mail", "Email"),
("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"),
("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,
),
),
("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",},
),
@ -58,30 +71,25 @@ class Migration(migrations.Migration):
fields=[
(
"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(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="passbook_policies.PolicyBindingModel",
to="passbook_flows.stage",
),
),
("fields", models.ManyToManyField(to="passbook_stages_prompt.Prompt")),
(
"validation_policies",
models.ManyToManyField(blank=True, to="passbook_policies.Policy"),
),
],
options={
"verbose_name": "Prompt Stage",
"verbose_name_plural": "Prompt Stages",
},
bases=("passbook_policies.policybindingmodel", "passbook_flows.stage"),
bases=("passbook_flows.stage",),
),
]

View file

@ -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,
),
),
]

View file

@ -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,
),
),
]

View file

@ -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,
),
),
]

View file

@ -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,
),
),
]

View file

@ -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),
),
]

View file

@ -11,7 +11,7 @@ from rest_framework.serializers import BaseSerializer
from passbook.flows.models import Stage
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
@ -123,11 +123,13 @@ class Prompt(SerializerModel):
verbose_name_plural = _("Prompts")
class PromptStage(PolicyBindingModel, Stage):
class PromptStage(Stage):
"""Define arbitrary prompts for the user."""
fields = models.ManyToManyField(Prompt)
validation_policies = models.ManyToManyField(Policy, blank=True)
@property
def serializer(self) -> BaseSerializer:
from passbook.stages.prompt.api import PromptStageSerializer

View file

@ -11,7 +11,6 @@ from passbook.flows.models import Flow, FlowDesignation, FlowStageBinding
from passbook.flows.planner import FlowPlan
from passbook.flows.views import SESSION_KEY_PLAN
from passbook.policies.expression.models import ExpressionPolicy
from passbook.policies.models import PolicyBinding
from passbook.stages.prompt.forms import PromptForm
from passbook.stages.prompt.models import FieldTypes, Prompt, PromptStage
from passbook.stages.prompt.stage import PLAN_CONTEXT_PROMPT
@ -124,7 +123,8 @@ class TestPromptStage(TestCase):
expr_policy = ExpressionPolicy.objects.create(
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)
self.assertEqual(form.is_valid(), True)
return form
@ -138,7 +138,8 @@ class TestPromptStage(TestCase):
expr_policy = ExpressionPolicy.objects.create(
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)
self.assertEqual(form.is_valid(), False)
return form