mirror of
https://github.com/netbox-community/netbox.git
synced 2026-04-11 19:49:49 +02:00
feat(ipam): Add changelog message support to bulk Prefix/IP creation
Extend bulk add forms for Prefix and IPAddress to support changelog messages. Switch IPAddressBulkAddForm to PrimaryModelForm base, update field ordering, consolidate template rendering, and add test coverage. Fixes #21780
This commit is contained in:
@@ -483,7 +483,7 @@ class IPAddressForm(TenancyForm, PrimaryModelForm):
|
||||
return ipaddress
|
||||
|
||||
|
||||
class IPAddressBulkAddForm(TenancyForm, NetBoxModelForm):
|
||||
class IPAddressBulkAddForm(TenancyForm, PrimaryModelForm):
|
||||
vrf = DynamicModelChoiceField(
|
||||
queryset=VRF.objects.all(),
|
||||
required=False,
|
||||
@@ -498,7 +498,8 @@ class IPAddressBulkAddForm(TenancyForm, NetBoxModelForm):
|
||||
class Meta:
|
||||
model = IPAddress
|
||||
fields = [
|
||||
'address', 'vrf', 'status', 'role', 'dns_name', 'description', 'tenant_group', 'tenant', 'tags',
|
||||
'address', 'vrf', 'status', 'role', 'dns_name', 'tenant_group', 'tenant', 'description', 'owner',
|
||||
'comments', 'tags',
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,8 @@ from django.test import override_settings
|
||||
from django.urls import reverse
|
||||
from netaddr import IPNetwork
|
||||
|
||||
from core.models import ObjectType
|
||||
from core.choices import ObjectChangeActionChoices
|
||||
from core.models import ObjectChange, ObjectType
|
||||
from dcim.constants import InterfaceTypeChoices
|
||||
from dcim.models import Device, DeviceRole, DeviceType, Interface, Manufacturer, Site
|
||||
from ipam.choices import *
|
||||
@@ -543,6 +544,37 @@ class PrefixTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
f'Expected prefix {prefix_str} was not created'
|
||||
)
|
||||
|
||||
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||
def test_bulk_add_prefixes_with_changelog_message(self):
|
||||
obj_perm = ObjectPermission(name='Test permission', actions=['add'])
|
||||
obj_perm.save()
|
||||
obj_perm.users.add(self.user)
|
||||
obj_perm.object_types.add(ObjectType.objects.get_for_model(Prefix))
|
||||
|
||||
changelog_message = 'Bulk-created prefixes'
|
||||
prefixes = [IPNetwork(f'198.18.{i}.0/24') for i in range(3)]
|
||||
url = reverse('ipam:prefix_bulk_add')
|
||||
data = {
|
||||
'pattern': '198.18.[0-2].0/24',
|
||||
'status': PrefixStatusChoices.STATUS_ACTIVE,
|
||||
'changelog_message': changelog_message,
|
||||
}
|
||||
|
||||
response = self.client.post(url, data)
|
||||
self.assertHttpStatus(response, 302)
|
||||
|
||||
created_prefixes = list(Prefix.objects.filter(prefix__in=prefixes))
|
||||
self.assertEqual(len(created_prefixes), len(prefixes))
|
||||
|
||||
objectchanges = ObjectChange.objects.filter(
|
||||
action=ObjectChangeActionChoices.ACTION_CREATE,
|
||||
changed_object_type=ContentType.objects.get_for_model(Prefix),
|
||||
changed_object_id__in=[obj.pk for obj in created_prefixes],
|
||||
)
|
||||
self.assertEqual(objectchanges.count(), len(prefixes))
|
||||
for objectchange in objectchanges:
|
||||
self.assertEqual(objectchange.message, changelog_message)
|
||||
|
||||
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||
def test_prefix_prefixes(self):
|
||||
prefixes = (
|
||||
@@ -908,6 +940,39 @@ class IPAddressTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
'description': 'New description',
|
||||
}
|
||||
|
||||
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||
def test_bulk_add_ipaddresses_with_changelog_message(self):
|
||||
obj_perm = ObjectPermission(name='Test permission', actions=['add'])
|
||||
obj_perm.save()
|
||||
obj_perm.users.add(self.user)
|
||||
obj_perm.object_types.add(ObjectType.objects.get_for_model(IPAddress))
|
||||
|
||||
vrf = VRF.objects.get(name='VRF 1')
|
||||
changelog_message = 'Bulk-created IP addresses'
|
||||
addresses = [IPNetwork(f'198.51.100.{i}/24') for i in range(10, 13)]
|
||||
url = reverse('ipam:ipaddress_bulk_add')
|
||||
data = {
|
||||
'pattern': '198.51.100.[10-12]/24',
|
||||
'vrf': vrf.pk,
|
||||
'status': IPAddressStatusChoices.STATUS_ACTIVE,
|
||||
'changelog_message': changelog_message,
|
||||
}
|
||||
|
||||
response = self.client.post(url, data)
|
||||
self.assertHttpStatus(response, 302)
|
||||
|
||||
created_addresses = list(IPAddress.objects.filter(address__in=addresses, vrf=vrf))
|
||||
self.assertEqual(len(created_addresses), len(addresses))
|
||||
|
||||
objectchanges = ObjectChange.objects.filter(
|
||||
action=ObjectChangeActionChoices.ACTION_CREATE,
|
||||
changed_object_type=ContentType.objects.get_for_model(IPAddress),
|
||||
changed_object_id__in=[obj.pk for obj in created_addresses],
|
||||
)
|
||||
self.assertEqual(objectchanges.count(), len(addresses))
|
||||
for objectchange in objectchanges:
|
||||
self.assertEqual(objectchange.message, changelog_message)
|
||||
|
||||
|
||||
class FHRPGroupTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
model = FHRPGroup
|
||||
|
||||
@@ -243,6 +243,7 @@ class BulkCreateView(GetReturnURLMixin, BaseMultiObjectView):
|
||||
|
||||
# Validate each new object independently.
|
||||
if model_form.is_valid():
|
||||
model_form.instance._changelog_message = model_form.cleaned_data.get('changelog_message', '')
|
||||
obj = model_form.save()
|
||||
new_objects.append(obj)
|
||||
else:
|
||||
|
||||
@@ -21,31 +21,14 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block pre_form_fields %}
|
||||
<div class="field-group my-5">
|
||||
<div class="row">
|
||||
<h2 class="col-9 offset-3">{% trans "Pattern" %}</h2>
|
||||
</div>
|
||||
{% render_field form.pattern %}
|
||||
<div class="field-group my-5">
|
||||
<div class="row">
|
||||
<h2 class="col-9 offset-3">{% trans "Pattern" %}</h2>
|
||||
</div>
|
||||
{% render_field form.pattern %}
|
||||
</div>
|
||||
{% endblock pre_form_fields %}
|
||||
|
||||
{% block form %}
|
||||
{% if model_form.fieldsets %}
|
||||
{% for fieldset in model_form.fieldsets %}
|
||||
{% render_fieldset model_form fieldset %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div class="field-group my-5">
|
||||
{% render_form model_form %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if model_form.custom_fields %}
|
||||
<div class="field-group my-5">
|
||||
<div class="row">
|
||||
<h2 class="col-9 offset-3">{% trans "Custom Fields" %}</h2>
|
||||
</div>
|
||||
{% render_custom_fields model_form %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% include 'htmx/form.html' with form=model_form %}
|
||||
{% endblock form %}
|
||||
|
||||
@@ -1,22 +1 @@
|
||||
{% load helpers %}
|
||||
{% load form_helpers %}
|
||||
{% load i18n %}
|
||||
|
||||
{% if model_form.fieldsets %}
|
||||
{% for fieldset in model_form.fieldsets %}
|
||||
{% render_fieldset model_form fieldset %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div class="field-group my-5">
|
||||
{% render_form model_form %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if model_form.custom_fields %}
|
||||
<div class="field-group my-5">
|
||||
<div class="row">
|
||||
<h2 class="col-9 offset-3">{% trans "Custom Fields" %}</h2>
|
||||
</div>
|
||||
{% render_custom_fields model_form %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% include 'htmx/form.html' with form=model_form %}
|
||||
|
||||
Reference in New Issue
Block a user