Browse Source

Fixes #1166: Re-implemented bulk IP address creation

Jeremy Stretch 8 years ago
parent
commit
a870a3b918

+ 9 - 5
netbox/ipam/forms.py

@@ -471,15 +471,19 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm)
         return ipaddress
 
 
-class IPAddressBulkAddForm(BootstrapMixin, CustomFieldForm):
-    address_pattern = ExpandableIPAddressField(label='Address Pattern')
-    vrf = forms.ModelChoiceField(queryset=VRF.objects.all(), required=False, label='VRF', empty_label='Global')
+class IPAddressPatternForm(BootstrapMixin, forms.Form):
+    pattern = ExpandableIPAddressField(label='Address pattern')
+
 
-    pattern_map = ('address_pattern', 'address')
+class IPAddressBulkAddForm(BootstrapMixin, CustomFieldForm):
 
     class Meta:
         model = IPAddress
-        fields = ['address_pattern', 'vrf', 'tenant', 'status', 'description']
+        fields = ['address', 'status', 'vrf', 'tenant', 'description']
+
+    def __init__(self, *args, **kwargs):
+        super(IPAddressBulkAddForm, self).__init__(*args, **kwargs)
+        self.fields['vrf'].empty_label = 'Global'
 
 
 class IPAddressFromCSVForm(forms.ModelForm):

+ 3 - 2
netbox/ipam/views.py

@@ -584,8 +584,9 @@ class IPAddressDeleteView(PermissionRequiredMixin, ObjectDeleteView):
 
 class IPAddressBulkAddView(PermissionRequiredMixin, BulkAddView):
     permission_required = 'ipam.add_ipaddress'
-    form = forms.IPAddressBulkAddForm
-    model_form = forms.IPAddressForm
+    pattern_form = forms.IPAddressPatternForm
+    model_form = forms.IPAddressBulkAddForm
+    pattern_target = 'address'
     template_name = 'ipam/ipaddress_bulk_add.html'
     default_return_url = 'ipam:ipaddress_list'
 

+ 7 - 7
netbox/templates/ipam/ipaddress_bulk_add.html

@@ -12,18 +12,18 @@
     <div class="panel panel-default">
         <div class="panel-heading"><strong>IP Addresses</strong></div>
         <div class="panel-body">
-            {% render_field form.address_pattern %}
-            {% render_field form.vrf %}
-            {% render_field form.tenant %}
-            {% render_field form.status %}
-            {% render_field form.description %}
+            {% render_field pattern_form.pattern %}
+            {% render_field model_form.status %}
+            {% render_field model_form.vrf %}
+            {% render_field model_form.tenant %}
+            {% render_field model_form.description %}
         </div>
     </div>
-    {% if form.custom_fields %}
+    {% if model_form.custom_fields %}
         <div class="panel panel-default">
             <div class="panel-heading"><strong>Custom Fields</strong></div>
             <div class="panel-body">
-                {% render_custom_fields form %}
+                {% render_custom_fields model_form %}
             </div>
         </div>
     {% endif %}

+ 40 - 28
netbox/utilities/views.py

@@ -290,66 +290,78 @@ class BulkAddView(View):
     """
     Create new objects in bulk.
 
-    form: Form class
+    pattern_form: Form class which provides the `pattern` field
     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
+    pattern_form = None
     model_form = None
+    pattern_target = ''
     template_name = None
     default_return_url = 'home'
 
     def get(self, request):
 
-        form = self.form()
+        pattern_form = self.pattern_form()
+        model_form = self.model_form()
 
         return render(request, self.template_name, {
             'obj_type': self.model_form._meta.model._meta.verbose_name,
-            'form': form,
+            'pattern_form': pattern_form,
+            'model_form': model_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():
+        pattern_form = self.pattern_form(request.POST)
+        model_form = self.model_form(request.POST)
 
-            # 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]
-            model_form_data = form.cleaned_data
+        if pattern_form.is_valid():
 
+            pattern = pattern_form.cleaned_data['pattern']
             new_objs = []
+
             try:
                 with transaction.atomic():
-                    # Validate and save each object individually
+
+                    # Create objects from the expanded. Abort the transaction on the first validation error.
                     for value in pattern:
-                        model_form_data[pattern_target] = value
-                        model_form = self.model_form(model_form_data)
+
+                        # Reinstantiate the model form each time to avoid overwriting the same instance. Use a mutable
+                        # copy of the POST QueryDict so that we can update the target field value.
+                        model_form = self.model_form(request.POST.copy())
+                        model_form.data[self.pattern_target] = value
+
+                        # Validate each new object independently.
                         if model_form.is_valid():
                             obj = model_form.save()
                             new_objs.append(obj)
                         else:
-                            for error in model_form.errors.as_data().values():
-                                form.add_error(None, error)
-                    # Abort the creation of all objects if errors exist
-                    if form.errors:
-                        raise ValidationError("Validation of one or more model forms failed.")
-            except ValidationError:
-                pass
+                            # Copy any errors on the pattern target field to the pattern form.
+                            errors = model_form.errors.as_data()
+                            if errors.get(self.pattern_target):
+                                pattern_form.add_error('pattern', errors[self.pattern_target])
+                            # Raise an IntegrityError to break the for loop and abort the transaction.
+                            raise IntegrityError()
+
+                    # If we make it to this point, validation has succeeded on all new objects.
+                    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(model), msg)
 
-            if not form.errors:
-                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(model), msg)
-                if '_addanother' in request.POST:
-                    return redirect(request.path)
-                return redirect(self.default_return_url)
+                    if '_addanother' in request.POST:
+                        return redirect(request.path)
+                    return redirect(self.default_return_url)
+
+            except IntegrityError:
+                pass
 
         return render(request, self.template_name, {
-            'form': form,
+            'pattern_form': pattern_form,
+            'model_form': model_form,
             'obj_type': model._meta.verbose_name,
             'return_url': reverse(self.default_return_url),
         })