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:
		| @ -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"), | ||||
|     ] | ||||
|  | ||||
|  | ||||
| @ -18,6 +18,7 @@ class PromptStageSerializer(ModelSerializer): | ||||
|             "pk", | ||||
|             "name", | ||||
|             "fields", | ||||
|             "validation_policies", | ||||
|         ] | ||||
|  | ||||
|  | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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",), | ||||
|         ), | ||||
|     ] | ||||
|  | ||||
| @ -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.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 | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Jens Langhammer
					Jens Langhammer