From de56e2b405627e47c4fcfe1565a5ea93a1abfe99 Mon Sep 17 00:00:00 2001 From: checktheroads Date: Fri, 30 Apr 2021 11:13:38 -0700 Subject: [PATCH 1/7] Closes #2434: Show 'Create & Assign IP Address' Button when Creating Interfaces --- netbox/dcim/views.py | 70 +++++++++++++++++++ .../templates/dcim/device_component_add.html | 5 ++ 2 files changed, 75 insertions(+) diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 69e043425..75d570df9 100644 --- a/netbox/dcim/views.py +++ b/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 @@ -1908,6 +1911,73 @@ class InterfaceCreateView(generic.ComponentCreateView): model_form = forms.InterfaceForm template_name = 'dcim/device_component_add.html' + def post(self, request): + logger = logging.getLogger('netbox.dcim.views.InterfaceCreateView') + form = self.form(request.POST, initial=request.GET) + + 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 + if field == 'name': + field = 'name_pattern' + elif field == 'label': + field = 'label_pattern' + for e in errors: + form.add_error(field, '{}: {}'.format(name, ', '.join(e))) + + if not form.errors: + try: + # Create the new components + new_objs = [] + with transaction.atomic(): + for component_form in new_components: + obj = component_form.save() + new_objs.append(obj) + + # Enforce object-level permissions + 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()) + elif '_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)) + + 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), + }) + class InterfaceEditView(generic.ObjectEditView): queryset = Interface.objects.all() diff --git a/netbox/templates/dcim/device_component_add.html b/netbox/templates/dcim/device_component_add.html index b643aa53f..6287330f6 100644 --- a/netbox/templates/dcim/device_component_add.html +++ b/netbox/templates/dcim/device_component_add.html @@ -20,6 +20,11 @@
{% block buttons %} Cancel + {% if perms.ipam.add_ipaddress %} + + {% endif %} From 04cc43b2f0858a1f3fb7044a58e4fd33976d8f56 Mon Sep 17 00:00:00 2001 From: checktheroads Date: Fri, 30 Apr 2021 17:17:56 -0700 Subject: [PATCH 2/7] ensure 'Create & Assign IP Address' button is only shown when the component_type is 'interface' --- netbox/templates/dcim/device_component_add.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/templates/dcim/device_component_add.html b/netbox/templates/dcim/device_component_add.html index 6287330f6..8fb78e4ff 100644 --- a/netbox/templates/dcim/device_component_add.html +++ b/netbox/templates/dcim/device_component_add.html @@ -20,7 +20,7 @@
{% block buttons %} Cancel - {% if perms.ipam.add_ipaddress %} + {% if component_type == 'interface' and perms.ipam.add_ipaddress %} From 2d2719cfb23663619b6f734a986247bc8b7bf74b Mon Sep 17 00:00:00 2001 From: checktheroads Date: Sat, 8 May 2021 12:01:25 -0700 Subject: [PATCH 3/7] #2434: Refactor ComponentCreateView to use generic form validation method New validate_form method on ComponentCreateView handles validation generically, which any post() method on ComponentCreateView can use to validate the form but handle the response differently as needed. --- netbox/dcim/views.py | 67 ++++++---------------------------- netbox/netbox/views/generic.py | 43 +++++++++++++--------- 2 files changed, 38 insertions(+), 72 deletions(-) diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 7082d9e39..f5b98ca06 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -1914,65 +1914,22 @@ class InterfaceCreateView(generic.ComponentCreateView): 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) + self.validate_form(request, form) 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 - if field == 'name': - field = 'name_pattern' - elif field == 'label': - field = 'label_pattern' - for e in errors: - form.add_error(field, '{}: {}'.format(name, ', '.join(e))) - - if not form.errors: - try: - # Create the new components - new_objs = [] - with transaction.atomic(): - for component_form in new_components: - obj = component_form.save() - new_objs.append(obj) - - # Enforce object-level permissions - 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()) - elif '_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)) - - except ObjectDoesNotExist: - msg = "Component creation failed due to object-level permissions violation" - logger.debug(msg) - form.add_error(None, msg) + if '_addanother' in request.POST: + return redirect(request.get_full_path()) + elif '_assignip' in request.POST and len(self.created_objects) >= 1 and request.user.has_perm('ipam.add_ipaddress'): + first_obj = self.created_objects[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, diff --git a/netbox/netbox/views/generic.py b/netbox/netbox/views/generic.py index 8f713fa63..e199884bd 100644 --- a/netbox/netbox/views/generic.py +++ b/netbox/netbox/views/generic.py @@ -1088,6 +1088,7 @@ class ComponentCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View form = None model_form = None template_name = None + created_objects = [] def get_required_permission(self): return get_permission_for_model(self.queryset.model, 'add') @@ -1105,25 +1106,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 '_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,40 +1158,26 @@ 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: obj = component_form.save() - new_objs.append(obj) + self.created_objects.append(obj) # Enforce object-level permissions - 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 self.created_objects]).count() != len(self.created_objects): 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)) 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), - }) - class BulkComponentCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View): """ From 3436780117b94fba825b29eab9f0a00be65c558c Mon Sep 17 00:00:00 2001 From: checktheroads Date: Sat, 8 May 2021 12:07:20 -0700 Subject: [PATCH 4/7] #6372: Show standard form validation errors --- netbox/templates/messages.html | 74 +++++++++++++++------------------- 1 file changed, 32 insertions(+), 42 deletions(-) diff --git a/netbox/templates/messages.html b/netbox/templates/messages.html index 3480a97b7..352777564 100644 --- a/netbox/templates/messages.html +++ b/netbox/templates/messages.html @@ -1,49 +1,39 @@ -
- {% if messages %} {% for message in messages %} -