Merge branch 'master' into e2e
This commit is contained in:
commit
467b95cf02
|
@ -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()
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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()
|
||||
|
|
10
swagger.yaml
10
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
|
||||
|
|
Reference in New Issue