Repository: netbox-community/netbox

Issue:
Add a "Create and assign IP" button to interface creation dialog
### Environment
* Python version: 2.7.13
* NetBox version: 2.4.4

### Proposed Functionality
Add another blue "Create and assign IP" button to the interface creation dialog that results in the same view as pressing the green "+" on a interface.

### Use Case
Often when adding an interface, users also want to assign an IP address to it. Also the distinction between interface itself and ip addresses configured on the interface (that makes perfectly sense) can be a difficulty to grasp. They have to find the mouseover on the green plus of the created interface to find out how to configure an address to the interface. Having a shortcut directly from the interface creation dialog would result in a much more streamlined experience.

### Database Changes
None.

### External Dependencies
None.


Discussion:
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. NetBox is governed by a small group of core maintainers which means not all opened issues may receive direct feedback. Please see our [contributing guide](https://github.com/netbox-community/netbox/blob/develop/CONTRIBUTING.md).

I will try to come up with a patch myself.
Assigning to @kasimon 
@kasimon Are you still working on this?
@jeremystretch I still intend to do so. Haven't had the time for it yet unfortunately.
@kasimon have you been able to make any progress on this?

Resolving patch:
diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py
--- 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
@@ -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()
diff --git a/netbox/netbox/views/generic.py b/netbox/netbox/views/generic.py
--- a/netbox/netbox/views/generic.py
+++ b/netbox/netbox/views/generic.py
@@ -1105,25 +1105,47 @@ def get(self, request):
     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 @@ def post(self, request):
                             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 @@ def post(self, request):
                         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):


Test patch: