Parcourir la source

Merge pull request #6323 from netbox-community/feature-2434

Closes #2434: Show 'Create & Assign IP Address' Button when Creating …
Jeremy Stretch il y a 4 ans
Parent
commit
fed0322ebf

+ 27 - 0
netbox/dcim/views.py

@@ -1,7 +1,10 @@
+import logging
+from copy import deepcopy
 from collections import OrderedDict
 from collections import OrderedDict
 
 
 from django.contrib import messages
 from django.contrib import messages
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes.models import ContentType
+from django.core.exceptions import ObjectDoesNotExist
 from django.core.paginator import EmptyPage, PageNotAnInteger
 from django.core.paginator import EmptyPage, PageNotAnInteger
 from django.db import transaction
 from django.db import transaction
 from django.db.models import F, Prefetch
 from django.db.models import F, Prefetch
@@ -1910,6 +1913,30 @@ class InterfaceCreateView(generic.ComponentCreateView):
     model_form = forms.InterfaceForm
     model_form = forms.InterfaceForm
     template_name = 'dcim/device_component_add.html'
     template_name = 'dcim/device_component_add.html'
 
 
+    def post(self, request):
+        """
+        Override inherited post() method to handle request to assign newly created
+        interface objects (first object) to an IP Address object.
+        """
+        logger = logging.getLogger('netbox.dcim.views.InterfaceCreateView')
+        form = self.form(request.POST, initial=request.GET)
+        new_objs = self.validate_form(request, form)
+
+        if form.is_valid() and not form.errors:
+            if '_addanother' in request.POST:
+                return redirect(request.get_full_path())
+            elif new_objs is not None and '_assignip' in request.POST and len(new_objs) >= 1 and request.user.has_perm('ipam.add_ipaddress'):
+                first_obj = new_objs[0].pk
+                return redirect(f'/ipam/ip-addresses/add/?interface={first_obj}&return_url={self.get_return_url(request)}')
+            else:
+                return redirect(self.get_return_url(request))
+
+        return render(request, self.template_name, {
+            'component_type': self.queryset.model._meta.verbose_name,
+            'form': form,
+            'return_url': self.get_return_url(request),
+        })
+
 
 
 class InterfaceEditView(generic.ObjectEditView):
 class InterfaceEditView(generic.ObjectEditView):
     queryset = Interface.objects.all()
     queryset = Interface.objects.all()

+ 30 - 18
netbox/netbox/views/generic.py

@@ -1105,25 +1105,47 @@ class ComponentCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View
     def post(self, request):
     def post(self, request):
         logger = logging.getLogger('netbox.views.ComponentCreateView')
         logger = logging.getLogger('netbox.views.ComponentCreateView')
         form = self.form(request.POST, initial=request.GET)
         form = self.form(request.POST, initial=request.GET)
+        self.validate_form(request, form)
 
 
-        if form.is_valid():
+        if form.is_valid() and not form.errors:
+            if '_addanother' in request.POST:
+                return redirect(request.get_full_path())
+            else:
+                return redirect(self.get_return_url(request))
 
 
+        return render(request, self.template_name, {
+            'component_type': self.queryset.model._meta.verbose_name,
+            'form': form,
+            'return_url': self.get_return_url(request),
+        })
+
+    def validate_form(self, request, form):
+        """
+        Validate form values and set errors on the form object as they are detected. If
+        no errors are found, signal success messages.
+        """
+
+        logger = logging.getLogger('netbox.views.ComponentCreateView')
+        if form.is_valid():
             new_components = []
             new_components = []
             data = deepcopy(request.POST)
             data = deepcopy(request.POST)
-
             names = form.cleaned_data['name_pattern']
             names = form.cleaned_data['name_pattern']
             labels = form.cleaned_data.get('label_pattern')
             labels = form.cleaned_data.get('label_pattern')
+
             for i, name in enumerate(names):
             for i, name in enumerate(names):
                 label = labels[i] if labels else None
                 label = labels[i] if labels else None
                 # Initialize the individual component form
                 # Initialize the individual component form
                 data['name'] = name
                 data['name'] = name
                 data['label'] = label
                 data['label'] = label
+
                 if hasattr(form, 'get_iterative_data'):
                 if hasattr(form, 'get_iterative_data'):
                     data.update(form.get_iterative_data(i))
                     data.update(form.get_iterative_data(i))
+
                 component_form = self.model_form(data)
                 component_form = self.model_form(data)
 
 
                 if component_form.is_valid():
                 if component_form.is_valid():
                     new_components.append(component_form)
                     new_components.append(component_form)
+
                 else:
                 else:
                     for field, errors in component_form.errors.as_data().items():
                     for field, errors in component_form.errors.as_data().items():
                         # Assign errors on the child form's name/label field to name_pattern/label_pattern on the parent form
                         # Assign errors on the child form's name/label field to name_pattern/label_pattern on the parent form
@@ -1135,11 +1157,8 @@ class ComponentCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View
                             form.add_error(field, '{}: {}'.format(name, ', '.join(e)))
                             form.add_error(field, '{}: {}'.format(name, ', '.join(e)))
 
 
             if not form.errors:
             if not form.errors:
-
                 try:
                 try:
-
                     with transaction.atomic():
                     with transaction.atomic():
-
                         # Create the new components
                         # Create the new components
                         new_objs = []
                         new_objs = []
                         for component_form in new_components:
                         for component_form in new_components:
