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.
This commit is contained in:
Marko Hauptvogel 2025-08-22 09:53:52 +02:00
parent 18480668e0
commit fa7b4d8978

View File

@ -328,6 +328,13 @@ class BulkImportView(GetReturnURLMixin, BaseMultiObjectView):
def get_required_permission(self): def get_required_permission(self):
return get_permission_for_model(self.queryset.model, 'add') 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): 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 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() raise AbortTransaction()
related_objects = list(enumerate(related_objects)) 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 = [] related_obj_pks = []
for i, rel_obj_data in related_objects: for i, rel_obj_data in related_objects:
if not isinstance(rel_obj_data, dict): # TODO isinstance(MutableMapping)? if not isinstance(rel_obj_data, dict): # TODO isinstance(MutableMapping)?