from typing import TYPE_CHECKING, Any from django.contrib.staticfiles.storage import staticfiles_storage from django.forms import Select, SelectMultiple from pydantic import TypeAdapter from core.models import Group, SithFile, User from core.schemas import GroupSchema, SithFileSchema, UserProfileSchema if TYPE_CHECKING: from collections.abc import Collection from django.db.models import Model, QuerySet from ninja import ModelSchema class AutoCompleteSelectMixin: component_name = "autocomplete-select" template_name = "core/widgets/autocomplete_select.jinja" model: type[Model] | None = None adapter: TypeAdapter[Collection[ModelSchema]] | None = None pk = "id" js = [ "bundled/core/components/ajax-select-index.ts", ] css = [ "bundled/core/components/ajax-select-index.css", "core/components/ajax-select.scss", ] def get_queryset(self, pks: Collection[Any]) -> QuerySet: return self.model.objects.filter( **{ f"{self.pk}__in": [ pk for pk in pks if str(pk).isdigit() # We filter empty values for create views ] } ).all() 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.adapter and self.model def optgroups(self, name, value, attrs=None): """Don't create option groups when doing ajax""" if self.is_ajax: return [] return super().optgroups(name, value, attrs=attrs) def get_context(self, name, value, attrs): context = super().get_context(name, value, attrs) context["widget"]["attrs"]["autocomplete"] = "off" context["component"] = self.component_name context["statics"] = { "js": [staticfiles_storage.url(file) for file in self.js], "css": [staticfiles_storage.url(file) for file in self.css], } if self.is_ajax: context["initial"] = self.adapter.dump_json( self.adapter.validate_python( self.get_queryset(context["widget"]["value"]) ) ).decode("utf-8") return context class AutoCompleteSelect(AutoCompleteSelectMixin, Select): ... class AutoCompleteSelectMultiple(AutoCompleteSelectMixin, SelectMultiple): ... class AutoCompleteSelectUser(AutoCompleteSelect): component_name = "user-ajax-select" model = User adapter = TypeAdapter(list[UserProfileSchema]) class AutoCompleteSelectMultipleUser(AutoCompleteSelectMultiple): component_name = "user-ajax-select" model = User adapter = TypeAdapter(list[UserProfileSchema]) class AutoCompleteSelectGroup(AutoCompleteSelect): component_name = "group-ajax-select" model = Group adapter = TypeAdapter(list[GroupSchema]) class AutoCompleteSelectMultipleGroup(AutoCompleteSelectMultiple): component_name = "group-ajax-select" model = Group adapter = TypeAdapter(list[GroupSchema]) class AutoCompleteSelectSithFile(AutoCompleteSelect): component_name = "sith-file-ajax-select" model = SithFile adapter = TypeAdapter(list[SithFileSchema]) class AutoCompleteSelectMultipleSithFile(AutoCompleteSelectMultiple): component_name = "sith-file-ajax-select" model = SithFile adapter = TypeAdapter(list[SithFileSchema])