1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Added virtual chassis member add view

This commit is contained in:
Jeremy Stretch
2018-02-01 11:39:13 -05:00
parent a4019be28c
commit f1da517c84
8 changed files with 168 additions and 39 deletions

View File

@ -6,3 +6,6 @@ from django.apps import AppConfig
class DCIMConfig(AppConfig):
name = "dcim"
verbose_name = "DCIM"
def ready(self):
import dcim.signals

View File

@ -2276,38 +2276,54 @@ class VirtualChassisForm(BootstrapMixin, forms.ModelForm):
fields = ['master', 'domain']
# class VCAddMemberForm(BootstrapMixin, ChainedFieldsMixin, forms.Form):
# 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'
# )
# )
# vc_position = forms.IntegerField(label='Position')
# vc_priority = forms.IntegerField(required=False, label='Priority')
class VCMemberSelectForm(BootstrapMixin, ChainedFieldsMixin, forms.Form):
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 DeviceVCMembershipForm(forms.ModelForm):
class Meta:
model = Device
fields = ['vc_position', 'vc_priority']
labels = {
'vc_position': 'Position',
'vc_priority': 'Priority',
}
def clean_vc_position(self):
vc_position = self.cleaned_data['vc_position']
if Device.objects.filter(virtual_chassis=self.instance.virtual_chassis, vc_position=vc_position).exists():
raise forms.ValidationError("A virtual chassis member already exists in this position.")
return vc_position

View File

@ -1006,6 +1006,12 @@ class Device(CreatedUpdatedModel, CustomFieldModel):
'cluster': "The assigned cluster belongs to a different site ({})".format(self.cluster.site)
})
# Validate virtual chassis assignment
if self.virtual_chassis and not self.vc_position:
raise ValidationError({
'vc_position': "A device assigned to a virtual chassis must have its position defined."
})
def save(self, *args, **kwargs):
is_new = not bool(self.pk)

14
netbox/dcim/signals.py Normal file
View File

@ -0,0 +1,14 @@
from __future__ import unicode_literals
from django.db.models.signals import pre_delete
from django.dispatch import receiver
from .models import Device, VirtualChassis
@receiver(pre_delete, sender=VirtualChassis)
def clear_virtualchassis_members(instance, **kwargs):
"""
When a VirtualChassis is deleted, nullify the vc_position and vc_priority fields of its prior members.
"""
Device.objects.filter(virtual_chassis=instance.pk).update(vc_position=None, vc_priority=None)

View File

@ -220,6 +220,6 @@ urlpatterns = [
url(r'^virtual-chassis/add/$', views.VirtualChassisCreateView.as_view(), name='virtualchassis_add'),
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/(?P<pk>\d+)/add-member/$', views.VirtualChassisAddMemberView.as_view(), name='virtualchassis_add_member'),
]

View File

@ -2158,7 +2158,7 @@ class VirtualChassisEditView(PermissionRequiredMixin, GetReturnURLMixin, View):
return redirect(vc_form.cleaned_data['master'].get_absolute_url())
return render(request, 'dcim/virtualchassis_add.html', {
return render(request, 'dcim/virtualchassis_edit.html', {
'vc_form': vc_form,
'formset': formset,
'return_url': self.get_return_url(request, virtual_chassis),
@ -2169,3 +2169,58 @@ class VirtualChassisDeleteView(PermissionRequiredMixin, ObjectDeleteView):
permission_required = 'dcim.delete_virtualchassis'
model = VirtualChassis
default_return_url = 'dcim:device_list'
class VirtualChassisAddMemberView(PermissionRequiredMixin, GetReturnURLMixin, View):
permission_required = 'dcim.change_device'
def get(self, request, pk):
virtual_chassis = get_object_or_404(VirtualChassis, pk=pk)
initial_data = {k: request.GET[k] for k in request.GET}
member_select_form = forms.VCMemberSelectForm(initial=initial_data)
membership_form = forms.DeviceVCMembershipForm(initial=initial_data)
return render(request, 'dcim/virtualchassis_add_member.html', {
'virtual_chassis': virtual_chassis,
'member_select_form': member_select_form,
'membership_form': membership_form,
'return_url': self.get_return_url(request, virtual_chassis),
})
def post(self, request, pk):
virtual_chassis = get_object_or_404(VirtualChassis, pk=pk)
member_select_form = forms.VCMemberSelectForm(request.POST)
if member_select_form.is_valid():
device = member_select_form.cleaned_data['device']
device.virtual_chassis = virtual_chassis
data = {k: request.POST[k] for k in ['vc_position', 'vc_priority']}
membership_form = forms.DeviceVCMembershipForm(data, instance=device)
if membership_form.is_valid():
membership_form.save()
msg = 'Added member <a href="{}">{}</a>'.format(device.get_absolute_url(), escape(device))
messages.success(request, mark_safe(msg))
UserAction.objects.log_edit(request.user, device, msg)
if '_addanother' in request.POST:
return redirect(request.get_full_path())
return redirect(self.get_return_url(request, device))
else:
membership_form = forms.DeviceVCMembershipForm(request.POST)
return render(request, 'dcim/virtualchassis_add_member.html', {
'virtual_chassis': virtual_chassis,
'member_select_form': member_select_form,
'membership_form': membership_form,
'return_url': self.get_return_url(request, virtual_chassis),
})

View File

@ -0,0 +1,35 @@
{% extends '_base.html' %}
{% load form_helpers %}
{% block content %}
<form action="" method="post" enctype="multipart/form-data" class="form form-horizontal">
{% csrf_token %}
<div class="row">
<div class="col-md-6 col-md-offset-3">
<h3>{% block title %}Add New Member to Virtual Chassis {{ virtual_chassis }}{% endblock %}</h3>
{% if membership_form.non_field_errors %}
<div class="panel panel-danger">
<div class="panel-heading"><strong>Errors</strong></div>
<div class="panel-body">
{{ membership_form.non_field_errors }}
</div>
</div>
{% endif %}
<div class="panel panel-default">
<div class="panel-heading"><strong>Add New Member</strong></div>
<div class="table panel-body">
{% render_form member_select_form %}
{% render_form membership_form %}
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6 col-md-offset-3 text-right">
<button type="submit" name="_save" class="btn btn-primary">Save</button>
<button type="submit" name="_addanother" class="btn btn-primary">Add Another</button>
<a href="{{ return_url }}" class="btn btn-default">Cancel</a>
</div>
</div>
</form>
{% endblock %}

View File

@ -35,8 +35,8 @@
</thead>
<tbody>
{% for form in formset %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% for field in form.hidden_fields %}
{{ field }}
{% endfor %}
<tr>
<td>{{ form.instance.name }}</td>