@@ -1150,24 +1169,17 @@ class ComponentCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View
                         if self.queryset.filter(pk__in=[obj.pk for obj in new_objs]).count() != len(new_objs):
                         if self.queryset.filter(pk__in=[obj.pk for obj in new_objs]).count() != len(new_objs):
                             raise ObjectDoesNotExist
                             raise ObjectDoesNotExist
 
 
-                    messages.success(request, "Added {} {}".format(
-                        len(new_components), self.queryset.model._meta.verbose_name_plural
-                    ))
-                    if '_addanother' in request.POST:
-                        return redirect(request.get_full_path())
-                    else:
-                        return redirect(self.get_return_url(request))
+                        messages.success(request, "Added {} {}".format(
+                            len(new_components), self.queryset.model._meta.verbose_name_plural
+                        ))
+                        # Return the newly created objects so overridden post methods can use the data as needed.
+                        return new_objs
 
 
                 except ObjectDoesNotExist:
                 except ObjectDoesNotExist:
                     msg = "Component creation failed due to object-level permissions violation"
                     msg = "Component creation failed due to object-level permissions violation"
                     logger.debug(msg)
                     logger.debug(msg)
                     form.add_error(None, msg)
                     form.add_error(None, msg)
-
-        return render(request, self.template_name, {
-            'component_type': self.queryset.model._meta.verbose_name,
-            'form': form,
-            'return_url': self.get_return_url(request),
-        })
+        return None
 
 
 
 
 class BulkComponentCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
 class BulkComponentCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):

+ 5 - 0
netbox/templates/dcim/device_component_add.html

@@ -20,6 +20,11 @@
         <div class="col col-md-8 text-end">
         <div class="col col-md-8 text-end">
         {% block buttons %}
         {% block buttons %}
         <a class="btn btn-outline-danger" href="{{ return_url }}">Cancel</a>
         <a class="btn btn-outline-danger" href="{{ return_url }}">Cancel</a>
+        {% if component_type == 'interface' and perms.ipam.add_ipaddress %}
+        <button type="submit" name="_assignip" class="btn btn-outline-success">
+            Create & Assign IP Address
+        </button>
+        {% endif %}
         <button type="submit" name="_addanother" class="btn btn-outline-primary">
         <button type="submit" name="_addanother" class="btn btn-outline-primary">
             Create & Add Another
             Create & Add Another
         </button>
         </button>

+ 32 - 42
netbox/templates/messages.html

@@ -1,49 +1,39 @@
-<div
-  id="django-messages"
-  class="toast-container position-fixed bottom-0 end-0 m-3"
->
-  {% if messages %} {% for message in messages %}
-  <div
-    class="django-message toast align-items-center border-0 bg-{% if message.tags %}{{ message.tags }}{% else %}info{% endif %}"
-    role="alert"
-    aria-live="assertive"
-    aria-atomic="true"
-    data-bs-delay="10000"
-  >
-    <div class="d-flex">
-      <div class="toast-body">
-        {{ message }}
+<div id="django-messages" class="toast-container position-fixed bottom-0 end-0 m-3">
+  {% if messages %}
+    {% for message in messages %}
+    <div class="django-message toast align-items-center border-0 bg-{% if message.tags %}{{ message.tags }}{% else %}info{% endif %}" role="alert" aria-live="assertive" aria-atomic="true" data-bs-delay="10000">
+      <div class="d-flex">
+        <div class="toast-body">
+          {{ message }}
+        </div>
+        <button type="button" class="btn-close me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
       </div>
       </div>
-      <button
-        type="button"
-        class="btn-close me-2 m-auto"
-        data-bs-dismiss="toast"
-        aria-label="Close"
-      ></button>
     </div>
     </div>
-  </div>
-  {% endfor %}
+    {% endfor %}
   {% elif form and form.non_field_errors %}
   {% elif form and form.non_field_errors %}
-  {% for error in form.non_field_errors.get_json_data %}
-  <div
-    class="django-message toast align-items-center border-0 bg-danger"
-    role="alert"
-    aria-live="assertive"
-    aria-atomic="true"
-    data-bs-delay="10000"
-  >
-    <div class="d-flex">
-      <div class="toast-body">
-        {{ error.message }}
+    {% for error in form.non_field_errors.get_json_data %}
+    <div class="django-message toast align-items-center border-0 bg-danger" role="alert" aria-live="assertive" aria-atomic="true" data-bs-delay="10000">
+      <div class="d-flex">
+        <div class="toast-body">
+          {{ error.message }}
+        </div>
+        <button type="button" class="btn-close me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
       </div>
       </div>
-      <button
-        type="button"
-        class="btn-close me-2 m-auto"
-        data-bs-dismiss="toast"
-        aria-label="Close"
-      ></button>
     </div>
     </div>
-  </div>
-  {% endfor %}
+    {% endfor %}
+  {% elif form and form.errors %}
+    {% for field in form %}
+      {% for error in field.errors %}
+      <div class="django-message toast align-items-center border-0 bg-danger" role="alert" aria-live="assertive" aria-atomic="true" data-bs-delay="60000">
+        <div class="toast-header bg-danger">
+          <strong class="me-auto">{{ field.label }}</strong>
+          <button type="button" class="btn-close me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
+        </div>
+        <div class="toast-body">
+          {{ error|escape }}
+        </div>
+      </div>
+      {% endfor %}
+    {% endfor %}
   {% endif %}
   {% endif %}
 </div>
 </div>