Merge branch 'master' into e2e

This commit is contained in:
Jens Langhammer 2020-05-29 00:23:47 +02:00
commit 467b95cf02
12 changed files with 111 additions and 76 deletions

View file

@ -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 %}

View file

@ -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"

View file

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

View file

@ -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}`);

View file

@ -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")

View file

@ -38,6 +38,7 @@ class PromptSerializer(ModelSerializer):
"type",
"required",
"placeholder",
"order",
]

View file

@ -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()

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

View file

@ -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:

View file

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

View file

@ -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()

View file

@ -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