1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

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

Closes #2434: Show 'Create & Assign IP Address' Button when Creating …
This commit is contained in:
Jeremy Stretch
2021-05-14 14:53:59 -04:00
committed by GitHub
4 changed files with 94 additions and 60 deletions

View File

@ -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()

View File

@ -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() 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):

View File

@ -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>

View File

@ -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>