From 14f1a78e5770ad858d241a87264f85a331a4eed9 Mon Sep 17 00:00:00 2001 From: Brian Tiemann Date: Tue, 9 Sep 2025 20:06:57 -0400 Subject: [PATCH 1/3] Ensure default values are populated in create form for multiobject fields --- netbox_custom_objects/field_types.py | 9 ++++--- netbox_custom_objects/views.py | 39 +++++++++++++++++++++++++++- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/netbox_custom_objects/field_types.py b/netbox_custom_objects/field_types.py index d2bca8a..5fe9637 100644 --- a/netbox_custom_objects/field_types.py +++ b/netbox_custom_objects/field_types.py @@ -754,7 +754,8 @@ def get_model_field(self, field, **kwargs): # Extract our custom parameters and keep only Django field parameters field_kwargs = {k: v for k, v in kwargs.items() if not k.startswith('_')} - field_kwargs.update({"default": field.default, "unique": field.unique}) + # Remove default from field_kwargs since ManyToManyField doesn't handle defaults the same way + field_kwargs.update({"unique": field.unique}) is_self_referential = ( content_type.app_label == APP_LABEL @@ -818,6 +819,9 @@ def get_form_field(self, field, for_csv_import=False, **kwargs): # This is a regular NetBox model model = content_type.model_class() + # Don't set initial values here - let Django handle it properly + # Initial values will be set in the form's __init__ method + if for_csv_import: field_class = CSVModelMultipleChoiceField # For CSV import, determine to_field_name from the field configuration @@ -825,7 +829,6 @@ def get_form_field(self, field, for_csv_import=False, **kwargs): return field_class( queryset=model.objects.all(), required=field.required, - # Remove initial=field.default to allow Django to handle instance data properly to_field_name=to_field_name, ) else: @@ -833,7 +836,6 @@ def get_form_field(self, field, for_csv_import=False, **kwargs): return field_class( queryset=model.objects.all(), required=field.required, - # Remove initial=field.default to allow Django to handle instance data properly query_params=( field.related_object_filter if hasattr(field, "related_object_filter") @@ -842,6 +844,7 @@ def get_form_field(self, field, for_csv_import=False, **kwargs): selector=model._meta.app_label != APP_LABEL, ) + def get_filterform_field(self, field, **kwargs): return None diff --git a/netbox_custom_objects/views.py b/netbox_custom_objects/views.py index 3ac9e82..6dd30c2 100644 --- a/netbox_custom_objects/views.py +++ b/netbox_custom_objects/views.py @@ -28,6 +28,7 @@ from . import field_types, filtersets, forms, tables from .models import CustomObject, CustomObjectType, CustomObjectTypeField from extras.choices import CustomFieldTypeChoices +from netbox_custom_objects.constants import APP_LABEL logger = logging.getLogger("netbox_custom_objects.views") @@ -493,12 +494,48 @@ def get_form(self, model): # Create a custom __init__ method to set instance attributes def custom_init(self, *args, **kwargs): - forms.NetBoxModelForm.__init__(self, *args, **kwargs) # Set the grouping info as instance attributes from the outer scope self.custom_object_type_fields = attrs["custom_object_type_fields"] self.custom_object_type_field_groups = attrs[ "custom_object_type_field_groups" ] + + # Handle default values for MultiObject fields BEFORE calling parent __init__ + # This ensures the initial values are set before Django processes the form + instance = kwargs.get('instance', None) + if not instance or not instance.pk: + # Only set defaults for new instances (not when editing existing ones) + for field_name, field_obj in self.custom_object_type_fields.items(): + if field_obj.type == CustomFieldTypeChoices.TYPE_MULTIOBJECT: + if field_obj.default and isinstance(field_obj.default, list): + # Get the related model + content_type = field_obj.related_object_type + if content_type.app_label == APP_LABEL: + # Custom object type + from netbox_custom_objects.models import CustomObjectType + custom_object_type_id = content_type.model.replace("table", "").replace("model", "") + custom_object_type = CustomObjectType.objects.get(pk=custom_object_type_id) + model = custom_object_type.get_model(skip_object_fields=True) + else: + # Regular NetBox model + model = content_type.model_class() + + try: + # Query the database to get the actual objects + initial_objects = model.objects.filter(pk__in=field_obj.default) + # Convert to list of IDs for ModelMultipleChoiceField + initial_ids = list(initial_objects.values_list('pk', flat=True)) + + # Set the initial value in the form's initial data + if 'initial' not in kwargs: + kwargs['initial'] = {} + kwargs['initial'][field_name] = initial_ids + except Exception as e: + # If there's an error, don't set initial values + pass + + # Now call the parent __init__ with the modified kwargs + forms.NetBoxModelForm.__init__(self, *args, **kwargs) # Create a custom save method to properly handle M2M fields def custom_save(self, commit=True): From 1ee40446af4c0d244c66d432896a91ec6d08a615 Mon Sep 17 00:00:00 2001 From: Brian Tiemann Date: Tue, 9 Sep 2025 20:24:22 -0400 Subject: [PATCH 2/3] Ruff fixes --- netbox_custom_objects/field_types.py | 1 - netbox_custom_objects/views.py | 10 +++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/netbox_custom_objects/field_types.py b/netbox_custom_objects/field_types.py index 5fe9637..d96322a 100644 --- a/netbox_custom_objects/field_types.py +++ b/netbox_custom_objects/field_types.py @@ -844,7 +844,6 @@ def get_form_field(self, field, for_csv_import=False, **kwargs): selector=model._meta.app_label != APP_LABEL, ) - def get_filterform_field(self, field, **kwargs): return None diff --git a/netbox_custom_objects/views.py b/netbox_custom_objects/views.py index 6dd30c2..b89026b 100644 --- a/netbox_custom_objects/views.py +++ b/netbox_custom_objects/views.py @@ -499,7 +499,7 @@ def custom_init(self, *args, **kwargs): self.custom_object_type_field_groups = attrs[ "custom_object_type_field_groups" ] - + # Handle default values for MultiObject fields BEFORE calling parent __init__ # This ensures the initial values are set before Django processes the form instance = kwargs.get('instance', None) @@ -519,21 +519,21 @@ def custom_init(self, *args, **kwargs): else: # Regular NetBox model model = content_type.model_class() - + try: # Query the database to get the actual objects initial_objects = model.objects.filter(pk__in=field_obj.default) # Convert to list of IDs for ModelMultipleChoiceField initial_ids = list(initial_objects.values_list('pk', flat=True)) - + # Set the initial value in the form's initial data if 'initial' not in kwargs: kwargs['initial'] = {} kwargs['initial'][field_name] = initial_ids - except Exception as e: + except Exception: # If there's an error, don't set initial values pass - + # Now call the parent __init__ with the modified kwargs forms.NetBoxModelForm.__init__(self, *args, **kwargs) From a3820b94a6cc5b8646279b9d4e0e9230aa36b8a9 Mon Sep 17 00:00:00 2001 From: Brian Tiemann Date: Tue, 9 Sep 2025 20:26:34 -0400 Subject: [PATCH 3/3] Remove comments --- netbox_custom_objects/field_types.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/netbox_custom_objects/field_types.py b/netbox_custom_objects/field_types.py index d96322a..981ce36 100644 --- a/netbox_custom_objects/field_types.py +++ b/netbox_custom_objects/field_types.py @@ -819,9 +819,6 @@ def get_form_field(self, field, for_csv_import=False, **kwargs): # This is a regular NetBox model model = content_type.model_class() - # Don't set initial values here - let Django handle it properly - # Initial values will be set in the form's __init__ method - if for_csv_import: field_class = CSVModelMultipleChoiceField # For CSV import, determine to_field_name from the field configuration