diff --git a/passbook/admin/templates/administration/stage_prompt/list.html b/passbook/admin/templates/administration/stage_prompt/list.html
index 5c13689b4..5aff63c69 100644
--- a/passbook/admin/templates/administration/stage_prompt/list.html
+++ b/passbook/admin/templates/administration/stage_prompt/list.html
@@ -29,6 +29,7 @@
{% trans 'Field' %} |
{% trans 'Label' %} |
{% trans 'Type' %} |
+ {% trans 'Order' %} |
{% trans 'Flows' %} |
|
@@ -51,6 +52,11 @@
{{ prompt.type }}
+
+
+ {{ prompt.order }}
+
+ |
{% for flow in prompt.flow_set.all %}
diff --git a/passbook/admin/views/stages_prompts.py b/passbook/admin/views/stages_prompts.py
index bb833d4a1..5614f206b 100644
--- a/passbook/admin/views/stages_prompts.py
+++ b/passbook/admin/views/stages_prompts.py
@@ -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"
diff --git a/passbook/flows/planner.py b/passbook/flows/planner.py
index f03f8e6f9..e44e378ee 100644
--- a/passbook/flows/planner.py
+++ b/passbook/flows/planner.py
@@ -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)
diff --git a/passbook/flows/templates/flows/shell.html b/passbook/flows/templates/flows/shell.html
index aa57f7a5d..be6b22724 100644
--- a/passbook/flows/templates/flows/shell.html
+++ b/passbook/flows/templates/flows/shell.html
@@ -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}`);
diff --git a/passbook/stages/identification/stage.py b/passbook/stages/identification/stage.py
index d06474aaf..c6abe62b6 100644
--- a/passbook/stages/identification/stage.py
+++ b/passbook/stages/identification/stage.py
@@ -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")
diff --git a/passbook/stages/prompt/api.py b/passbook/stages/prompt/api.py
index 381e9427c..1c4d4a01b 100644
--- a/passbook/stages/prompt/api.py
+++ b/passbook/stages/prompt/api.py
@@ -38,6 +38,7 @@ class PromptSerializer(ModelSerializer):
"type",
"required",
"placeholder",
+ "order",
]
diff --git a/passbook/stages/prompt/forms.py b/passbook/stages/prompt/forms.py
index d59517e00..7347e9053 100644
--- a/passbook/stages/prompt/forms.py
+++ b/passbook/stages/prompt/forms.py
@@ -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()
diff --git a/passbook/stages/prompt/migrations/0002_auto_20200528_2059.py b/passbook/stages/prompt/migrations/0002_auto_20200528_2059.py
new file mode 100644
index 000000000..5ecb06b4b
--- /dev/null
+++ b/passbook/stages/prompt/migrations/0002_auto_20200528_2059.py
@@ -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,
+ ),
+ ),
+ ]
diff --git a/passbook/stages/prompt/models.py b/passbook/stages/prompt/models.py
index 908d121b7..fc12c7b2e 100644
--- a/passbook/stages/prompt/models.py
+++ b/passbook/stages/prompt/models.py
@@ -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:
diff --git a/passbook/stages/prompt/tests.py b/passbook/stages/prompt/tests.py
index ee63c8566..85b66c1f8 100644
--- a/passbook/stages/prompt/tests.py
+++ b/passbook/stages/prompt/tests.py
@@ -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])
diff --git a/passbook/stages/user_write/stage.py b/passbook/stages/user_write/stage.py
index 4910e0426..28eab9853 100644
--- a/passbook/stages/user_write/stage.py
+++ b/passbook/stages/user_write/stage.py
@@ -25,33 +25,30 @@ class UserWriteStageView(StageView):
LOGGER.debug(message)
return self.executor.stage_invalid()
data = self.executor.plan.context[PLAN_CONTEXT_PROMPT]
- if PLAN_CONTEXT_PENDING_USER in self.executor.plan.context:
- user = self.executor.plan.context[PLAN_CONTEXT_PENDING_USER]
- for key, value in data.items():
- setter_name = f"set_{key}"
- # Check if user has a setter for this key, like set_password
- if hasattr(user, setter_name):
- setter = getattr(user, setter_name)
- if callable(setter):
- setter(value)
- # User has this key already
- elif hasattr(user, key):
- setattr(user, key, value)
- # Otherwise we just save it as custom attribute
- else:
- user.attributes[key] = value
- user.save()
- LOGGER.debug(
- "Updated existing user", user=user, flow_slug=self.executor.flow.slug,
- )
- else:
- user = User.objects.create_user(**data)
- # Set created user as pending_user, so this can be chained with user_login
- self.executor.plan.context[PLAN_CONTEXT_PENDING_USER] = user
+ if PLAN_CONTEXT_PENDING_USER not in self.executor.plan.context:
+ self.executor.plan.context[PLAN_CONTEXT_PENDING_USER] = User()
self.executor.plan.context[
PLAN_CONTEXT_AUTHENTICATION_BACKEND
] = class_to_path(ModelBackend)
LOGGER.debug(
- "Created new user", user=user, flow_slug=self.executor.flow.slug,
+ "Created new user", flow_slug=self.executor.flow.slug,
)
+ user = self.executor.plan.context[PLAN_CONTEXT_PENDING_USER]
+ for key, value in data.items():
+ setter_name = f"set_{key}"
+ # Check if user has a setter for this key, like set_password
+ if hasattr(user, setter_name):
+ setter = getattr(user, setter_name)
+ if callable(setter):
+ setter(value)
+ # User has this key already
+ elif hasattr(user, key):
+ setattr(user, key, value)
+ # Otherwise we just save it as custom attribute
+ else:
+ user.attributes[key] = value
+ user.save()
+ LOGGER.debug(
+ "Updated existing user", user=user, flow_slug=self.executor.flow.slug,
+ )
return self.executor.stage_ok()
diff --git a/swagger.yaml b/swagger.yaml
index 54beb80bb..d999299c8 100755
--- a/swagger.yaml
+++ b/swagger.yaml
@@ -6028,7 +6028,12 @@ definitions:
- e-mail
- password
- number
+ - checkbox
+ - data
+ - data-time
+ - separator
- hidden
+ - static
required:
title: Required
type: boolean
@@ -6036,6 +6041,11 @@ definitions:
title: Placeholder
type: string
minLength: 1
+ order:
+ title: Order
+ type: integer
+ maximum: 2147483647
+ minimum: -2147483648
PromptStage:
required:
- name
|