stages/prompt: add prompt order field
This commit is contained in:
		| @ -29,6 +29,7 @@ | ||||
|                     <th role="columnheader" scope="col">{% trans 'Field' %}</th> | ||||
|                     <th role="columnheader" scope="col">{% trans 'Label' %}</th> | ||||
|                     <th role="columnheader" scope="col">{% trans 'Type' %}</th> | ||||
|                     <th role="columnheader" scope="col">{% trans 'Order' %}</th> | ||||
|                     <th role="columnheader" scope="col">{% trans 'Flows' %}</th> | ||||
|                     <th role="cell"></th> | ||||
|                 </tr> | ||||
| @ -51,6 +52,11 @@ | ||||
|                             {{ prompt.type }} | ||||
|                         </div> | ||||
|                     </td> | ||||
|                     <td role="cell"> | ||||
|                         <div> | ||||
|                             {{ prompt.order }} | ||||
|                         </div> | ||||
|                     </td> | ||||
|                     <td role="cell"> | ||||
|                         <ul> | ||||
|                             {% for flow in prompt.flow_set.all %} | ||||
|  | ||||
| @ -20,7 +20,7 @@ class PromptListView(LoginRequiredMixin, PermissionListMixin, ListView): | ||||
|  | ||||
|     model = Prompt | ||||
|     permission_required = "passbook_stages_prompt.view_prompt" | ||||
|     ordering = "field_key" | ||||
|     ordering = "order" | ||||
|     paginate_by = 40 | ||||
|     template_name = "administration/stage_prompt/list.html" | ||||
|  | ||||
|  | ||||
| @ -81,7 +81,6 @@ class FlowPlanner: | ||||
|             LOGGER.debug( | ||||
|                 "f(plan): Taking plan from cache", flow=self.flow, key=cached_plan_key | ||||
|             ) | ||||
|             LOGGER.debug(cached_plan) | ||||
|             return cached_plan | ||||
|         plan = self._build_plan(user, request, default_context) | ||||
|         cache.set(cache_key(self.flow, user), plan) | ||||
|  | ||||
| @ -138,7 +138,7 @@ const loadFormCode = () => { | ||||
|         newScript.src = script.src; | ||||
|         document.head.appendChild(newScript); | ||||
|     }); | ||||
| } | ||||
| }; | ||||
| const setFormSubmitHandlers = () => { | ||||
|     document.querySelectorAll("#flow-body form").forEach(form => { | ||||
|         console.log(`Setting action for form ${form}`); | ||||
|  | ||||
| @ -38,13 +38,13 @@ class IdentificationStageView(FormView, StageView): | ||||
|         enrollment_flow = self.executor.flow.related_flow(FlowDesignation.ENROLLMENT) | ||||
|         if enrollment_flow: | ||||
|             kwargs["enroll_url"] = reverse( | ||||
|                 "passbook_flows:flow-executor", | ||||
|                 "passbook_flows:flow-executor-shell", | ||||
|                 kwargs={"flow_slug": enrollment_flow.slug}, | ||||
|             ) | ||||
|         recovery_flow = self.executor.flow.related_flow(FlowDesignation.RECOVERY) | ||||
|         if recovery_flow: | ||||
|             kwargs["recovery_url"] = reverse( | ||||
|                 "passbook_flows:flow-executor", | ||||
|                 "passbook_flows:flow-executor-shell", | ||||
|                 kwargs={"flow_slug": recovery_flow.slug}, | ||||
|             ) | ||||
|         kwargs["primary_action"] = _("Log in") | ||||
|  | ||||
| @ -38,6 +38,7 @@ class PromptSerializer(ModelSerializer): | ||||
|             "type", | ||||
|             "required", | ||||
|             "placeholder", | ||||
|             "order", | ||||
|         ] | ||||
|  | ||||
|  | ||||
|  | ||||
| @ -31,6 +31,7 @@ class PromptAdminForm(forms.ModelForm): | ||||
|             "type", | ||||
|             "required", | ||||
|             "placeholder", | ||||
|             "order", | ||||
|         ] | ||||
|         widgets = { | ||||
|             "label": forms.TextInput(), | ||||
| @ -48,9 +49,12 @@ class PromptForm(forms.Form): | ||||
|         self.stage = stage | ||||
|         self.plan = plan | ||||
|         super().__init__(*args, **kwargs) | ||||
|         for field in self.stage.fields.all(): | ||||
|         # list() is called so we only load the fields once | ||||
|         fields = list(self.stage.fields.all()) | ||||
|         for field in fields: | ||||
|             field: Prompt | ||||
|             self.fields[field.field_key] = field.field | ||||
|         self.field_order = sorted(fields, key=lambda x: x.order) | ||||
|  | ||||
|     def clean(self): | ||||
|         cleaned_data = super().clean() | ||||
|  | ||||
							
								
								
									
										35
									
								
								passbook/stages/prompt/migrations/0002_auto_20200528_2059.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								passbook/stages/prompt/migrations/0002_auto_20200528_2059.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,35 @@ | ||||
| # 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, | ||||
|             ), | ||||
|         ), | ||||
|     ] | ||||
| @ -16,7 +16,13 @@ class FieldTypes(models.TextChoices): | ||||
|     EMAIL = "e-mail" | ||||
|     PASSWORD = "password"  # noqa # nosec | ||||
|     NUMBER = "number" | ||||
|     CHECKBOX = "checkbox" | ||||
|     DATE = "data" | ||||
|     DATE_TIME = "data-time" | ||||
|  | ||||
|     SEPARATOR = "separator" | ||||
|     HIDDEN = "hidden" | ||||
|     STATIC = "static" | ||||
|  | ||||
|  | ||||
| class Prompt(models.Model): | ||||
| @ -32,41 +38,37 @@ class Prompt(models.Model): | ||||
|     required = models.BooleanField(default=True) | ||||
|     placeholder = models.TextField() | ||||
|  | ||||
|     order = models.IntegerField(default=0) | ||||
|  | ||||
|     @property | ||||
|     def field(self): | ||||
|         """Return instantiated form input field""" | ||||
|         attrs = {"placeholder": _(self.placeholder)} | ||||
|         if self.type == FieldTypes.TEXT: | ||||
|             return forms.CharField( | ||||
|                 label=_(self.label), | ||||
|                 widget=forms.TextInput(attrs=attrs), | ||||
|                 required=self.required, | ||||
|             ) | ||||
|         field_class = forms.CharField | ||||
|         widget = forms.TextInput(attrs=attrs) | ||||
|         kwargs = { | ||||
|             "label": _(self.label), | ||||
|             "required": self.required, | ||||
|         } | ||||
|         if self.type == FieldTypes.EMAIL: | ||||
|             return forms.EmailField( | ||||
|                 label=_(self.label), | ||||
|                 widget=forms.TextInput(attrs=attrs), | ||||
|                 required=self.required, | ||||
|             ) | ||||
|             field_class = forms.EmailField | ||||
|         if self.type == FieldTypes.PASSWORD: | ||||
|             return forms.CharField( | ||||
|                 label=_(self.label), | ||||
|                 widget=forms.PasswordInput(attrs=attrs), | ||||
|                 required=self.required, | ||||
|             ) | ||||
|             widget = forms.PasswordInput(attrs=attrs) | ||||
|         if self.type == FieldTypes.NUMBER: | ||||
|             return forms.IntegerField( | ||||
|                 label=_(self.label), | ||||
|                 widget=forms.NumberInput(attrs=attrs), | ||||
|                 required=self.required, | ||||
|             ) | ||||
|             field_class = forms.IntegerField | ||||
|             widget = forms.NumberInput(attrs=attrs) | ||||
|         if self.type == FieldTypes.HIDDEN: | ||||
|             return forms.CharField( | ||||
|                 widget=forms.HiddenInput(attrs=attrs), | ||||
|                 required=False, | ||||
|                 initial=self.placeholder, | ||||
|             ) | ||||
|         raise ValueError("field_type is not valid, not one of FieldTypes.") | ||||
|             widget = forms.HiddenInput(attrs=attrs) | ||||
|             kwargs["required"] = False | ||||
|             kwargs["initial"] = self.placeholder | ||||
|         if self.type == FieldTypes.CHECKBOX: | ||||
|             field_class = forms.CheckboxInput | ||||
|             kwargs["required"] = False | ||||
|  | ||||
|         # TODO: Implement static | ||||
|         # TODO: Implement separator | ||||
|         kwargs["widget"] = widget | ||||
|         return field_class(**kwargs) | ||||
|  | ||||
|     def save(self, *args, **kwargs): | ||||
|         if self.type not in FieldTypes: | ||||
|  | ||||
| @ -93,25 +93,6 @@ class TestPromptStage(TestCase): | ||||
|  | ||||
|         FlowStageBinding.objects.create(flow=self.flow, stage=self.stage, order=2) | ||||
|  | ||||
|     def test_invalid_type(self): | ||||
|         """Test that invalid form type raises an error""" | ||||
|         with self.assertRaises(ValueError): | ||||
|             _ = Prompt.objects.create( | ||||
|                 field_key="hidden_prompt", | ||||
|                 type="invalid", | ||||
|                 required=True, | ||||
|                 placeholder="HIDDEN_PLACEHOLDER", | ||||
|             ) | ||||
|         with self.assertRaises(ValueError): | ||||
|             prompt = Prompt.objects.create( | ||||
|                 field_key="hidden_prompt", | ||||
|                 type=FieldTypes.HIDDEN, | ||||
|                 required=True, | ||||
|                 placeholder="HIDDEN_PLACEHOLDER", | ||||
|             ) | ||||
|             with patch.object(prompt, "type", MagicMock(return_value="invalid")): | ||||
|                 _ = prompt.field | ||||
|  | ||||
|     def test_render(self): | ||||
|         """Test render of form, check if all prompts are rendered correctly""" | ||||
|         plan = FlowPlan(flow_pk=self.flow.pk.hex, stages=[self.stage]) | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Jens Langhammer
					Jens Langhammer