From 8bbebfdb13be9e177959846cb0c1dfa68e09cc2a Mon Sep 17 00:00:00 2001 From: Sli Date: Sun, 20 Oct 2024 13:33:44 +0200 Subject: [PATCH] Add AutoCompleteSelectGroup --- .../core/components/ajax-select-index.ts | 57 +++++++++++++++---- .../core/widgets/autocomplete_select.jinja | 1 + core/views/widgets/select.py | 43 +++++++++++++- election/views.py | 10 ++-- 4 files changed, 91 insertions(+), 20 deletions(-) diff --git a/core/static/webpack/core/components/ajax-select-index.ts b/core/static/webpack/core/components/ajax-select-index.ts index 32888cf2..42875544 100644 --- a/core/static/webpack/core/components/ajax-select-index.ts +++ b/core/static/webpack/core/components/ajax-select-index.ts @@ -9,7 +9,12 @@ import type { TomSettings, } from "tom-select/dist/types/types"; import type { escape_html } from "tom-select/dist/types/utils"; -import { type UserProfileSchema, userSearchUsers } from "#openapi"; +import { + type GroupSchema, + type UserProfileSchema, + groupSearchGroup, + userSearchUsers, +} from "#openapi"; @registerComponent("autocomplete-select") class AutocompleteSelect extends inheritHtmlElement("select") { @@ -56,14 +61,9 @@ class AutocompleteSelect extends inheritHtmlElement("select") { connectedCallback() { super.connectedCallback(); - // Collect all options nodes and put them into the select node - const options: Element[] = []; // We need to make a copy to delete while iterating - for (const child of this.children) { - if (child.tagName.toLowerCase() === "option") { - options.push(child); - } - } - for (const option of options) { + for (const option of Array.from(this.children).filter( + (child) => child.tagName.toLowerCase() === "option", + )) { this.removeChild(option); this.node.appendChild(option); } @@ -158,6 +158,18 @@ abstract class AjaxSelect extends AutocompleteSelect { }, }; } + + protected attachBehaviors() { + super.attachBehaviors(); + + // Gather selected options, they must be added with slots like `json` + for (const value of Array.from(this.children) + .filter((child) => child.tagName.toLowerCase() === "slot") + .map((slot) => JSON.parse(slot.innerHTML))) { + this.widget.addOption(value, true); + this.widget.addItem(value[this.valueField]); + } + } } @registerComponent("user-ajax-select") @@ -172,9 +184,6 @@ export class UserAjaxSelect extends AjaxSelect { } return []; } - protected tomSelectSettings(): RecursivePartial { - return super.tomSelectSettings(); - } protected renderOption(item: UserProfileSchema, sanitize: typeof escape_html) { return `
@@ -191,3 +200,27 @@ export class UserAjaxSelect extends AjaxSelect { return `${sanitize(item.display_name)}`; } } + +@registerComponent("group-ajax-select") +export class GroupsAjaxSelect extends AjaxSelect { + protected valueField = "id"; + protected labelField = "name"; + + protected async search(query: string): Promise { + const resp = await groupSearchGroup({ query: { search: query } }); + if (resp.data) { + return resp.data.results; + } + return []; + } + + protected renderOption(item: GroupSchema, sanitize: typeof escape_html) { + return `
+ ${sanitize(item.name)} +
`; + } + + protected renderItem(item: GroupSchema, sanitize: typeof escape_html) { + return `${sanitize(item.name)}`; + } +} diff --git a/core/templates/core/widgets/autocomplete_select.jinja b/core/templates/core/widgets/autocomplete_select.jinja index 30bc7eb5..ab91d766 100644 --- a/core/templates/core/widgets/autocomplete_select.jinja +++ b/core/templates/core/widgets/autocomplete_select.jinja @@ -7,4 +7,5 @@ {% endif %}{% for widget in group_choices %} {% include widget.template_name %}{% endfor %}{% if group_name %} {% endif %}{% endfor %} +{% for sel in selected %}{{ sel }}{% endfor %} \ No newline at end of file diff --git a/core/views/widgets/select.py b/core/views/widgets/select.py index 8bc7f5ea..a83ead7e 100644 --- a/core/views/widgets/select.py +++ b/core/views/widgets/select.py @@ -1,11 +1,27 @@ from django.contrib.staticfiles.storage import staticfiles_storage +from django.db.models import Model from django.forms import Select, SelectMultiple +from ninja import ModelSchema + +from core.models import Group, User +from core.schemas import GroupSchema, UserProfileSchema class AutoCompleteSelectMixin: component_name = "autocomplete-select" template_name = "core/widgets/autocomplete_select.jinja" - is_ajax = False + model: Model | None = None + schema: ModelSchema | None = None + pk = "id" + + def __init__(self, attrs=None, choices=()): + if self.is_ajax: + choices = () # Avoid computing anything when in ajax mode + super().__init__(attrs=attrs, choices=choices) + + @property + def is_ajax(self): + return self.model and self.schema def optgroups(self, name, value, attrs=None): """Don't create option groups when doing ajax""" @@ -27,6 +43,13 @@ class AutoCompleteSelectMixin: staticfiles_storage.url("core/components/ajax-select.scss"), ], } + if self.is_ajax: + context["selected"] = [ + self.schema.from_orm(obj).json() + for obj in self.model.objects.filter( + **{f"{self.pk}__in": context["widget"]["value"]} + ).all() + ] return context @@ -38,9 +61,23 @@ class AutoCompleteSelectMultiple(AutoCompleteSelectMixin, SelectMultiple): ... class AutoCompleteSelectUser(AutoCompleteSelectMixin, Select): component_name = "user-ajax-select" - is_ajax = True + model = User + schema = UserProfileSchema class AutoCompleteSelectMultipleUser(AutoCompleteSelectMixin, SelectMultiple): component_name = "user-ajax-select" - is_ajax = True + model = User + schema = UserProfileSchema + + +class AutoCompleteSelectGroup(AutoCompleteSelectMixin, Select): + component_name = "group-ajax-select" + model = Group + schema = GroupSchema + + +class AutoCompleteSelectMultipleGroup(AutoCompleteSelectMixin, SelectMultiple): + component_name = "group-ajax-select" + model = Group + schema = GroupSchema diff --git a/election/views.py b/election/views.py index 1f189f41..422205fd 100644 --- a/election/views.py +++ b/election/views.py @@ -15,7 +15,7 @@ from core.views.forms import SelectDateTime from core.views.widgets.markdown import MarkdownInput from core.views.widgets.select import ( AutoCompleteSelect, - AutoCompleteSelectMultiple, + AutoCompleteSelectMultipleGroup, AutoCompleteSelectUser, ) from election.models import Candidature, Election, ElectionList, Role, Vote @@ -157,10 +157,10 @@ class ElectionForm(forms.ModelForm): "candidature_groups", ] widgets = { - "edit_groups": AutoCompleteSelectMultiple, - "view_groups": AutoCompleteSelectMultiple, - "vote_groups": AutoCompleteSelectMultiple, - "candidature_groups": AutoCompleteSelectMultiple, + "edit_groups": AutoCompleteSelectMultipleGroup, + "view_groups": AutoCompleteSelectMultipleGroup, + "vote_groups": AutoCompleteSelectMultipleGroup, + "candidature_groups": AutoCompleteSelectMultipleGroup, } start_date = forms.DateTimeField(