diff --git a/netbox/ipam/forms.py b/netbox/ipam/forms.py
index c9701f60b..6aa5347da 100644
--- a/netbox/ipam/forms.py
+++ b/netbox/ipam/forms.py
@@ -418,12 +418,15 @@ class IPAddressForm(BootstrapMixin, ReturnURLForm, CustomFieldForm):
self.fields['nat_inside'].choices = []
-class IPAddressBulkAddForm(BootstrapMixin, forms.Form):
- address = ExpandableIPAddressField()
+class IPAddressBulkAddForm(BootstrapMixin, CustomFieldForm):
+ address_pattern = ExpandableIPAddressField(label='Address Pattern')
vrf = forms.ModelChoiceField(queryset=VRF.objects.all(), required=False, label='VRF', empty_label='Global')
- tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False)
- status = forms.ChoiceField(choices=IPADDRESS_STATUS_CHOICES)
- description = forms.CharField(max_length=100, required=False)
+
+ pattern_map = ('address_pattern', 'address')
+
+ class Meta:
+ model = IPAddress
+ fields = ['address_pattern', 'vrf', 'tenant', 'status', 'description']
class IPAddressAssignForm(BootstrapMixin, forms.Form):
diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py
index 0c53b8bc9..0754e0fa7 100644
--- a/netbox/ipam/views.py
+++ b/netbox/ipam/views.py
@@ -588,7 +588,7 @@ class IPAddressDeleteView(PermissionRequiredMixin, ObjectDeleteView):
class IPAddressBulkAddView(PermissionRequiredMixin, BulkAddView):
permission_required = 'ipam.add_ipaddress'
form = forms.IPAddressBulkAddForm
- model = IPAddress
+ model_form = forms.IPAddressForm
template_name = 'ipam/ipaddress_bulk_add.html'
default_return_url = 'ipam:ipaddress_list'
diff --git a/netbox/templates/ipam/ipaddress_bulk_add.html b/netbox/templates/ipam/ipaddress_bulk_add.html
index 1599ee900..d53f73bd5 100644
--- a/netbox/templates/ipam/ipaddress_bulk_add.html
+++ b/netbox/templates/ipam/ipaddress_bulk_add.html
@@ -10,13 +10,21 @@
{% block form %}
-
IP Address
+
IP Addresses
- {% render_field form.address %}
+ {% render_field form.address_pattern %}
{% render_field form.vrf %}
{% render_field form.tenant %}
{% render_field form.status %}
{% render_field form.description %}
+ {% if form.custom_fields %}
+
+
Custom Fields
+
+ {% render_custom_fields form %}
+
+
+ {% endif %}
{% endblock %}
diff --git a/netbox/utilities/views.py b/netbox/utilities/views.py
index 0f867e16b..71e385a6e 100644
--- a/netbox/utilities/views.py
+++ b/netbox/utilities/views.py
@@ -296,12 +296,12 @@ class BulkAddView(View):
Create new objects in bulk.
form: Form class
- model: The model of the objects being created
+ model_form: The ModelForm used to create individual objects
template_name: The name of the template
default_return_url: Name of the URL to which the user is redirected after creating the objects
"""
form = None
- model = None
+ model_form = None
template_name = None
default_return_url = 'home'
@@ -310,47 +310,44 @@ class BulkAddView(View):
form = self.form()
return render(request, self.template_name, {
- 'obj_type': self.model._meta.verbose_name,
+ 'obj_type': self.model_form._meta.model._meta.verbose_name,
'form': form,
'return_url': reverse(self.default_return_url),
})
def post(self, request):
+ model = self.model_form._meta.model
form = self.form(request.POST)
if form.is_valid():
- # The first field will be used as the pattern
- field_names = list(form.fields.keys())
- pattern_field = field_names[0]
+ # Read the pattern field and target from the form's pattern_map
+ pattern_field, pattern_target = form.pattern_map
pattern = form.cleaned_data[pattern_field]
-
- # All other fields will be copied as object attributes
- kwargs = {k: form.cleaned_data[k] for k in field_names[1:]}
+ model_form_data = form.cleaned_data
new_objs = []
try:
with transaction.atomic():
for value in pattern:
- obj = self.model(**kwargs)
- setattr(obj, pattern_field, value)
- obj.full_clean()
- obj.save()
+ model_form_data[pattern_target] = value
+ model_form = self.model_form(model_form_data)
+ obj = model_form.save()
new_objs.append(obj)
except ValidationError as e:
form.add_error(None, e)
if not form.errors:
- msg = u"Added {} {}".format(len(new_objs), self.model._meta.verbose_name_plural)
+ msg = u"Added {} {}".format(len(new_objs), model._meta.verbose_name_plural)
messages.success(request, msg)
- UserAction.objects.log_bulk_create(request.user, ContentType.objects.get_for_model(self.model), msg)
+ UserAction.objects.log_bulk_create(request.user, ContentType.objects.get_for_model(model), msg)
if '_addanother' in request.POST:
return redirect(request.path)
return redirect(self.default_return_url)
return render(request, self.template_name, {
'form': form,
- 'obj_type': self.model._meta.verbose_name,
+ 'obj_type': model._meta.verbose_name,
'return_url': reverse(self.default_return_url),
})