mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-15 19:52:52 -06:00
Added virtual chassis member add view
This commit is contained in:
parent
a4019be28c
commit
f1da517c84
@ -6,3 +6,6 @@ from django.apps import AppConfig
|
|||||||
class DCIMConfig(AppConfig):
|
class DCIMConfig(AppConfig):
|
||||||
name = "dcim"
|
name = "dcim"
|
||||||
verbose_name = "DCIM"
|
verbose_name = "DCIM"
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
import dcim.signals
|
||||||
|
@ -2276,38 +2276,54 @@ class VirtualChassisForm(BootstrapMixin, forms.ModelForm):
|
|||||||
fields = ['master', 'domain']
|
fields = ['master', 'domain']
|
||||||
|
|
||||||
|
|
||||||
# class VCAddMemberForm(BootstrapMixin, ChainedFieldsMixin, forms.Form):
|
class VCMemberSelectForm(BootstrapMixin, ChainedFieldsMixin, forms.Form):
|
||||||
# site = forms.ModelChoiceField(
|
site = forms.ModelChoiceField(
|
||||||
# queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
# label='Site',
|
label='Site',
|
||||||
# required=False,
|
required=False,
|
||||||
# widget=forms.Select(
|
widget=forms.Select(
|
||||||
# attrs={'filter-for': 'rack'}
|
attrs={'filter-for': 'rack'}
|
||||||
# )
|
)
|
||||||
# )
|
)
|
||||||
# rack = ChainedModelChoiceField(
|
rack = ChainedModelChoiceField(
|
||||||
# queryset=Rack.objects.all(),
|
queryset=Rack.objects.all(),
|
||||||
# chains=(
|
chains=(
|
||||||
# ('site', 'site'),
|
('site', 'site'),
|
||||||
# ),
|
),
|
||||||
# label='Rack',
|
label='Rack',
|
||||||
# required=False,
|
required=False,
|
||||||
# widget=APISelect(
|
widget=APISelect(
|
||||||
# api_url='/api/dcim/racks/?site_id={{site}}',
|
api_url='/api/dcim/racks/?site_id={{site}}',
|
||||||
# attrs={'filter-for': 'device', 'nullable': 'true'}
|
attrs={'filter-for': 'device', 'nullable': 'true'}
|
||||||
# )
|
)
|
||||||
# )
|
)
|
||||||
# device = ChainedModelChoiceField(
|
device = ChainedModelChoiceField(
|
||||||
# queryset=Device.objects.all(),
|
queryset=Device.objects.all(),
|
||||||
# chains=(
|
chains=(
|
||||||
# ('site', 'site'),
|
('site', 'site'),
|
||||||
# ('rack', 'rack'),
|
('rack', 'rack'),
|
||||||
# ),
|
),
|
||||||
# label='Device',
|
label='Device',
|
||||||
# widget=APISelect(
|
widget=APISelect(
|
||||||
# api_url='/api/dcim/devices/?site_id={{site}}&rack_id={{rack}}',
|
api_url='/api/dcim/devices/?site_id={{site}}&rack_id={{rack}}',
|
||||||
# display_field='display_name'
|
display_field='display_name'
|
||||||
# )
|
)
|
||||||
# )
|
)
|
||||||
# vc_position = forms.IntegerField(label='Position')
|
|
||||||
# vc_priority = forms.IntegerField(required=False, label='Priority')
|
|
||||||
|
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
|
@ -1006,6 +1006,12 @@ class Device(CreatedUpdatedModel, CustomFieldModel):
|
|||||||
'cluster': "The assigned cluster belongs to a different site ({})".format(self.cluster.site)
|
'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):
|
def save(self, *args, **kwargs):
|
||||||
|
|
||||||
is_new = not bool(self.pk)
|
is_new = not bool(self.pk)
|
||||||
|
14
netbox/dcim/signals.py
Normal file
14
netbox/dcim/signals.py
Normal 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)
|
@ -220,6 +220,6 @@ urlpatterns = [
|
|||||||
url(r'^virtual-chassis/add/$', views.VirtualChassisCreateView.as_view(), name='virtualchassis_add'),
|
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+)/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+)/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'),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
@ -2158,7 +2158,7 @@ class VirtualChassisEditView(PermissionRequiredMixin, GetReturnURLMixin, View):
|
|||||||
|
|
||||||
return redirect(vc_form.cleaned_data['master'].get_absolute_url())
|
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,
|
'vc_form': vc_form,
|
||||||
'formset': formset,
|
'formset': formset,
|
||||||
'return_url': self.get_return_url(request, virtual_chassis),
|
'return_url': self.get_return_url(request, virtual_chassis),
|
||||||
@ -2169,3 +2169,58 @@ class VirtualChassisDeleteView(PermissionRequiredMixin, ObjectDeleteView):
|
|||||||
permission_required = 'dcim.delete_virtualchassis'
|
permission_required = 'dcim.delete_virtualchassis'
|
||||||
model = VirtualChassis
|
model = VirtualChassis
|
||||||
default_return_url = 'dcim:device_list'
|
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),
|
||||||
|
})
|
||||||
|
35
netbox/templates/dcim/virtualchassis_add_member.html
Normal file
35
netbox/templates/dcim/virtualchassis_add_member.html
Normal 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 %}
|
@ -35,8 +35,8 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for form in formset %}
|
{% for form in formset %}
|
||||||
{% for hidden in form.hidden_fields %}
|
{% for field in form.hidden_fields %}
|
||||||
{{ hidden }}
|
{{ field }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ form.instance.name }}</td>
|
<td>{{ form.instance.name }}</td>
|
||||||
|
Loading…
Reference in New Issue
Block a user