Просмотр исходного кода

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

Closes #2434: Show 'Create & Assign IP Address' Button when Creating …
Jeremy Stretch 4 лет назад
Родитель
Сommit
fed0322ebf

+ 27 - 0
netbox/dcim/views.py

@@ -1,7 +1,10 @@
+import logging
+from copy import deepcopy
 from collections import OrderedDict
 
 from django.contrib import messages
 from django.contrib.contenttypes.models import ContentType
+from django.core.exceptions import ObjectDoesNotExist
 from django.core.paginator import EmptyPage, PageNotAnInteger
 from django.db import transaction
 from django.db.models import F, Prefetch
@@ -1910,6 +1913,30 @@ class InterfaceCreateView(generic.ComponentCreateView):
     model_form = forms.InterfaceForm
     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):
     queryset = Interface.objects.all()

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

@@ -1105,25 +1105,47 @@ class ComponentCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View
     def post(self, request):
         logger = logging.getLogger('netbox.views.ComponentCreateView')
         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 = []
             data = deepcopy(request.POST)
-
             names = form.cleaned_data['name_pattern']
             labels = form.cleaned_data.get('label_pattern')
+
             for i, name in enumerate(names):
                 label = labels[i] if labels else None
                 # Initialize the individual component form
                 data['name'] = name
                 data['label'] = label
+
                 if hasattr(form, 'get_iterative_data'):
                     data.update(form.get_iterative_data(i))
+
                 component_form = self.model_form(data)
 
                 if component_form.is_valid():
                     new_components.append(component_form)
+
                 else:
                     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
@@ -1135,11 +1157,8 @@ class ComponentCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View
                             form.add_error(field, '{}: {}'.format(name, ', '.join(e)))
 
             if not form.errors:
-
                 try:
-
                     with transaction.atomic():
-
                         # Create the new components
                         new_objs = []
                         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):
                             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:
                     msg = "Component creation failed due to object-level permissions violation"
                     logger.debug(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):

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

@@ -20,6 +20,11 @@
         <div class="col col-md-8 text-end">
         {% block buttons %}
         <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">
             Create & Add Another
         </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>
-      <button
-        type="button"
-        class="btn-close me-2 m-auto"
-        data-bs-dismiss="toast"
-        aria-label="Close"
-      ></button>
     </div>
-  </div>
-  {% endfor %}
+    {% endfor %}
   {% 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>
-      <button
-        type="button"
-        class="btn-close me-2 m-auto"
-        data-bs-dismiss="toast"
-        aria-label="Close"
-      ></button>
     </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 %}
 </div>