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

View File

@ -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() 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(): 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:
@ -1153,21 +1172,14 @@ class ComponentCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View
messages.success(request, "Added {} {}".format( messages.success(request, "Added {} {}".format(
len(new_components), self.queryset.model._meta.verbose_name_plural len(new_components), self.queryset.model._meta.verbose_name_plural
)) ))
if '_addanother' in request.POST: # Return the newly created objects so overridden post methods can use the data as needed.
return redirect(request.get_full_path()) return new_objs
else:
return redirect(self.get_return_url(request))
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 None
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): class BulkComponentCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):

View File

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

View File

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