From 8f585eca70a7275cc6348b0791baae8db60ea855 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Wed, 30 Sep 2020 23:55:23 +0200 Subject: [PATCH] stages/identification: replace buggy FilteredSelectMultiple with ArrayFieldSelectMultiple --- passbook/admin/fields.py | 28 +++++++++++++++++++++++- passbook/stages/identification/forms.py | 6 ++--- passbook/stages/identification/models.py | 7 +++++- swagger.yaml | 7 +++++- 4 files changed, 41 insertions(+), 7 deletions(-) diff --git a/passbook/admin/fields.py b/passbook/admin/fields.py index b7c952f7c..b6749172a 100644 --- a/passbook/admin/fields.py +++ b/passbook/admin/fields.py @@ -1,9 +1,35 @@ -"""YAML fields""" +"""Additional fields""" import yaml from django import forms +from django.utils.datastructures import MultiValueDict from django.utils.translation import gettext_lazy as _ +class ArrayFieldSelectMultiple(forms.SelectMultiple): + """This is a Form Widget for use with a Postgres ArrayField. It implements + a multi-select interface that can be given a set of `choices`. + You can provide a `delimiter` keyword argument to specify the delimeter used. + + https://gist.github.com/stephane/00e73c0002de52b1c601""" + + def __init__(self, *args, **kwargs): + # Accept a `delimiter` argument, and grab it (defaulting to a comma) + self.delimiter = kwargs.pop("delimiter", ",") + super().__init__(*args, **kwargs) + + def value_from_datadict(self, data, files, name): + if isinstance(data, MultiValueDict): + # Normally, we'd want a list here, which is what we get from the + # SelectMultiple superclass, but the SimpleArrayField expects to + # get a delimited string, so we're doing a little extra work. + return self.delimiter.join(data.getlist(name)) + + return data.get(name) + + def get_context(self, name, value, attrs): + return super().get_context(name, value.split(self.delimiter), attrs) + + class CodeMirrorWidget(forms.Textarea): """Custom Textarea-based Widget that triggers a CodeMirror editor""" diff --git a/passbook/stages/identification/forms.py b/passbook/stages/identification/forms.py index a8827abd9..70bf71eb8 100644 --- a/passbook/stages/identification/forms.py +++ b/passbook/stages/identification/forms.py @@ -1,10 +1,10 @@ """passbook flows identification forms""" from django import forms -from django.contrib.admin.widgets import FilteredSelectMultiple from django.core.validators import validate_email from django.utils.translation import gettext_lazy as _ from structlog import get_logger +from passbook.admin.fields import ArrayFieldSelectMultiple from passbook.flows.models import Flow, FlowDesignation from passbook.lib.utils.ui import human_list from passbook.stages.identification.models import IdentificationStage, UserFields @@ -37,9 +37,7 @@ class IdentificationStageForm(forms.ModelForm): ] widgets = { "name": forms.TextInput(), - "user_fields": FilteredSelectMultiple( - _("fields"), False, choices=UserFields.choices - ), + "user_fields": ArrayFieldSelectMultiple(choices=UserFields.choices), } diff --git a/passbook/stages/identification/models.py b/passbook/stages/identification/models.py index 70fc139f1..27f25dd86 100644 --- a/passbook/stages/identification/models.py +++ b/passbook/stages/identification/models.py @@ -30,7 +30,12 @@ class IdentificationStage(Stage): user_fields = ArrayField( models.CharField(max_length=100, choices=UserFields.choices), - help_text=_("Fields of the user object to match against."), + help_text=_( + ( + "Fields of the user object to match against. " + "(Hold shift to select multiple options)" + ) + ), ) template = models.TextField(choices=Templates.choices) diff --git a/swagger.yaml b/swagger.yaml index 7dd6bd231..1412f9cbc 100755 --- a/swagger.yaml +++ b/swagger.yaml @@ -7342,7 +7342,8 @@ definitions: type: string minLength: 1 user_fields: - description: Fields of the user object to match against. + description: Fields of the user object to match against. (Hold shift to select + multiple options) type: array items: title: User fields @@ -7350,6 +7351,10 @@ definitions: enum: - email - username + case_insensitive_matching: + title: Case insensitive matching + description: When enabled, user fields are matched regardless of their casing. + type: boolean template: title: Template type: string