Add message support for bulk import/update
Some checks are pending
CI / build (20.x, 3.10) (push) Waiting to run
CI / build (20.x, 3.11) (push) Waiting to run
CI / build (20.x, 3.12) (push) Waiting to run

This commit is contained in:
Jeremy Stretch 2025-07-22 14:49:55 -04:00
parent 0514bb4e60
commit f600429b7e
4 changed files with 38 additions and 25 deletions

View File

@ -15,6 +15,9 @@ __all__ = (
class ChangeLoggingMixin(forms.Form): class ChangeLoggingMixin(forms.Form):
"""
Adds an optional field for recording a message on the resulting changelog record(s).
"""
changelog_message = forms.CharField( changelog_message = forms.CharField(
required=False, required=False,
max_length=200 max_length=200

View File

@ -424,7 +424,6 @@ class BulkImportView(GetReturnURLMixin, BaseMultiObjectView):
} if prefetch_ids else {} } if prefetch_ids else {}
for i, record in enumerate(records, start=1): for i, record in enumerate(records, start=1):
instance = None
object_id = int(record.pop('id')) if record.get('id') else None object_id = int(record.pop('id')) if record.get('id') else None
# Determine whether this object is being created or updated # Determine whether this object is being created or updated
@ -440,6 +439,8 @@ class BulkImportView(GetReturnURLMixin, BaseMultiObjectView):
instance.snapshot() instance.snapshot()
else: else:
instance = self.queryset.model()
# For newly created objects, apply any default custom field values # For newly created objects, apply any default custom field values
custom_fields = CustomField.objects.filter( custom_fields = CustomField.objects.filter(
object_types=ContentType.objects.get_for_model(self.queryset.model), object_types=ContentType.objects.get_for_model(self.queryset.model),
@ -450,6 +451,9 @@ class BulkImportView(GetReturnURLMixin, BaseMultiObjectView):
if field_name not in record: if field_name not in record:
record[field_name] = cf.default record[field_name] = cf.default
# Record changelog message (if any)
instance._changelog_message = form.cleaned_data.pop('changelog_message', '')
# Instantiate the model form for the object # Instantiate the model form for the object
model_form_kwargs = { model_form_kwargs = {
'data': record, 'data': record,

View File

@ -42,32 +42,31 @@ Context:
{# Data Import Form #} {# Data Import Form #}
<div class="tab-pane show active" id="import-form" role="tabpanel" aria-labelledby="import-form-tab"> <div class="tab-pane show active" id="import-form" role="tabpanel" aria-labelledby="import-form-tab">
<div class="row"> <div class="col col-md-12 col-lg-10 offset-lg-1">
<div class="col col-md-12 col-lg-10 offset-lg-1"> <form action="" method="post" enctype="multipart/form-data" class="form">
<form action="" method="post" enctype="multipart/form-data" class="form"> {% csrf_token %}
{% csrf_token %} <input type="hidden" name="import_method" value="direct" />
<input type="hidden" name="import_method" value="direct" />
{# Form fields #} {# Form fields #}
{% render_field form.data %} {% render_field form.data %}
{% render_field form.format %} {% render_field form.format %}
{% render_field form.csv_delimiter %} {% render_field form.csv_delimiter %}
{# Meta fields #} {# Meta fields #}
<div class="bg-primary-subtle border border-primary rounded-1 pt-3 mb-3"> <div class="bg-primary-subtle border border-primary rounded-1 pt-3 mb-3">
{% render_field form.background_job %} {% render_field form.changelog_message %}
{% render_field form.background_job %}
</div>
<div class="form-group">
<div class="col col-md-12 text-end">
{% if return_url %}
<a href="{{ return_url }}" class="btn btn-outline-secondary">{% trans "Cancel" %}</a>
{% endif %}
<button type="submit" name="data_submit" class="btn btn-primary">{% trans "Submit" %}</button>
</div> </div>
</div>
<div class="form-group"> </form>
<div class="col col-md-12 text-end">
{% if return_url %}
<a href="{{ return_url }}" class="btn btn-outline-secondary">{% trans "Cancel" %}</a>
{% endif %}
<button type="submit" name="data_submit" class="btn btn-primary">{% trans "Submit" %}</button>
</div>
</div>
</form>
</div>
</div> </div>
</div> </div>
@ -83,6 +82,11 @@ Context:
{% render_field form.format %} {% render_field form.format %}
{% render_field form.csv_delimiter %} {% render_field form.csv_delimiter %}
{# Meta fields #}
<div class="bg-primary-subtle border border-primary rounded-1 pt-3 mb-3">
{% render_field form.changelog_message %}
</div>
<div class="form-group"> <div class="form-group">
<div class="col col-md-12 text-end"> <div class="col col-md-12 text-end">
{% if return_url %} {% if return_url %}
@ -110,6 +114,7 @@ Context:
{# Meta fields #} {# Meta fields #}
<div class="bg-primary-subtle border border-primary rounded-1 pt-3 mb-3"> <div class="bg-primary-subtle border border-primary rounded-1 pt-3 mb-3">
{% render_field form.changelog_message %}
{% render_field form.background_job %} {% render_field form.background_job %}
</div> </div>

View File

@ -8,12 +8,13 @@ from django.utils.translation import gettext as _
from core.forms.mixins import SyncedDataMixin from core.forms.mixins import SyncedDataMixin
from netbox.choices import CSVDelimiterChoices, ImportFormatChoices, ImportMethodChoices from netbox.choices import CSVDelimiterChoices, ImportFormatChoices, ImportMethodChoices
from netbox.forms.mixins import ChangeLoggingMixin
from utilities.constants import CSV_DELIMITERS from utilities.constants import CSV_DELIMITERS
from utilities.forms.mixins import BackgroundJobMixin from utilities.forms.mixins import BackgroundJobMixin
from utilities.forms.utils import parse_csv from utilities.forms.utils import parse_csv
class BulkImportForm(BackgroundJobMixin, SyncedDataMixin, forms.Form): class BulkImportForm(ChangeLoggingMixin, BackgroundJobMixin, SyncedDataMixin, forms.Form):
import_method = forms.ChoiceField( import_method = forms.ChoiceField(
choices=ImportMethodChoices, choices=ImportMethodChoices,
required=False required=False