Refactor UserUserGroupForm to use a reusable UserSearchSelectMultipleWidget for improved user selection and search functionality

This commit is contained in:
Ross
2025-11-13 21:48:58 +00:00
parent 345977e2ef
commit a70354c18d
+13 -14
View File
@@ -556,20 +556,16 @@ class UserUserGroupModelChoiceField(ModelMultipleChoiceField):
return f"{obj.username} ({obj.userprofile.grade}) [{obj.email}]" return f"{obj.username} ({obj.userprofile.grade}) [{obj.email}]"
class UserUserGroupForm(ModelForm): class UserSearchSelectMultipleWidget:
class UserSearchSelectMultiple(object): """Reusable lightweight widget renderer for a searchable multi-user selector.
"""
Lightweight widget renderer for a searchable multi-user selector. Usage: instantiate with (name, value) where value is an iterable of user ids
Renders a hidden <select multiple> containing selected user ids and and call .render() to get the HTML fragment. This mirrors the previous
a search box with results. Uses a small inline script to fetch nested widget but is available module-wide for reuse.
results from the `generic:user_search_widget` endpoint and add/remove
users from the selection.
This keeps implementation simple and avoids external JS dependencies.
""" """
def __init__(self, name, value): def __init__(self, name, value):
self.name = name self.name = name
# value is a list of selected user ids
self.value = value or [] self.value = value or []
def render(self): def render(self):
@@ -578,13 +574,11 @@ class UserUserGroupForm(ModelForm):
results_id = f"user_search_results_{self.name}" results_id = f"user_search_results_{self.name}"
selected_list_id = f"selected_users_{self.name}" selected_list_id = f"selected_users_{self.name}"
# Build initial options for selected users
users = User.objects.filter(pk__in=self.value) if self.value else [] users = User.objects.filter(pk__in=self.value) if self.value else []
options_html = "" options_html = ""
selected_html = "" selected_html = ""
for u in users: for u in users:
options_html += f'<option value="{u.pk}" selected>{u.get_full_name() or u.username} - {u.email}</option>' options_html += f'<option value="{u.pk}" selected>{u.get_full_name() or u.username} - {u.email}</option>'
# include grade display in the selected list if available
grade_text = "" grade_text = ""
try: try:
up = getattr(u, "userprofile", None) up = getattr(u, "userprofile", None)
@@ -605,10 +599,10 @@ class UserUserGroupForm(ModelForm):
url = reverse("generic:user_search_widget") url = reverse("generic:user_search_widget")
# Build a small grade filter select
grades_options = '<option value="">All grades</option>' grades_options = '<option value="">All grades</option>'
try: try:
from generic.models import UserGrades from generic.models import UserGrades
grades_qs = UserGrades.objects.all() grades_qs = UserGrades.objects.all()
for g in grades_qs: for g in grades_qs:
grades_options += f'<option value="{g.pk}">{g.name}</option>' grades_options += f'<option value="{g.pk}">{g.name}</option>'
@@ -776,6 +770,11 @@ class UserUserGroupForm(ModelForm):
return mark_safe(html) return mark_safe(html)
class UserUserGroupForm(ModelForm):
# Use module-level UserSearchSelectMultipleWidget for rendering the users field
users = UserUserGroupModelChoiceField( users = UserUserGroupModelChoiceField(
required=False, required=False,
queryset=User.objects.all(), queryset=User.objects.all(),
@@ -808,7 +807,7 @@ class UserUserGroupForm(ModelForm):
# store attrs so template helpers can inspect them # store attrs so template helpers can inspect them
self.attrs = attrs or {} self.attrs = attrs or {}
value = value or [] value = value or []
widget = UserUserGroupForm.UserSearchSelectMultiple(name, value) widget = UserSearchSelectMultipleWidget(name, value)
return widget.render() return widget.render()
def value_from_datadict(self, data, files, name): def value_from_datadict(self, data, files, name):