diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index e5631a04c..f82787865 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -2219,8 +2219,12 @@ class VirtualChassisCreateForm(BootstrapMixin, forms.ModelForm): self.fields['master'].queryset = Device.objects.filter(pk__in=candidate_pks) +# +# VC memberships +# + class VCMembershipForm(BootstrapMixin, forms.ModelForm): class Meta: model = VCMembership - fields = ['device', 'position', 'priority'] + fields = ['position', 'priority'] diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py index ec267c297..6b912c627 100644 --- a/netbox/dcim/models.py +++ b/netbox/dcim/models.py @@ -1507,7 +1507,7 @@ class VirtualChassis(models.Model): return self.master.name def get_absolute_url(self): - return "{}?virtual_chassis={}".format(reverse('dcim:device_list'), self.pk) + return self.master.get_absolute_url() @property def master(self): @@ -1547,13 +1547,21 @@ class VCMembership(models.Model): unique_together = ['virtual_chassis', 'position'] verbose_name = 'VC membership' + def __str__(self): + return self.device.name + def clean(self): + # We have to call this here because it won't be called by VCMembershipForm + self.validate_unique() + # Check for master conflicts if getattr(self, 'virtual_chassis', None) and self.is_master: - master_conflict = VCMembership.objects.filter(virtual_chassis=self.virtual_chassis).first() + master_conflict = VCMembership.objects.filter( + virtual_chassis=self.virtual_chassis, is_master=True + ).exclude(pk=self.pk).first() if master_conflict: - raise ValidationError({ - 'virtual_chassis': "{} has already been designated as the master for this virtual chassis. It must " - "be demoted before a new master can be assigned.".format(master_conflict.device) - }) + raise ValidationError( + "{} has already been designated as the master for this virtual chassis. It must be demoted before " + "a new master can be assigned.".format(master_conflict.device) + ) diff --git a/netbox/dcim/urls.py b/netbox/dcim/urls.py index cde30f11f..10f3aafde 100644 --- a/netbox/dcim/urls.py +++ b/netbox/dcim/urls.py @@ -213,4 +213,8 @@ urlpatterns = [ 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'), + # VC memberships + url(r'^vc-memberships/(?P\d+)/edit/$', views.VCMembershipEditView.as_view(), name='vcmembership_edit'), + url(r'^vc-memberships/(?P\d+)/delete/$', views.VCMembershipDeleteView.as_view(), name='vcmembership_delete'), + ] diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index bc90be536..1d20a6b97 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -1859,6 +1859,10 @@ class VirtualChassisCreateView(PermissionRequiredMixin, View): class _VCMembershipForm(forms.VCMembershipForm): device = ModelChoiceField(queryset=Device.objects.filter(pk__in=device_list)) + class Meta: + model = VCMembership + fields = ['device', 'position', 'priority'] + VCMembershipFormSet = modelformset_factory(model=VCMembership, form=_VCMembershipForm, extra=len(device_list)) if '_create' in request.POST: @@ -1902,3 +1906,18 @@ class VirtualChassisDeleteView(PermissionRequiredMixin, ObjectDeleteView): permission_required = 'dcim.delete_virtualchassis' model = VirtualChassis default_return_url = 'dcim:device_list' + + +# +# VC memberships +# + +class VCMembershipEditView(PermissionRequiredMixin, ObjectEditView): + permission_required = 'dcim.change_vcmembership' + model = VCMembership + model_form = forms.VCMembershipForm + + +class VCMembershipDeleteView(PermissionRequiredMixin, ObjectDeleteView): + permission_required = 'dcim.delete_vcmembership' + model = VCMembership diff --git a/netbox/templates/dcim/virtualchassis_edit.html b/netbox/templates/dcim/virtualchassis_edit.html index a2627c0b3..8e9724e17 100644 --- a/netbox/templates/dcim/virtualchassis_edit.html +++ b/netbox/templates/dcim/virtualchassis_edit.html @@ -1,11 +1,44 @@ {% extends 'utilities/obj_edit.html' %} {% load form_helpers %} -{% block form %} -
-
{{ obj_type|capfirst }}
-
- {% render_form form %} +{% block content %} + {{ block.super }} +
+
+

Memberships

+
+ + + + + + + + + {% for vcm in form.instance.memberships.all %} + + + + + + + + {% endfor %} +
DevicePositionMasterPriority
+ {{ vcm.device }} + {{ vcm.position }}{% if vcm.is_master %}{% endif %}{{ vcm.priority|default:"" }} + {% if perms.dcim.change_vcmembership %} + + Edit + + {% endif %} + {% if perms.dcim.delete_vcmembership %} + + Delete + + {% endif %} +
+
{% endblock %}