Coverage for utilities/forms/widgets/generic.py: 41%
19 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 18:35 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 18:35 +0000
1from django import forms
2from django.contrib.contenttypes.models import ContentType
4from .apiselect import APISelect
5from .select import HTMXSelect
7__all__ = (
8 'GenericObjectSelect',
9)
12class GenericObjectSelect(forms.MultiWidget):
13 """
14 Composite widget pairing a content-type selector with an API-backed object selector. Used by
15 GenericObjectChoiceField to represent a generic foreign key as a single form field.
17 Because the subwidgets are supplied as a dict, the rendered names are suffixed with the dict keys,
18 e.g. a field named "scope" renders inputs named "scope_content_type" and "scope_object_id".
19 """
20 template_name = 'widgets/generic_object.html'
22 def __init__(self, content_type_widget=None, object_widget=None, attrs=None):
23 widgets = {
24 'content_type': content_type_widget or HTMXSelect(),
25 'object_id': object_widget or APISelect(),
26 }
27 super().__init__(widgets, attrs)
29 def decompress(self, value):
30 # An object instance: split into (content type pk, object pk)
31 if value and hasattr(value, '_meta'):
32 return [ContentType.objects.get_for_model(value).pk, value.pk]
33 # An already-decomposed [content_type, object_id] pair
34 if isinstance(value, (list, tuple)):
35 if len(value) == 2:
36 return list(value)
37 if len(value) == 1:
38 return [value[0], None]
39 return [None, None]