mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Merge v2.9.4 release
This commit is contained in:
@@ -641,11 +641,11 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldModel
|
||||
self.initial['primary_for_parent'] = True
|
||||
|
||||
def clean(self):
|
||||
super().clean()
|
||||
|
||||
# Cannot select both a device interface and a VM interface
|
||||
if self.cleaned_data.get('interface') and self.cleaned_data.get('vminterface'):
|
||||
raise forms.ValidationError("Cannot select both a device interface and a virtual machine interface")
|
||||
self.instance.assigned_object = self.cleaned_data.get('interface') or self.cleaned_data.get('vminterface')
|
||||
|
||||
# Primary IP assignment is only available if an interface has been assigned.
|
||||
interface = self.cleaned_data.get('interface') or self.cleaned_data.get('vminterface')
|
||||
@@ -655,26 +655,21 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldModel
|
||||
)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
|
||||
# Set assigned object
|
||||
interface = self.cleaned_data.get('interface') or self.cleaned_data.get('vminterface')
|
||||
if interface:
|
||||
self.instance.assigned_object = interface
|
||||
|
||||
ipaddress = super().save(*args, **kwargs)
|
||||
|
||||
# Assign/clear this IPAddress as the primary for the associated Device/VirtualMachine.
|
||||
interface = self.instance.assigned_object
|
||||
if interface and self.cleaned_data['primary_for_parent']:
|
||||
if ipaddress.address.version == 4:
|
||||
interface.parent.primary_ip4 = ipaddress
|
||||
else:
|
||||
interface.primary_ip6 = ipaddress
|
||||
interface.parent.primary_ip6 = ipaddress
|
||||
interface.parent.save()
|
||||
elif interface and ipaddress.address.version == 4 and interface.parent.primary_ip4 == ipaddress:
|
||||
interface.parent.primary_ip4 = None
|
||||
interface.parent.save()
|
||||
elif interface and ipaddress.address.version == 6 and interface.parent.primary_ip6 == ipaddress:
|
||||
interface.parent.primary_ip4 = None
|
||||
interface.parent.primary_ip6 = None
|
||||
interface.parent.save()
|
||||
|
||||
return ipaddress
|
||||
|
@@ -707,30 +707,18 @@ class IPAddress(ChangeLoggedModel, CustomFieldModel):
|
||||
})
|
||||
|
||||
# Check for primary IP assignment that doesn't match the assigned device/VM
|
||||
if self.pk and type(self.assigned_object) is Interface:
|
||||
if self.pk:
|
||||
device = Device.objects.filter(Q(primary_ip4=self) | Q(primary_ip6=self)).first()
|
||||
if device:
|
||||
if self.assigned_object is None:
|
||||
if getattr(self.assigned_object, 'device', None) != device:
|
||||
raise ValidationError({
|
||||
'interface': f"IP address is primary for device {device} but not assigned to an interface"
|
||||
'interface': f"IP address is primary for device {device} but not assigned to it!"
|
||||
})
|
||||
elif self.assigned_object.device != device:
|
||||
raise ValidationError({
|
||||
'interface': f"IP address is primary for device {device} but assigned to "
|
||||
f"{self.assigned_object.device} ({self.assigned_object})"
|
||||
})
|
||||
elif self.pk and type(self.assigned_object) is VMInterface:
|
||||
vm = VirtualMachine.objects.filter(Q(primary_ip4=self) | Q(primary_ip6=self)).first()
|
||||
if vm:
|
||||
if self.assigned_object is None:
|
||||
if getattr(self.assigned_object, 'virtual_machine', None) != vm:
|
||||
raise ValidationError({
|
||||
'vminterface': f"IP address is primary for virtual machine {vm} but not assigned to an "
|
||||
f"interface"
|
||||
})
|
||||
elif self.assigned_object.virtual_machine != vm:
|
||||
raise ValidationError({
|
||||
'vminterface': f"IP address is primary for virtual machine {vm} but assigned to "
|
||||
f"{self.assigned_object.virtual_machine} ({self.assigned_object})"
|
||||
'vminterface': f"IP address is primary for virtual machine {vm} but not assigned to it!"
|
||||
})
|
||||
|
||||
# Validate IP status selection
|
||||
@@ -973,13 +961,20 @@ class VLAN(ChangeLoggedModel, CustomFieldModel):
|
||||
def get_status_class(self):
|
||||
return self.STATUS_CLASS_MAP[self.status]
|
||||
|
||||
def get_members(self):
|
||||
# Return all interfaces assigned to this VLAN
|
||||
def get_interfaces(self):
|
||||
# Return all device interfaces assigned to this VLAN
|
||||
return Interface.objects.filter(
|
||||
Q(untagged_vlan_id=self.pk) |
|
||||
Q(tagged_vlans=self.pk)
|
||||
).distinct()
|
||||
|
||||
def get_vminterfaces(self):
|
||||
# Return all VM interfaces assigned to this VLAN
|
||||
return VMInterface.objects.filter(
|
||||
Q(untagged_vlan_id=self.pk) |
|
||||
Q(tagged_vlans=self.pk)
|
||||
).distinct()
|
||||
|
||||
|
||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
||||
class Service(ChangeLoggedModel, CustomFieldModel):
|
||||
|
@@ -4,6 +4,7 @@ from django_tables2.utils import Accessor
|
||||
from dcim.models import Interface
|
||||
from tenancy.tables import COL_TENANT
|
||||
from utilities.tables import BaseTable, BooleanColumn, ButtonsColumn, TagColumn, ToggleColumn
|
||||
from virtualization.models import VMInterface
|
||||
from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
|
||||
|
||||
RIR_UTILIZATION = """
|
||||
@@ -124,9 +125,11 @@ VLANGROUP_ADD_VLAN = """
|
||||
{% endwith %}
|
||||
"""
|
||||
|
||||
VLAN_MEMBER_UNTAGGED = """
|
||||
VLAN_MEMBER_TAGGED = """
|
||||
{% if record.untagged_vlan_id == vlan.pk %}
|
||||
<i class="glyphicon glyphicon-ok">
|
||||
<span class="text-danger"><i class="fa fa-close"></i></span>
|
||||
{% else %}
|
||||
<span class="text-success"><i class="fa fa-check"></i></span>
|
||||
{% endif %}
|
||||
"""
|
||||
|
||||
@@ -415,7 +418,7 @@ class IPAddressDetailTable(IPAddressTable):
|
||||
tenant = tables.TemplateColumn(
|
||||
template_code=COL_TENANT
|
||||
)
|
||||
assigned = tables.BooleanColumn(
|
||||
assigned = BooleanColumn(
|
||||
accessor='assigned_object_id',
|
||||
verbose_name='Assigned'
|
||||
)
|
||||
@@ -553,15 +556,15 @@ class VLANDetailTable(VLANTable):
|
||||
default_columns = ('pk', 'vid', 'site', 'group', 'name', 'prefixes', 'tenant', 'status', 'role', 'description')
|
||||
|
||||
|
||||
class VLANMemberTable(BaseTable):
|
||||
parent = tables.LinkColumn(
|
||||
order_by=['device', 'virtual_machine']
|
||||
)
|
||||
class VLANMembersTable(BaseTable):
|
||||
"""
|
||||
Base table for Interface and VMInterface assignments
|
||||
"""
|
||||
name = tables.LinkColumn(
|
||||
verbose_name='Interface'
|
||||
)
|
||||
untagged = tables.TemplateColumn(
|
||||
template_code=VLAN_MEMBER_UNTAGGED,
|
||||
tagged = tables.TemplateColumn(
|
||||
template_code=VLAN_MEMBER_TAGGED,
|
||||
orderable=False
|
||||
)
|
||||
actions = tables.TemplateColumn(
|
||||
@@ -570,9 +573,21 @@ class VLANMemberTable(BaseTable):
|
||||
verbose_name=''
|
||||
)
|
||||
|
||||
|
||||
class VLANDevicesTable(VLANMembersTable):
|
||||
device = tables.LinkColumn()
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
model = Interface
|
||||
fields = ('parent', 'name', 'untagged', 'actions')
|
||||
fields = ('device', 'name', 'tagged', 'actions')
|
||||
|
||||
|
||||
class VLANVirtualMachinesTable(VLANMembersTable):
|
||||
virtual_machine = tables.LinkColumn()
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
model = VMInterface
|
||||
fields = ('virtual_machine', 'name', 'tagged', 'actions')
|
||||
|
||||
|
||||
class InterfaceVLANTable(BaseTable):
|
||||
|
@@ -90,7 +90,8 @@ urlpatterns = [
|
||||
path('vlans/edit/', views.VLANBulkEditView.as_view(), name='vlan_bulk_edit'),
|
||||
path('vlans/delete/', views.VLANBulkDeleteView.as_view(), name='vlan_bulk_delete'),
|
||||
path('vlans/<int:pk>/', views.VLANView.as_view(), name='vlan'),
|
||||
path('vlans/<int:pk>/members/', views.VLANMembersView.as_view(), name='vlan_members'),
|
||||
path('vlans/<int:pk>/interfaces/', views.VLANInterfacesView.as_view(), name='vlan_interfaces'),
|
||||
path('vlans/<int:pk>/vm-interfaces/', views.VLANVMInterfacesView.as_view(), name='vlan_vminterfaces'),
|
||||
path('vlans/<int:pk>/edit/', views.VLANEditView.as_view(), name='vlan_edit'),
|
||||
path('vlans/<int:pk>/delete/', views.VLANDeleteView.as_view(), name='vlan_delete'),
|
||||
path('vlans/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='vlan_changelog', kwargs={'model': VLAN}),
|
||||
|
@@ -749,15 +749,13 @@ class VLANView(ObjectView):
|
||||
})
|
||||
|
||||
|
||||
class VLANMembersView(ObjectView):
|
||||
class VLANInterfacesView(ObjectView):
|
||||
queryset = VLAN.objects.all()
|
||||
|
||||
def get(self, request, pk):
|
||||
|
||||
vlan = get_object_or_404(self.queryset, pk=pk)
|
||||
members = vlan.get_members().restrict(request.user, 'view').prefetch_related('device', 'virtual_machine')
|
||||
|
||||
members_table = tables.VLANMemberTable(members)
|
||||
interfaces = vlan.get_interfaces().prefetch_related('device')
|
||||
members_table = tables.VLANDevicesTable(interfaces)
|
||||
|
||||
paginate = {
|
||||
'paginator_class': EnhancedPaginator,
|
||||
@@ -765,10 +763,31 @@ class VLANMembersView(ObjectView):
|
||||
}
|
||||
RequestConfig(request, paginate).configure(members_table)
|
||||
|
||||
return render(request, 'ipam/vlan_members.html', {
|
||||
return render(request, 'ipam/vlan_interfaces.html', {
|
||||
'vlan': vlan,
|
||||
'members_table': members_table,
|
||||
'active_tab': 'members',
|
||||
'active_tab': 'interfaces',
|
||||
})
|
||||
|
||||
|
||||
class VLANVMInterfacesView(ObjectView):
|
||||
queryset = VLAN.objects.all()
|
||||
|
||||
def get(self, request, pk):
|
||||
vlan = get_object_or_404(self.queryset, pk=pk)
|
||||
interfaces = vlan.get_vminterfaces().prefetch_related('virtual_machine')
|
||||
members_table = tables.VLANVirtualMachinesTable(interfaces)
|
||||
|
||||
paginate = {
|
||||
'paginator_class': EnhancedPaginator,
|
||||
'per_page': request.GET.get('per_page', settings.PAGINATE_COUNT)
|
||||
}
|
||||
RequestConfig(request, paginate).configure(members_table)
|
||||
|
||||
return render(request, 'ipam/vlan_vminterfaces.html', {
|
||||
'vlan': vlan,
|
||||
'members_table': members_table,
|
||||
'active_tab': 'vminterfaces',
|
||||
})
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user