Browse Source

Added virtual chassis member remove view

Jeremy Stretch 7 years ago
parent
commit
b61bccbb67

+ 16 - 0
netbox/dcim/models.py

@@ -1011,6 +1011,14 @@ class Device(CreatedUpdatedModel, CustomFieldModel):
             raise ValidationError({
                 'vc_position': "A device assigned to a virtual chassis must have its position defined."
             })
+        try:
+            virtual_chassis = VirtualChassis.objects.filter(master=self.pk)
+            if self.virtual_chassis != virtual_chassis:
+                raise ValidationError(
+                    "This device has been designated the master of a virtual chassis but is not assigned to it."
+                )
+        except VirtualChassis.DoesNotExist:
+            pass
 
     def save(self, *args, **kwargs):
 
@@ -1627,3 +1635,11 @@ class VirtualChassis(models.Model):
 
     def get_absolute_url(self):
         return self.master.get_absolute_url()
+
+    def clean(self):
+
+        # Validate master assignment
+        if self.master not in self.members.all():
+            raise ValidationError({
+                'master': "The selected master is not assigned to this virtual chassis."
+            })

+ 1 - 0
netbox/dcim/urls.py

@@ -221,5 +221,6 @@ urlpatterns = [
     url(r'^virtual-chassis/(?P<pk>\d+)/edit/$', views.VirtualChassisEditView.as_view(), name='virtualchassis_edit'),
     url(r'^virtual-chassis/(?P<pk>\d+)/delete/$', views.VirtualChassisDeleteView.as_view(), name='virtualchassis_delete'),
     url(r'^virtual-chassis/(?P<pk>\d+)/add-member/$', views.VirtualChassisAddMemberView.as_view(), name='virtualchassis_add_member'),
+    url(r'^virtual-chassis-members/(?P<pk>\d+)/delete/$', views.VirtualChassisRemoveMemberView.as_view(), name='virtualchassis_remove_member'),
 
 ]

+ 48 - 1
netbox/dcim/views.py

@@ -2172,7 +2172,7 @@ class VirtualChassisDeleteView(PermissionRequiredMixin, ObjectDeleteView):
 
 
 class VirtualChassisAddMemberView(PermissionRequiredMixin, GetReturnURLMixin, View):
-    permission_required = 'dcim.change_device'
+    permission_required = 'dcim.change_virtualchassis'
 
     def get(self, request, pk):
 
@@ -2224,3 +2224,50 @@ class VirtualChassisAddMemberView(PermissionRequiredMixin, GetReturnURLMixin, Vi
             'membership_form': membership_form,
             'return_url': self.get_return_url(request, virtual_chassis),
         })
+
+
+class VirtualChassisRemoveMemberView(PermissionRequiredMixin, GetReturnURLMixin, View):
+    permission_required = 'dcim.change_virtualchassis'
+
+    def get(self, request, pk):
+
+        device = get_object_or_404(Device, pk=pk, virtual_chassis__isnull=False)
+        form = ConfirmationForm(initial=request.GET)
+
+        return render(request, 'dcim/virtualchassis_remove_member.html', {
+            'device': device,
+            'form': form,
+            'return_url': self.get_return_url(request, device),
+        })
+
+    def post(self, request, pk):
+
+        device = get_object_or_404(Device, pk=pk, virtual_chassis__isnull=False)
+        form = ConfirmationForm(request.POST)
+
+        # Protect master device from being removed
+        virtual_chassis = VirtualChassis.objects.filter(master=device).first()
+        if virtual_chassis is not None:
+            msg = 'Unable to remove master device {} from the virtual chassis.'.format(escape(device))
+            messages.error(request, mark_safe(msg))
+            return redirect(device.get_absolute_url())
+
+        if form.is_valid():
+
+            Device.objects.filter(pk=device.pk).update(
+                virtual_chassis=None,
+                vc_position=None,
+                vc_priority=None
+            )
+
+            msg = 'Removed {} from virtual chassis {}'.format(device, device.virtual_chassis)
+            messages.success(request, msg)
+            UserAction.objects.log_edit(request.user, device, msg)
+
+            return redirect(self.get_return_url(request, device))
+
+        return render(request, 'dcim/virtualchassis_remove_member.html', {
+            'device': device,
+            'form': form,
+            'return_url': self.get_return_url(request, device),
+        })

+ 22 - 6
netbox/templates/dcim/virtualchassis_edit.html

@@ -31,6 +31,7 @@
                                 <th>Device</th>
                                 <th>Position</th>
                                 <th>Priority</th>
+                                <th></th>
                             </tr>
                         </thead>
                         <tbody>
@@ -38,11 +39,22 @@
                                 {% for field in form.hidden_fields %}
                                     {{ field }}
                                 {% endfor %}
-                                <tr>
-                                    <td>{{ form.instance.name }}</td>
-                                    <td>{{ form.vc_position }}</td>
-                                    <td>{{ form.vc_priority }}</td>
-                                </tr>
+                                {% with device=form.instance virtual_chassis=vc_form.instance %}
+                                    <tr>
+                                        <td>
+                                            <a href="{{ device.get_absolute_url }}">{{ device }}</a>
+                                        </td>
+                                        <td>{{ form.vc_position }}</td>
+                                        <td>{{ form.vc_priority }}</td>
+                                        <td>
+                                            {% if virtual_chassis.pk %}
+                                                <a href="{% url 'dcim:virtualchassis_remove_member' pk=device.pk %}?return_url={% url 'dcim:virtualchassis_edit' pk=virtual_chassis.pk %}" class="btn btn-danger btn-xs{% if virtual_chassis.master == device %} disabled{% endif %}">
+                                                    <span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
+                                                </a>
+                                            {% endif %}
+                                        </td>
+                                    </tr>
+                                {% endwith %}
                             {% endfor %}
                         </tbody>
                     </table>
@@ -51,7 +63,11 @@
         </div>
         <div class="row">
             <div class="col-md-6 col-md-offset-3 text-right">
-                <button type="submit" name="_create" class="btn btn-primary">Create</button>
+                {% if vc_form.instance.pk %}
+                    <button type="submit" name="_update" class="btn btn-primary">Update</button>
+                {% else %}
+                    <button type="submit" name="_create" class="btn btn-primary">Create</button>
+                {% endif %}
                 <a href="{{ return_url }}" class="btn btn-default">Cancel</a>
             </div>
         </div>

+ 8 - 0
netbox/templates/dcim/virtualchassis_remove_member.html

@@ -0,0 +1,8 @@
+{% extends 'utilities/confirmation_form.html' %}
+{% load form_helpers %}
+
+{% block title %}Remove Virtual Chassis Member?{% endblock %}
+
+{% block message %}
+    <p>Are you sure you want to remove <strong>{{ device }}</strong> from virtual chassis {{ device.virtual_chassis }}?</p>
+{% endblock %}