Coverage for netbox/forms/mixins.py: 42%
59 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.utils.translation import gettext as _
4from core.models import ObjectType
5from extras.choices import *
6from extras.models import *
7from users.models import Owner, OwnerGroup
8from utilities.forms.fields import DynamicModelChoiceField, DynamicModelMultipleChoiceField
10__all__ = (
11 'ChangelogMessageMixin',
12 'CustomFieldsMixin',
13 'OwnerFilterMixin',
14 'OwnerMixin',
15 'SavedFiltersMixin',
16 'TagsMixin',
17)
20class ChangelogMessageMixin(forms.Form):
21 """
22 Adds an optional field for recording a message on the resulting changelog record(s).
23 """
24 changelog_message = forms.CharField(
25 required=False,
26 max_length=200,
27 )
29 def __init__(self, *args, **kwargs):
30 super().__init__(*args, **kwargs)
32 # Declare changelog_message a meta field
33 if hasattr(self, 'meta_fields'):
34 self.meta_fields.append('changelog_message')
35 else:
36 self.meta_fields = ['changelog_message']
39class CustomFieldsMixin:
40 """
41 Extend a Form to include custom field support.
43 Attributes:
44 model: The model class
45 """
47 model = None
49 def __init__(self, *args, **kwargs):
50 self.custom_fields = {}
51 self.custom_field_groups = {}
53 super().__init__(*args, **kwargs)
55 self._append_customfield_fields()
57 def _get_content_type(self):
58 """
59 Return the ObjectType of the form's model.
60 """
61 if not getattr(self, 'model', None):
62 raise NotImplementedError(_("{class_name} must specify a model class.").format(
63 class_name=self.__class__.__name__
64 ))
65 return ObjectType.objects.get_for_model(self.model)
67 def _get_custom_fields(self, content_type):
68 # Return only custom fields that are not hidden from the UI
69 return [
70 cf for cf in CustomField.objects.get_for_model(content_type.model_class())
71 if cf.ui_editable != CustomFieldUIEditableChoices.HIDDEN
72 ]
74 def _get_form_field(self, customfield):
75 return customfield.to_form_field()
77 def _append_customfield_fields(self):
78 """
79 Append form fields for all CustomFields assigned to this object type.
80 """
81 for customfield in self._get_custom_fields(self._get_content_type()):
82 field_name = f'cf_{customfield.name}'
83 self.fields[field_name] = self._get_form_field(customfield)
85 # Annotate the field in the list of CustomField form fields
86 self.custom_fields[field_name] = customfield
87 if customfield.group_name not in self.custom_field_groups:
88 self.custom_field_groups[customfield.group_name] = []
89 self.custom_field_groups[customfield.group_name].append(field_name)
92class SavedFiltersMixin(forms.Form):
93 """
94 Form mixin for forms that support saved filters.
96 Provides a field for selecting a saved filter,
97 with options limited to those applicable to the form's model.
98 """
100 filter_id = DynamicModelMultipleChoiceField(
101 queryset=SavedFilter.objects.all(),
102 required=False,
103 label=_('Saved Filter'),
104 query_params={
105 'usable': True,
106 },
107 )
109 def __init__(self, *args, **kwargs):
110 super().__init__(*args, **kwargs)
112 # Ensure the underlying <select> has an accessible name even when Tom Select
113 # hides the original element (the visible UI is a JS-built combobox).
114 self.fields['filter_id'].widget.attrs['aria-label'] = _('Saved filter')
116 # Limit saved filters to those applicable to the form's model
117 if hasattr(self, 'model'):
118 object_type = ObjectType.objects.get_for_model(self.model)
119 self.fields['filter_id'].widget.add_query_params({
120 'object_type_id': object_type.pk,
121 })
124class TagsMixin(forms.Form):
125 """
126 Mixin for forms that support tagging.
128 Provides a field for selecting tags,
129 with options limited to those applicable to the form's model.
130 """
132 tags = DynamicModelMultipleChoiceField(
133 queryset=Tag.objects.all(),
134 required=False,
135 label=_('Tags'),
136 )
138 def __init__(self, *args, **kwargs):
139 super().__init__(*args, **kwargs)
141 # Limit tags to those applicable to the object type
142 object_type = ObjectType.objects.get_for_model(self._meta.model)
143 if object_type and hasattr(self.fields['tags'].widget, 'add_query_param'):
144 self.fields['tags'].widget.add_query_param('for_object_type_id', object_type.pk)
147class OwnerMixin(forms.Form):
148 """
149 Mixin for forms which adds ownership fields.
151 Include this mixin in forms for models which
152 support owner and/or owner group assignment.
153 """
155 owner_group = DynamicModelChoiceField(
156 label=_('Owner group'),
157 queryset=OwnerGroup.objects.all(),
158 required=False,
159 null_option='None',
160 initial_params={'members': '$owner'},
161 )
162 owner = DynamicModelChoiceField(
163 queryset=Owner.objects.all(),
164 required=False,
165 query_params={'group_id': '$owner_group'},
166 label=_('Owner'),
167 )
170class OwnerFilterMixin(forms.Form):
171 """
172 Mixin for filterset forms which adds owner and owner group filtering.
174 Include this mixin in filterset forms for models
175 which support owner and/or owner group assignment.
176 """
178 owner_group_id = DynamicModelMultipleChoiceField(
179 queryset=OwnerGroup.objects.all(),
180 required=False,
181 null_option='None',
182 label=_('Owner Group'),
183 )
184 owner_id = DynamicModelMultipleChoiceField(
185 queryset=Owner.objects.all(),
186 required=False,
187 null_option='None',
188 query_params={
189 'group_id': '$owner_group_id'
190 },
191 label=_('Owner'),
192 )