Browse Source

Closes #181: Implemented support for bulk IP address creation

Jeremy Stretch 8 years ago
parent
commit
9fd9719d0b

+ 10 - 1
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'}))

+ 1 - 0
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'),

+ 9 - 1
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

+ 4 - 0
netbox/templates/ipam/inc/ipadress_edit_header.html

@@ -0,0 +1,4 @@
+<ul class="nav nav-tabs" style="margin-bottom: 20px">
+    <li role="presentation"{% if active_tab == 'add' %} class="active"{% endif %}><a href="{% url 'ipam:ipaddress_add' %}">Individual</a></li>
+    <li role="presentation"{% if active_tab == 'bulk_add' %} class="active"{% endif %}><a href="{% url 'ipam:ipaddress_bulk_add' %}">Bulk</a></li>
+</ul>

+ 22 - 0
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 %}
+    <div class="panel panel-default">
+        <div class="panel-heading"><strong>IP Address</strong></div>
+        <div class="panel-body">
+            {% render_field form.address %}
+            {% render_field form.vrf %}
+            {% render_field form.tenant %}
+            {% render_field form.status %}
+            {% render_field form.description %}
+        </div>
+    </div>
+{% endblock %}

+ 6 - 0
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 %}
     <div class="panel panel-default">
         <div class="panel-heading"><strong>IP Address</strong></div>

+ 2 - 5
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 %}
     <form action="." method="post" class="form form-horizontal">
         {% csrf_token %}
@@ -13,7 +9,8 @@
         {% endfor %}
         <div class="row">
             <div class="col-md-6 col-md-offset-3">
-                <h3>{% if obj.pk %}Editing {{ obj_type }} {{ obj }}{% else %}Add a new {{ obj_type }}{% endif %}</h3>
+                <h3>{% block title %}{% if obj.pk %}Editing {{ obj_type }} {{ obj }}{% else %}Add a new {{ obj_type }}{% endif %}{% endblock %}</h3>
+                {% block tabs %}{% endblock %}
                 {% if form.non_field_errors %}
                     <div class="panel panel-danger">
                         <div class="panel-heading"><strong>Errors</strong></div>

+ 52 - 1
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