From 9fd9719d0bd2e503148655b8a7299c0cbfae039c Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 20 Dec 2016 15:39:22 -0500 Subject: [PATCH] Closes #181: Implemented support for bulk IP address creation --- netbox/ipam/forms.py | 11 +++- netbox/ipam/urls.py | 1 + netbox/ipam/views.py | 10 +++- .../ipam/inc/ipadress_edit_header.html | 4 ++ netbox/templates/ipam/ipaddress_bulk_add.html | 22 ++++++++ netbox/templates/ipam/ipaddress_edit.html | 6 +++ netbox/templates/utilities/obj_edit.html | 7 +-- netbox/utilities/views.py | 53 ++++++++++++++++++- 8 files changed, 106 insertions(+), 8 deletions(-) create mode 100644 netbox/templates/ipam/inc/ipadress_edit_header.html create mode 100644 netbox/templates/ipam/ipaddress_bulk_add.html diff --git a/netbox/ipam/forms.py b/netbox/ipam/forms.py index 9afb37092..d31488f46 100644 --- a/netbox/ipam/forms.py +++ b/netbox/ipam/forms.py @@ -5,7 +5,8 @@ from dcim.models import Site, Rack, Device, Interface from extras.forms import CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm from tenancy.models import Tenant from utilities.forms import ( - APISelect, BootstrapMixin, CSVDataField, BulkImportForm, FilterChoiceField, Livesearch, SlugField, add_blank_choice, + APISelect, BootstrapMixin, BulkImportForm, CSVDataField, ExpandableIPAddressField, FilterChoiceField, Livesearch, + SlugField, add_blank_choice, ) from .models import ( @@ -339,6 +340,14 @@ class IPAddressForm(BootstrapMixin, CustomFieldForm): self.fields['nat_inside'].choices = [] +class IPAddressBulkAddForm(forms.Form, BootstrapMixin): + address = ExpandableIPAddressField() + vrf = forms.ModelChoiceField(queryset=VRF.objects.all(), required=False, label='VRF') + tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False) + status = forms.ChoiceField(choices=IPADDRESS_STATUS_CHOICES) + description = forms.CharField(max_length=100, required=False) + + class IPAddressAssignForm(BootstrapMixin, forms.Form): site = forms.ModelChoiceField(queryset=Site.objects.all(), label='Site', required=False, widget=forms.Select(attrs={'filter-for': 'rack'})) diff --git a/netbox/ipam/urls.py b/netbox/ipam/urls.py index 51bd8f849..5ef052b37 100644 --- a/netbox/ipam/urls.py +++ b/netbox/ipam/urls.py @@ -51,6 +51,7 @@ urlpatterns = [ # IP addresses url(r'^ip-addresses/$', views.IPAddressListView.as_view(), name='ipaddress_list'), url(r'^ip-addresses/add/$', views.IPAddressEditView.as_view(), name='ipaddress_add'), + url(r'^ip-addresses/bulk-add/$', views.IPAddressBulkAddView.as_view(), name='ipaddress_bulk_add'), url(r'^ip-addresses/import/$', views.IPAddressBulkImportView.as_view(), name='ipaddress_import'), url(r'^ip-addresses/edit/$', views.IPAddressBulkEditView.as_view(), name='ipaddress_bulk_edit'), url(r'^ip-addresses/delete/$', views.IPAddressBulkDeleteView.as_view(), name='ipaddress_bulk_delete'), diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index f7a851572..3087efda4 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -12,7 +12,7 @@ from dcim.models import Device from utilities.forms import ConfirmationForm from utilities.paginator import EnhancedPaginator from utilities.views import ( - BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView, + BulkAddView, BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView, ) from . import filters, forms, tables @@ -613,6 +613,14 @@ class IPAddressDeleteView(PermissionRequiredMixin, ObjectDeleteView): redirect_url = 'ipam:ipaddress_list' +class IPAddressBulkAddView(PermissionRequiredMixin, BulkAddView): + permission_required = 'ipam.add_ipaddress' + form = forms.IPAddressBulkAddForm + model = IPAddress + template_name = 'ipam/ipaddress_bulk_add.html' + redirect_url = 'ipam:ipaddress_list' + + class IPAddressBulkImportView(PermissionRequiredMixin, BulkImportView): permission_required = 'ipam.add_ipaddress' form = forms.IPAddressImportForm diff --git a/netbox/templates/ipam/inc/ipadress_edit_header.html b/netbox/templates/ipam/inc/ipadress_edit_header.html new file mode 100644 index 000000000..7d590291a --- /dev/null +++ b/netbox/templates/ipam/inc/ipadress_edit_header.html @@ -0,0 +1,4 @@ + diff --git a/netbox/templates/ipam/ipaddress_bulk_add.html b/netbox/templates/ipam/ipaddress_bulk_add.html new file mode 100644 index 000000000..1599ee900 --- /dev/null +++ b/netbox/templates/ipam/ipaddress_bulk_add.html @@ -0,0 +1,22 @@ +{% extends 'utilities/obj_edit.html' %} +{% load static from staticfiles %} +{% load form_helpers %} + +{% block title %}Bulk Add IP Addresses{% endblock %} + +{% block tabs %} + {% include 'ipam/inc/ipadress_edit_header.html' with active_tab='bulk_add' %} +{% endblock %} + +{% block form %} +
+
IP Address
+
+ {% render_field form.address %} + {% render_field form.vrf %} + {% render_field form.tenant %} + {% render_field form.status %} + {% render_field form.description %} +
+
+{% endblock %} diff --git a/netbox/templates/ipam/ipaddress_edit.html b/netbox/templates/ipam/ipaddress_edit.html index 78d9efa95..7226a75fc 100644 --- a/netbox/templates/ipam/ipaddress_edit.html +++ b/netbox/templates/ipam/ipaddress_edit.html @@ -2,6 +2,12 @@ {% load static from staticfiles %} {% load form_helpers %} +{% block tabs %} + {% if not obj.pk %} + {% include 'ipam/inc/ipadress_edit_header.html' with active_tab='add' %} + {% endif %} +{% endblock %} + {% block form %}
IP Address
diff --git a/netbox/templates/utilities/obj_edit.html b/netbox/templates/utilities/obj_edit.html index 18da9378d..ec0777c35 100644 --- a/netbox/templates/utilities/obj_edit.html +++ b/netbox/templates/utilities/obj_edit.html @@ -1,10 +1,6 @@ {% extends '_base.html' %} {% load form_helpers %} -{% block title %} - {% if obj.pk %}Editing {{ obj_type }} {{ obj }}{% else %}Add a new {{ obj_type }}{% endif %} -{% endblock %} - {% block content %}
{% csrf_token %} @@ -13,7 +9,8 @@ {% endfor %}
-

