From 450f68c40e449fd9275b3d501d0202f808ff6260 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Fri, 4 Nov 2022 10:26:02 -0400 Subject: [PATCH] Introduce CSVModelMultipleChoiceField for CSV import tag assignment --- netbox/netbox/forms/base.py | 18 +++++------------- netbox/utilities/forms/fields/csv.py | 16 +++++++++++++++- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/netbox/netbox/forms/base.py b/netbox/netbox/forms/base.py index ee97261af..4a4368a65 100644 --- a/netbox/netbox/forms/base.py +++ b/netbox/netbox/forms/base.py @@ -6,9 +6,8 @@ from django.db.models import Q from extras.choices import CustomFieldFilterLogicChoices, CustomFieldTypeChoices, CustomFieldVisibilityChoices from extras.forms.mixins import CustomFieldsMixin, SavedFiltersMixin from extras.models import CustomField, Tag -from taggit.forms import TagField from utilities.forms import BootstrapMixin, CSVModelForm -from utilities.forms.fields import DynamicModelMultipleChoiceField +from utilities.forms.fields import CSVModelMultipleChoiceField, DynamicModelMultipleChoiceField __all__ = ( 'NetBoxModelForm', @@ -63,9 +62,11 @@ class NetBoxModelCSVForm(CSVModelForm, NetBoxModelForm): """ Base form for creating a NetBox objects from CSV data. Used for bulk importing. """ - tags = TagField( + tags = CSVModelMultipleChoiceField( + queryset=Tag.objects.all(), required=False, - help_text='Tags (as quoted string: "tag1,tag2")' + 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): @@ -76,15 +77,6 @@ class NetBoxModelCSVForm(CSVModelForm, NetBoxModelForm): def _get_form_field(self, customfield): return customfield.to_form_field(for_csv_import=True) - def clean_tags(self): - data = self.cleaned_data['tags'] - existing_tags = Tag.objects.values_list('slug', flat=True) - for tag in data: - if tag.strip().lower() not in existing_tags: - raise ValidationError(f"Unknown tag: {tag}") - - return data - class NetBoxModelBulkEditForm(BootstrapMixin, CustomFieldsMixin, forms.Form): """ diff --git a/netbox/utilities/forms/fields/csv.py b/netbox/utilities/forms/fields/csv.py index 275c8084c..59765cae8 100644 --- a/netbox/utilities/forms/fields/csv.py +++ b/netbox/utilities/forms/fields/csv.py @@ -16,6 +16,7 @@ __all__ = ( 'CSVDataField', 'CSVFileField', 'CSVModelChoiceField', + 'CSVModelMultipleChoiceField', 'CSVMultipleChoiceField', 'CSVMultipleContentTypeField', 'CSVTypedChoiceField', @@ -142,7 +143,7 @@ class CSVModelChoiceField(forms.ModelChoiceField): Extends Django's `ModelChoiceField` to provide additional validation for CSV values. """ default_error_messages = { - 'invalid_choice': 'Object not found.', + 'invalid_choice': 'Object not found: %(value)s', } def to_python(self, value): @@ -154,6 +155,19 @@ class CSVModelChoiceField(forms.ModelChoiceField): ) +class CSVModelMultipleChoiceField(forms.ModelMultipleChoiceField): + """ + Extends Django's `ModelMultipleChoiceField` to support comma-separated values. + """ + default_error_messages = { + 'invalid_choice': 'Object not found: %(value)s', + } + + def clean(self, value): + value = value.split(',') if value else [] + return super().clean(value) + + class CSVContentTypeField(CSVModelChoiceField): """ CSV field for referencing a single content type, in the form `.`.