From f7b3dad29cc9134300064541471309f28e2b44a2 Mon Sep 17 00:00:00 2001 From: Arthur Date: Tue, 25 Apr 2023 08:07:09 -0700 Subject: [PATCH] 11617 update header processing for related fields --- netbox/netbox/views/generic/bulk_views.py | 9 ++++-- netbox/utilities/forms/utils.py | 39 ++++++++++++++--------- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/netbox/netbox/views/generic/bulk_views.py b/netbox/netbox/views/generic/bulk_views.py index ce2d79439..0d670eb77 100644 --- a/netbox/netbox/views/generic/bulk_views.py +++ b/netbox/netbox/views/generic/bulk_views.py @@ -21,7 +21,7 @@ from utilities.error_handlers import handle_protectederror from utilities.exceptions import AbortRequest, AbortTransaction, PermissionsViolation from utilities.forms import BulkRenameForm, ConfirmationForm, restrict_form_fields from utilities.forms.bulk_import import BulkImportForm -from utilities.forms.utils import validate_import_headers +from utilities.forms.utils import headers_to_dict, validate_import_headers from utilities.htmx import is_embedded from utilities.htmx import is_htmx from utilities.permissions import get_permission_for_model @@ -393,8 +393,10 @@ class BulkImportView(GetReturnURLMixin, BaseMultiObjectView): 'data': record, 'instance': instance, } + headers = None if hasattr(form, '_csv_headers'): - model_form_kwargs['headers'] = form._csv_headers # Add CSV headers + headers = form._csv_headers + model_form_kwargs['headers'] = headers # Add CSV headers model_form = self.model_form(**model_form_kwargs) # validate the fields (required fields are present and no unknown fields) @@ -402,7 +404,8 @@ class BulkImportView(GetReturnURLMixin, BaseMultiObjectView): required_fields = [ name for name, field in form_fields.items() if field.required ] - headers = list(record.keys()) + if not headers: + headers = headers_to_dict(list(record.keys())) validate_import_headers(headers, form_fields, required_fields) # When updating, omit all form fields other than those specified in the record. (No diff --git a/netbox/utilities/forms/utils.py b/netbox/utilities/forms/utils.py index 5f2c86999..d92635e02 100644 --- a/netbox/utilities/forms/utils.py +++ b/netbox/utilities/forms/utils.py @@ -205,6 +205,28 @@ def restrict_form_fields(form, user, action='view'): field.queryset = field.queryset.restrict(user, action) +def headers_to_dict(headers): + """ + Create a dictionary mapping each header to an optional "to" field specifying how + the related object is being referenced. For example, importing a Device might use a + `site.slug` header, to indicate the related site is being referenced by its slug. + """ + header_dict = {} + for header in headers: + header = header.strip() + if '.' in header: + field, to_field = header.split('.', 1) + if field in headers: + raise forms.ValidationError(f'Duplicate or conflicting column header for "{field}"') + header_dict[field] = to_field + else: + if header in headers: + raise forms.ValidationError(f'Duplicate or conflicting column header for "{header}"') + header_dict[header] = None + + return header_dict + + def parse_csv(reader): """ Parse a csv_reader object into a headers dictionary and a list of records dictionaries. Raise an error @@ -213,21 +235,8 @@ def parse_csv(reader): records = [] headers = {} - # Consume the first line of CSV data as column headers. Create a dictionary mapping each header to an optional - # "to" field specifying how the related object is being referenced. For example, importing a Device might use a - # `site.slug` header, to indicate the related site is being referenced by its slug. - - for header in next(reader): - header = header.strip() - if '.' in header: - field, to_field = header.split('.', 1) - if field in headers: - raise forms.ValidationError(f'Duplicate or conflicting column header for "{field}"') - headers[field] = to_field - else: - if header in headers: - raise forms.ValidationError(f'Duplicate or conflicting column header for "{header}"') - headers[header] = None + # Consume the first line of CSV data as column headers. + headers = headers_to_dict(list(reader)) # Parse CSV rows into a list of dictionaries mapped from the column headers. for i, row in enumerate(reader, start=1):