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

1from django import forms 

2from django.utils.translation import gettext as _ 

3 

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 

9 

10__all__ = ( 

11 'ChangelogMessageMixin', 

12 'CustomFieldsMixin', 

13 'OwnerFilterMixin', 

14 'OwnerMixin', 

15 'SavedFiltersMixin', 

16 'TagsMixin', 

17) 

18 

19 

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 ) 

28 

29 def __init__(self, *args, **kwargs): 

30 super().__init__(*args, **kwargs) 

31 

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'] 

37 

38 

39class CustomFieldsMixin: 

40 """ 

41 Extend a Form to include custom field support. 

42 

43 Attributes: 

44 model: The model class 

45 """ 

46 

47 model = None 

48 

49 def __init__(self, *args, **kwargs): 

50 self.custom_fields = {} 

51 self.custom_field_groups = {} 

52 

53 super().__init__(*args, **kwargs) 

54 

55 self._append_customfield_fields() 

56 

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) 

66 

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 ] 

73 

74 def _get_form_field(self, customfield): 

75 return customfield.to_form_field() 

76 

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) 

84 

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) 

90 

91 

92class SavedFiltersMixin(forms.Form): 

93 """ 

94 Form mixin for forms that support saved filters. 

95 

96 Provides a field for selecting a saved filter, 

97 with options limited to those applicable to the form's model. 

98 """ 

99 

100 filter_id = DynamicModelMultipleChoiceField( 

101 queryset=SavedFilter.objects.all(), 

102 required=False, 

103 label=_('Saved Filter'), 

104 query_params={ 

105 'usable': True, 

106 }, 

107 ) 

108 

109 def __init__(self, *args, **kwargs): 

110 super().__init__(*args, **kwargs) 

111 

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') 

115 

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 }) 

122 

123 

124class TagsMixin(forms.Form): 

125 """ 

126 Mixin for forms that support tagging. 

127 

128 Provides a field for selecting tags, 

129 with options limited to those applicable to the form's model. 

130 """ 

131 

132 tags = DynamicModelMultipleChoiceField( 

133 queryset=Tag.objects.all(), 

134 required=False, 

135 label=_('Tags'), 

136 ) 

137 

138 def __init__(self, *args, **kwargs): 

139 super().__init__(*args, **kwargs) 

140 

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) 

145 

146 

147class OwnerMixin(forms.Form): 

148 """ 

149 Mixin for forms which adds ownership fields. 

150 

151 Include this mixin in forms for models which 

152 support owner and/or owner group assignment. 

153 """ 

154 

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 ) 

168 

169 

170class OwnerFilterMixin(forms.Form): 

171 """ 

172 Mixin for filterset forms which adds owner and owner group filtering. 

173 

174 Include this mixin in filterset forms for models 

175 which support owner and/or owner group assignment. 

176 """ 

177 

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 )