From fa7b4d8978b0e20e59e6ba478db06a4cd230428c Mon Sep 17 00:00:00 2001 From: Marko Hauptvogel Date: Fri, 22 Aug 2025 09:53:52 +0200 Subject: [PATCH] Add prep_related_object_list hook Related objects are saved in the same order as they are defined in the input by the user. So related objects with a dependency on another need to be saved after the related object they depend on. Which means that when required, the order needs to be changed. The related object lists are not part of the actual form, and the individual related-object forms only cover the elements themselves. So while the re-order should be done at the form-level during the clean- step, there is no form covering the lists to use for that. Plus should all errors be reported with their original location/index and not with the fixed/resorted one, because these are not useful and even confusing for the user. So the sorting should be done as late as possible, ideally just before the save. This adds a new `prep_related_object_list` hook, which gets passed each list of related objects. This should only be used for modifications to the list (aka order). Any changes to the individual objects should still be done using the existing `prep_related_object_data` hook. The passed list is not the plain list, but an enumerated variant. This is needed to preserve the original index of the elements independently from the actual (possibly changed) order the list, so any later detected errors (for example by the elements form validation) can accurately be reported back to the user. --- netbox/netbox/views/generic/bulk_views.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/netbox/netbox/views/generic/bulk_views.py b/netbox/netbox/views/generic/bulk_views.py index b89b1d3a0..b9192b021 100644 --- a/netbox/netbox/views/generic/bulk_views.py +++ b/netbox/netbox/views/generic/bulk_views.py @@ -328,6 +328,13 @@ class BulkImportView(GetReturnURLMixin, BaseMultiObjectView): def get_required_permission(self): return get_permission_for_model(self.queryset.model, 'add') + def prep_related_object_list(self, field_name, enumerated_list): + """ + Hook to modify the enumerated list of related objects before it's passed to the related object form (for + example, to change the order). + """ + pass # TODO keep in-place only, or return modified list? + def prep_related_object_data(self, parent, data): """ Hook to modify the data for related objects before it's passed to the related object form (for example, to @@ -369,6 +376,13 @@ class BulkImportView(GetReturnURLMixin, BaseMultiObjectView): raise AbortTransaction() related_objects = list(enumerate(related_objects)) + try: + self.prep_related_object_list(field_name, related_objects) + except ValidationError as e: + for message in e.messages: + import_form.add_error(None, f"{field_name}: {message}") + raise AbortTransaction() + related_obj_pks = [] for i, rel_obj_data in related_objects: if not isinstance(rel_obj_data, dict): # TODO isinstance(MutableMapping)?