{% if obj.pk %}Editing {{ obj_type }} {{ obj }}{% else %}Add a new {{ obj_type }}{% endif %}

+

{% block title %}{% if obj.pk %}Editing {{ obj_type }} {{ obj }}{% else %}Add a new {{ obj_type }}{% endif %}{% endblock %}

+ {% block tabs %}{% endblock %} {% if form.non_field_errors %}
Errors
diff --git a/netbox/utilities/views.py b/netbox/utilities/views.py index 32318d761..3bd969295 100644 --- a/netbox/utilities/views.py +++ b/netbox/utilities/views.py @@ -3,7 +3,7 @@ from django_tables2 import RequestConfig from django.contrib import messages from django.contrib.contenttypes.models import ContentType -from django.core.exceptions import ImproperlyConfigured +from django.core.exceptions import ImproperlyConfigured, ValidationError from django.core.urlresolvers import reverse from django.db import transaction, IntegrityError from django.db.models import ProtectedError @@ -254,6 +254,57 @@ class ObjectDeleteView(View): }) +class BulkAddView(View): + form = None + model = None + template_name = None + redirect_url = None + + def get(self, request): + + form = self.form() + + return render(request, self.template_name, { + 'obj_type': self.model._meta.verbose_name, + 'form': form, + 'cancel_url': reverse(self.redirect_url), + }) + + def post(self, request): + + form = self.form(request.POST) + if form.is_valid(): + + # The first field will be used as the pattern + pattern_field = form.fields.keys()[0] + pattern = form.cleaned_data[pattern_field] + + # All other fields will be copied as object attributes + kwargs = {k: form.cleaned_data[k] for k in form.fields.keys()[1:]} + + new_objs = [] + try: + with transaction.atomic(): + for value in pattern: + obj = self.model(**kwargs) + setattr(obj, pattern_field, value) + obj.full_clean() + obj.save() + new_objs.append(obj) + except ValidationError as e: + form.add_error(None, e) + + if not form.errors: + messages.success(request, u"Added {} {}.".format(len(new_objs), self.model._meta.verbose_name_plural)) + return redirect(self.redirect_url) + + return render(request, self.template_name, { + 'form': form, + 'obj_type': self.model._meta.verbose_name, + 'cancel_url': reverse(self.redirect_url), + }) + + class BulkImportView(View): form = None table = None