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:
@ -6,3 +6,6 @@ from django.apps import AppConfig
|
||||
class DCIMConfig(AppConfig):
|
||||
name = "dcim"
|
||||
verbose_name = "DCIM"
|
||||
|
||||
def ready(self):
|
||||
import dcim.signals
|
||||
|
@ -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
|
@ -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
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/(?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'),
|
||||
|
||||
]
|
||||
|
@ -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),
|
||||
})
|
||||
|
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>
|
||||
<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>
|
||||
|
Reference in New Issue
Block a user