From 8d1676db546067b5e31deabdc3a47491871bb55f Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 19 Jan 2018 12:34:09 -0500 Subject: [PATCH] Implemented a view for adding individual devices to an existing virtual chassis --- netbox/dcim/forms.py | 47 ++++++++++++++++++++++++--- netbox/dcim/urls.py | 1 + netbox/dcim/views.py | 53 +++++++++++++++++++++++++++++++ netbox/templates/dcim/device.html | 5 +++ 4 files changed, 102 insertions(+), 4 deletions(-) diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 4420f86e3..91206e78d 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -22,9 +22,10 @@ from utilities.forms import ( ) from virtualization.models import Cluster from .constants import ( - CONNECTION_STATUS_CHOICES, CONNECTION_STATUS_CONNECTED, IFACE_FF_CHOICES, IFACE_FF_LAG, IFACE_ORDERING_CHOICES, - RACK_FACE_CHOICES, RACK_TYPE_CHOICES, RACK_WIDTH_CHOICES, RACK_WIDTH_19IN, RACK_WIDTH_23IN, STATUS_CHOICES, - SUBDEVICE_ROLE_CHILD, SUBDEVICE_ROLE_PARENT, SUBDEVICE_ROLE_CHOICES, + CONNECTION_STATUS_CHOICES, CONNECTION_STATUS_CONNECTED, IFACE_FF_CHOICES, IFACE_FF_LAG, IFACE_MODE_ACCESS, + IFACE_MODE_CHOICES, IFACE_MODE_TAGGED_ALL, IFACE_ORDERING_CHOICES, RACK_FACE_CHOICES, RACK_TYPE_CHOICES, + RACK_WIDTH_CHOICES, RACK_WIDTH_19IN, RACK_WIDTH_23IN, STATUS_CHOICES, SUBDEVICE_ROLE_CHILD, SUBDEVICE_ROLE_PARENT, + SUBDEVICE_ROLE_CHOICES, ) from .formfields import MACAddressFormField from .models import ( @@ -33,7 +34,6 @@ from .models import ( Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, Region, Site, VCMembership, VirtualChassis ) -from .constants import * DEVICE_BY_PK_RE = '{\d+\}' @@ -2253,3 +2253,42 @@ class VCMembershipForm(BootstrapMixin, forms.ModelForm): class Meta: model = VCMembership fields = ['position', 'priority'] + + +class VCMembershipCreateForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm): + site = forms.ModelChoiceField( + queryset=Site.objects.all(), + label='Site', + required=False, + widget=forms.Select( + attrs={'filter-for': 'rack'} + ) + ) + rack = ChainedModelChoiceField( + queryset=Rack.objects.all(), + chains=( + ('site', 'site'), + ), + label='Rack', + required=False, + widget=APISelect( + api_url='/api/dcim/racks/?site_id={{site}}', + attrs={'filter-for': 'device', 'nullable': 'true'} + ) + ) + device = ChainedModelChoiceField( + queryset=Device.objects.all(), + chains=( + ('site', 'site'), + ('rack', 'rack'), + ), + label='Device', + widget=APISelect( + api_url='/api/dcim/devices/?site_id={{site}}&rack_id={{rack}}', + display_field='display_name' + ) + ) + + class Meta: + model = VCMembership + fields = ['site', 'rack', 'device', 'position', 'priority'] diff --git a/netbox/dcim/urls.py b/netbox/dcim/urls.py index fbb678135..d836ce63b 100644 --- a/netbox/dcim/urls.py +++ b/netbox/dcim/urls.py @@ -216,6 +216,7 @@ urlpatterns = [ url(r'^virtual-chassis/add/$', views.VirtualChassisCreateView.as_view(), name='virtualchassis_add'), url(r'^virtual-chassis/(?P\d+)/edit/$', views.VirtualChassisEditView.as_view(), name='virtualchassis_edit'), url(r'^virtual-chassis/(?P\d+)/delete/$', views.VirtualChassisDeleteView.as_view(), name='virtualchassis_delete'), + url(r'^virtual-chassis/(?P\d+)/add-member/$', views.VirtualChassisAddMemberView.as_view(), name='virtualchassis_add_member'), # VC memberships url(r'^vc-memberships/(?P\d+)/edit/$', views.VCMembershipEditView.as_view(), name='vcmembership_edit'), diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 59cc9110e..d0e4c90a4 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -2099,6 +2099,59 @@ class VirtualChassisDeleteView(PermissionRequiredMixin, ObjectDeleteView): default_return_url = 'dcim:device_list' +class VirtualChassisAddMemberView(GetReturnURLMixin, View): + """ + Create a new VCMembership tying a Device to the VirtualChassis. + """ + template_name = 'utilities/obj_edit.html' + + def get(self, request, pk): + + virtual_chassis = get_object_or_404(VirtualChassis, pk=pk) + obj = VCMembership(virtual_chassis=virtual_chassis) + + initial_data = {k: request.GET[k] for k in request.GET} + form = forms.VCMembershipCreateForm(instance=obj, initial=initial_data) + + return render(request, self.template_name, { + 'obj': obj, + 'obj_type': VCMembership._meta.verbose_name, + 'form': form, + 'return_url': self.get_return_url(request, obj), + }) + + def post(self, request, pk): + + virtual_chassis = get_object_or_404(VirtualChassis, pk=pk) + obj = VCMembership(virtual_chassis=virtual_chassis) + + form = forms.VCMembershipCreateForm(request.POST, instance=obj) + + if form.is_valid(): + + obj = form.save() + + msg = 'Added member {}'.format(obj.device.get_absolute_url(), escape(obj.device)) + messages.success(request, mark_safe(msg)) + UserAction.objects.log_create(request.user, obj, msg) + + if '_addanother' in request.POST: + return redirect(request.get_full_path()) + + return_url = form.cleaned_data.get('return_url') + if return_url is not None and is_safe_url(url=return_url, host=request.get_host()): + return redirect(return_url) + else: + return redirect(self.get_return_url(request, obj)) + + return render(request, self.template_name, { + 'obj': obj, + 'obj_type': VCMembership._meta.verbose_name, + 'form': form, + 'return_url': self.get_return_url(request, obj), + }) + + # # VC memberships # diff --git a/netbox/templates/dcim/device.html b/netbox/templates/dcim/device.html index 6c596a236..afe7379ff 100644 --- a/netbox/templates/dcim/device.html +++ b/netbox/templates/dcim/device.html @@ -122,6 +122,11 @@ {% endfor %}