diff --git a/passbook/stages/prompt/forms.py b/passbook/stages/prompt/forms.py index 15bb648ae..908395079 100644 --- a/passbook/stages/prompt/forms.py +++ b/passbook/stages/prompt/forms.py @@ -1,12 +1,15 @@ """Prompt forms""" +from typing import Callable + from django import forms from django.contrib.admin.widgets import FilteredSelectMultiple 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.stages.prompt.models import Prompt, PromptStage +from passbook.stages.prompt.models import FieldTypes, Prompt, PromptStage class PromptStageForm(forms.ModelForm): @@ -57,6 +60,14 @@ class PromptForm(forms.Form): for field in fields: field: Prompt self.fields[field.field_key] = field.field + # Special handling for fields with username type + # these check for existing users with the same username + if field.type == FieldTypes.USERNAME: + setattr( + self, + f"clean_{field.field_key}", + username_field_cleaner_generator(field), + ) self.field_order = sorted(fields, key=lambda x: x.order) def clean(self): @@ -68,3 +79,15 @@ class PromptForm(forms.Form): result = engine.result if not result.passing: raise forms.ValidationError(list(result.messages)) + + +def username_field_cleaner_generator(field: Prompt) -> Callable: + """Return a `clean_` method for `field`. Clean method checks if username is taken already.""" + + def username_field_cleaner(self: PromptForm): + """Check for duplicate usernames""" + username = self.cleaned_data.get(field.field_key) + if User.objects.filter(username=username).exists(): + raise forms.ValidationError("Username is already taken.") + + return username_field_cleaner diff --git a/passbook/stages/prompt/models.py b/passbook/stages/prompt/models.py index 1cea88068..a7374e4d6 100644 --- a/passbook/stages/prompt/models.py +++ b/passbook/stages/prompt/models.py @@ -7,25 +7,34 @@ from django.utils.translation import gettext_lazy as _ from passbook.flows.models import Stage from passbook.policies.models import PolicyBindingModel +from passbook.stages.prompt.widgets import HorizontalRuleWidget, StaticTextWidget class FieldTypes(models.TextChoices): """Field types an Prompt can be""" # Simple text field - TEXT = "text" + TEXT = "text", _("Text: Simple Text input") # Same as text, but has autocomplete for password managers - USERNAME = "username" - EMAIL = "email" + USERNAME = ( + "username", + _( + ( + "Username: Same as Text input, but checks for " + "duplicate and prevents duplicate usernames." + ) + ), + ) + EMAIL = "email", _("Email: Text field with Email type.") PASSWORD = "password" # noqa # nosec NUMBER = "number" CHECKBOX = "checkbox" DATE = "data" DATE_TIME = "data-time" - SEPARATOR = "separator" - HIDDEN = "hidden" - STATIC = "static" + SEPARATOR = "separator", _("Separator: Static Separator Line") + HIDDEN = "hidden", _("Hidden: Hidden field, can be used to insert data into form.") + STATIC = "static", _("Static: Static value, displayed as-is.") class Prompt(models.Model): @@ -74,9 +83,16 @@ class Prompt(models.Model): field_class = forms.DateInput if self.type == FieldTypes.DATE_TIME: field_class = forms.DateTimeInput + if self.type == FieldTypes.STATIC: + widget = StaticTextWidget(attrs=attrs) + kwargs["initial"] = self.placeholder + kwargs["required"] = False + kwargs["label"] = "" + if self.type == FieldTypes.SEPARATOR: + widget = HorizontalRuleWidget(attrs=attrs) + kwargs["required"] = False + kwargs["label"] = "" - # TODO: Implement static - # TODO: Implement separator kwargs["widget"] = widget return field_class(**kwargs) diff --git a/passbook/stages/prompt/widgets.py b/passbook/stages/prompt/widgets.py new file mode 100644 index 000000000..0ddb26a10 --- /dev/null +++ b/passbook/stages/prompt/widgets.py @@ -0,0 +1,17 @@ +"""Prompt Widgets""" +from django import forms +from django.utils.safestring import mark_safe + + +class StaticTextWidget(forms.widgets.Widget): + """Widget to render static text""" + + def render(self, name, value, attrs=None, renderer=None): + return mark_safe(f"

{value}

") # nosec + + +class HorizontalRuleWidget(forms.widgets.Widget): + """Widget, which renders an
element""" + + def render(self, name, value, attrs=None, renderer=None): + return mark_safe("
") # nosec