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

1from django import forms 

2from django.contrib.contenttypes.models import ContentType 

3 

4from .apiselect import APISelect 

5from .select import HTMXSelect 

6 

7__all__ = ( 

8 'GenericObjectSelect', 

9) 

10 

11 

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. 

16 

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' 

21 

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) 

28 

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]