| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185 |
- from django import forms
- from django.contrib.contenttypes.models import ContentType
- from django.db.models import Q
- from django.utils.translation import gettext_lazy as _
- from core.models import ObjectType
- from extras.choices import *
- from extras.models import CustomField, Tag
- from utilities.forms import CSVModelForm
- from utilities.forms.fields import CSVModelMultipleChoiceField, DynamicModelMultipleChoiceField
- from utilities.forms.mixins import CheckLastUpdatedMixin
- from .mixins import CustomFieldsMixin, SavedFiltersMixin, TagsMixin
- __all__ = (
- 'NetBoxModelForm',
- 'NetBoxModelImportForm',
- 'NetBoxModelBulkEditForm',
- 'NetBoxModelFilterSetForm',
- )
- class NetBoxModelForm(CheckLastUpdatedMixin, CustomFieldsMixin, TagsMixin, forms.ModelForm):
- """
- Base form for creating & editing NetBox models. Extends Django's ModelForm to add support for custom fields.
- Attributes:
- fieldsets: An iterable of FieldSets which define a name and set of fields to display per section of
- the rendered form (optional). If not defined, the all fields will be rendered as a single section.
- """
- fieldsets = ()
- def _get_content_type(self):
- return ContentType.objects.get_for_model(self._meta.model)
- def _get_form_field(self, customfield):
- if self.instance.pk:
- form_field = customfield.to_form_field(set_initial=False)
- form_field.initial = self.instance.custom_field_data.get(customfield.name, None)
- return form_field
- return customfield.to_form_field()
- def clean(self):
- # Save custom field data on instance
- for cf_name, customfield in self.custom_fields.items():
- if cf_name not in self.fields:
- # Custom fields may be absent when performing bulk updates via import
- continue
- key = cf_name[3:] # Strip "cf_" from field name
- value = self.cleaned_data.get(cf_name)
- # Convert "empty" values to null
- if value in self.fields[cf_name].empty_values:
- self.instance.custom_field_data[key] = None
- else:
- self.instance.custom_field_data[key] = customfield.serialize(value)
- return super().clean()
- def _post_clean(self):
- """
- Override BaseModelForm's _post_clean() to store many-to-many field values on the model instance.
- """
- self.instance._m2m_values = {}
- for field in self.instance._meta.local_many_to_many:
- if field.name in self.cleaned_data:
- self.instance._m2m_values[field.name] = list(self.cleaned_data[field.name])
- return super()._post_clean()
- class NetBoxModelImportForm(CSVModelForm, NetBoxModelForm):
- """
- Base form for creating a NetBox objects from CSV data. Used for bulk importing.
- """
- id = forms.IntegerField(
- label=_('Id'),
- required=False,
- help_text='Numeric ID of an existing object to update (if not creating a new object)'
- )
- tags = CSVModelMultipleChoiceField(
- label=_('Tags'),
- queryset=Tag.objects.all(),
- required=False,
- to_field_name='slug',
- help_text='Tag slugs separated by commas, encased with double quotes (e.g. "tag1,tag2,tag3")'
- )
- def _get_custom_fields(self, content_type):
- return CustomField.objects.filter(
- object_types=content_type,
- ui_editable=CustomFieldUIEditableChoices.YES
- )
- def _get_form_field(self, customfield):
- return customfield.to_form_field(for_csv_import=True)
- class NetBoxModelBulkEditForm(CustomFieldsMixin, forms.Form):
- """
- Base form for modifying multiple NetBox objects (of the same type) in bulk via the UI. Adds support for custom
- fields and adding/removing tags.
- Attributes:
- fieldsets: An iterable of two-tuples which define a heading and field set to display per section of
- the rendered form (optional). If not defined, the all fields will be rendered as a single section.
- nullable_fields: A list of field names indicating which fields support being set to null/empty
- """
- nullable_fields = ()
- pk = forms.ModelMultipleChoiceField(
- queryset=None, # Set from self.model on init
- widget=forms.MultipleHiddenInput
- )
- add_tags = DynamicModelMultipleChoiceField(
- label=_('Add tags'),
- queryset=Tag.objects.all(),
- required=False
- )
- remove_tags = DynamicModelMultipleChoiceField(
- label=_('Remove tags'),
- queryset=Tag.objects.all(),
- required=False
- )
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.fields['pk'].queryset = self.model.objects.all()
- # Restrict tag fields by model
- object_type = ObjectType.objects.get_for_model(self.model)
- self.fields['add_tags'].widget.add_query_param('for_object_type_id', object_type.pk)
- self.fields['remove_tags'].widget.add_query_param('for_object_type_id', object_type.pk)
- self._extend_nullable_fields()
- def _get_form_field(self, customfield):
- return customfield.to_form_field(set_initial=False, enforce_required=False)
- def _extend_nullable_fields(self):
- nullable_custom_fields = [
- name for name, customfield in self.custom_fields.items()
- if (not customfield.required and customfield.ui_editable == CustomFieldUIEditableChoices.YES)
- ]
- self.nullable_fields = (*self.nullable_fields, *nullable_custom_fields)
- class NetBoxModelFilterSetForm(CustomFieldsMixin, SavedFiltersMixin, forms.Form):
- """
- Base form for FilerSet forms. These are used to filter object lists in the NetBox UI. Note that the
- corresponding FilterSet *must* provide a `q` filter.
- Attributes:
- model: The model class associated with the form
- fieldsets: An iterable of two-tuples which define a heading and field set to display per section of
- the rendered form (optional). If not defined, the all fields will be rendered as a single section.
- selector_fields: An iterable of names of fields to display by default when rendering the form as
- a selector widget
- """
- q = forms.CharField(
- required=False,
- label=_('Search')
- )
- selector_fields = ('filter_id', 'q')
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- # Limit saved filters to those applicable to the form's model
- object_type = ObjectType.objects.get_for_model(self.model)
- self.fields['filter_id'].widget.add_query_params({
- 'object_type_id': object_type.pk,
- })
- def _get_custom_fields(self, content_type):
- return super()._get_custom_fields(content_type).exclude(
- Q(filter_logic=CustomFieldFilterLogicChoices.FILTER_DISABLED) |
- Q(type=CustomFieldTypeChoices.TYPE_JSON)
- )
- def _get_form_field(self, customfield):
- return customfield.to_form_field(set_initial=False, enforce_required=False, enforce_visibility=False)
|