From 669e1974221a68df76109ef85f12ad536f3c5144 Mon Sep 17 00:00:00 2001 From: Saira Bano <79838286+sbanoeon@users.noreply.github.com> Date: Wed, 4 Sep 2024 15:54:03 +0200 Subject: [PATCH] refactor: possible solution for selection of users (#188) * refactor: possible solution for selection of users * chore: custom dropdown with checkboxes * fix: fixed integrity error bug * chore: made dropdown searchable and scrollable * refactor: refactor code * refactor: refactor code * fix: validation error handling and data reload * fix: fixed optional fields --------- Co-authored-by: Sebastian Blechmann --- app/Entirety/projects/forms.py | 99 +++++++++++-------- .../projects/templates/projects/update.html | 77 +++++++++++++++ app/Entirety/static/js/main.js | 23 +++++ app/Entirety/static/scss/style.scss | 10 ++ 4 files changed, 170 insertions(+), 39 deletions(-) diff --git a/app/Entirety/projects/forms.py b/app/Entirety/projects/forms.py index e6363578..4c6dcc9c 100644 --- a/app/Entirety/projects/forms.py +++ b/app/Entirety/projects/forms.py @@ -1,5 +1,4 @@ from crispy_forms.helper import FormHelper -from crispy_forms.layout import Submit from django import forms from django.core.exceptions import ValidationError from django.db.models import Q @@ -30,17 +29,26 @@ def __init__(self, user, *args, **kwargs): "title" ] = "The owner is assigned automatically on project creation. It can only be updated by a server admin." - self.fields["users"].widget = forms.CheckboxSelectMultiple( - attrs={ - "data-bs-toggle": "tooltip", - "data-bs-placement": "left", - "title": "Include or exclude users into project", - } + self.fields["logo"].required = False + self.fields["webpage_url"].required = False + + self.fields["viewers"] = forms.ModelMultipleChoiceField( + queryset=( + User.objects.exclude(id=self.instance.owner_id) + & User.objects.exclude(id=user.id) + ).filter(is_server_admin=False), + widget=forms.CheckboxSelectMultiple, + required=False, + ) + + self.fields["users"] = forms.ModelMultipleChoiceField( + widget=forms.CheckboxSelectMultiple, + queryset=( + User.objects.exclude(id=self.instance.owner_id) + & User.objects.exclude(id=user.id) + ).filter(is_server_admin=False), + required=False, ) - self.fields["users"].queryset = ( - User.objects.exclude(id=self.instance.owner_id) - & User.objects.exclude(id=user.id) - ).filter(is_server_admin=False) if user in self.instance.maintainers.all(): self.fields["maintainers"].disabled = True @@ -52,34 +60,38 @@ def __init__(self, user, *args, **kwargs): "title" ] = "Inclusion or exclusion of maintainers into project can by done by project owners only." else: - self.fields["maintainers"].widget = forms.CheckboxSelectMultiple( - attrs={ - "data-bs-toggle": "tooltip", - "data-bs-placement": "left", - "title": "Include or exclude maintainers into project", - } + self.fields["maintainers"] = forms.ModelMultipleChoiceField( + queryset=( + User.objects.exclude(id=self.instance.owner_id) + & User.objects.exclude(id=user.id) + ).filter(is_server_admin=False), + widget=forms.CheckboxSelectMultiple, + required=False, ) - self.fields["maintainers"].queryset = ( - User.objects.exclude(id=self.instance.owner_id) - & User.objects.exclude(id=user.id) - ).filter(is_server_admin=False) - - self.fields["viewers"].widget = forms.CheckboxSelectMultiple( - attrs={ - "data-bs-toggle": "tooltip", - "data-bs-placement": "left", - "title": "Include or exclude viewers into project", - } - ) - self.fields["viewers"].queryset = ( - User.objects.exclude(id=self.instance.owner_id) - & User.objects.exclude(id=user.id) - ).filter(is_server_admin=False) - self.helper.layout.append(Submit(name="save", value="Save")) + self.helper.form_tag = False - self.fields["logo"].required = False - self.fields["webpage_url"].required = False + if self.is_bound: + self.fields["viewers"].initial = [ + int(id) for id in self.data.getlist("viewers") + ] + self.fields["users"].initial = [ + int(id) for id in self.data.getlist("users") + ] + self.fields["maintainers"].initial = [ + int(id) for id in self.data.getlist("maintainers") + ] + else: + if self.instance.pk: + self.fields["viewers"].initial = list( + self.instance.viewers.values_list("id", flat=True) + ) + self.fields["users"].initial = list( + self.instance.users.values_list("id", flat=True) + ) + self.fields["maintainers"].initial = list( + self.instance.maintainers.values_list("id", flat=True) + ) def clean(self): cleaned_data = super().clean() @@ -89,6 +101,18 @@ def clean(self): except Exception as e: raise ValidationError(e) + def save(self, commit=True): + instance = super().save(commit=True) + + instance.viewers.set(self.cleaned_data["viewers"]) + instance.users.set(self.cleaned_data["users"]) + instance.maintainers.set(self.cleaned_data["maintainers"]) + + if commit: + instance.save() + + return instance + class Meta: model = Project fields = [ @@ -98,9 +122,6 @@ class Meta: "webpage_url", "logo", "owner", - "maintainers", - "users", - "viewers", ] widgets = { "name": forms.TextInput( diff --git a/app/Entirety/projects/templates/projects/update.html b/app/Entirety/projects/templates/projects/update.html index e012d832..4f698f76 100644 --- a/app/Entirety/projects/templates/projects/update.html +++ b/app/Entirety/projects/templates/projects/update.html @@ -14,6 +14,83 @@

{% if project.name %} Edit {{ project.name }} {% else %} Create {% if form_submitted %}class="was-validated" {% endif %}> {% csrf_token %} {% crispy form %} +
+ + + + + +
+ diff --git a/app/Entirety/static/js/main.js b/app/Entirety/static/js/main.js index 97252866..8fbf3cb8 100644 --- a/app/Entirety/static/js/main.js +++ b/app/Entirety/static/js/main.js @@ -16,3 +16,26 @@ } }) })() + +document.addEventListener('DOMContentLoaded', function() { + const searchListPairs = [ + { search: 'viewerSearch', list: 'viewerList' }, + { search: 'userSearch', list: 'userList' }, + { search: 'maintainerSearch', list: 'maintainerList' } + ]; + + searchListPairs.forEach(function(pair) { + const searchElement = document.getElementById(pair.search); + const listElement = document.getElementById(pair.list); + + searchElement.addEventListener('input', function() { + const query = this.value.toLowerCase(); + const checkboxes = listElement.querySelectorAll('.form-check'); + + checkboxes.forEach(function(checkbox) { + const label = checkbox.querySelector('label').textContent.toLowerCase(); + checkbox.style.display = label.includes(query) ? 'block' : 'none'; + }); + }); + }); +}); diff --git a/app/Entirety/static/scss/style.scss b/app/Entirety/static/scss/style.scss index dd208ba4..1078ad2e 100644 --- a/app/Entirety/static/scss/style.scss +++ b/app/Entirety/static/scss/style.scss @@ -10,3 +10,13 @@ h1{ color: $primary } + +.dropdown-scrollable-checkboxes { + @extend .dropdown-scrollable !optional; + max-height: 150px; + overflow-y: auto; +} + +.form-check[aria-hidden="false"] { + display: block; +}