mirror of
https://github.com/netbox-community/netbox.git
synced 2025-12-21 21:02:23 -06:00
Merge develop into develop-2.10
This commit is contained in:
@@ -814,6 +814,9 @@ class InterfaceModeChoices(ChoiceSet):
|
||||
class PortTypeChoices(ChoiceSet):
|
||||
|
||||
TYPE_8P8C = '8p8c'
|
||||
TYPE_8P6C = '8p6c'
|
||||
TYPE_8P4C = '8p4c'
|
||||
TYPE_8P2C = '8p2c'
|
||||
TYPE_110_PUNCH = '110-punch'
|
||||
TYPE_BNC = 'bnc'
|
||||
TYPE_MRJ21 = 'mrj21'
|
||||
@@ -833,6 +836,9 @@ class PortTypeChoices(ChoiceSet):
|
||||
'Copper',
|
||||
(
|
||||
(TYPE_8P8C, '8P8C'),
|
||||
(TYPE_8P6C, '8P6C'),
|
||||
(TYPE_8P4C, '8P4C'),
|
||||
(TYPE_8P2C, '8P2C'),
|
||||
(TYPE_110_PUNCH, '110 Punch'),
|
||||
(TYPE_BNC, 'BNC'),
|
||||
(TYPE_MRJ21, 'MRJ21'),
|
||||
|
||||
@@ -94,8 +94,12 @@ class RackElevationSVG:
|
||||
|
||||
# Embed front device type image if one exists
|
||||
if self.include_images and device.device_type.front_image:
|
||||
url = '{}{}'.format(self.base_url, device.device_type.front_image.url)
|
||||
image = drawing.image(href=url, insert=start, size=end, class_='device-image')
|
||||
image = drawing.image(
|
||||
href=device.device_type.front_image.url,
|
||||
insert=start,
|
||||
size=end,
|
||||
class_='device-image'
|
||||
)
|
||||
image.fit(scale='slice')
|
||||
link.add(image)
|
||||
|
||||
@@ -107,8 +111,12 @@ class RackElevationSVG:
|
||||
|
||||
# Embed rear device type image if one exists
|
||||
if self.include_images and device.device_type.rear_image:
|
||||
url = device.device_type.rear_image.url
|
||||
image = drawing.image(href=url, insert=start, size=end, class_='device-image')
|
||||
image = drawing.image(
|
||||
href=device.device_type.rear_image.url,
|
||||
insert=start,
|
||||
size=end,
|
||||
class_='device-image'
|
||||
)
|
||||
image.fit(scale='slice')
|
||||
drawing.add(image)
|
||||
|
||||
@@ -141,7 +149,7 @@ class RackElevationSVG:
|
||||
unit_cursor = 0
|
||||
for u in elevation:
|
||||
o = other[unit_cursor]
|
||||
if not u['device'] and o['device']:
|
||||
if not u['device'] and o['device'] and o['device'].device_type.is_full_depth:
|
||||
u['device'] = o['device']
|
||||
u['height'] = 1
|
||||
unit_cursor += u.get('height', 1)
|
||||
|
||||
@@ -1811,7 +1811,7 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
|
||||
nat_inside__assigned_object_id__in=interface_ids
|
||||
).prefetch_related('assigned_object')
|
||||
if nat_ips:
|
||||
ip_list = [(ip.id, f'{ip.address} ({ip.assigned_object})') for ip in nat_ips]
|
||||
ip_list = [(ip.id, f'{ip.address} (NAT)') for ip in nat_ips]
|
||||
ip_choices.append(('NAT IPs', ip_list))
|
||||
self.fields['primary_ip{}'.format(family)].choices = ip_choices
|
||||
|
||||
@@ -2317,7 +2317,7 @@ class ConsoleServerPortForm(BootstrapMixin, forms.ModelForm):
|
||||
class Meta:
|
||||
model = ConsoleServerPort
|
||||
fields = [
|
||||
'device', 'name', 'type', 'description', 'tags',
|
||||
'device', 'name', 'label', 'type', 'description', 'tags',
|
||||
]
|
||||
widgets = {
|
||||
'device': forms.HiddenInput(),
|
||||
@@ -2390,7 +2390,7 @@ class PowerPortForm(BootstrapMixin, forms.ModelForm):
|
||||
class Meta:
|
||||
model = PowerPort
|
||||
fields = [
|
||||
'device', 'name', 'type', 'maximum_draw', 'allocated_draw', 'description', 'tags',
|
||||
'device', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description', 'tags',
|
||||
]
|
||||
widgets = {
|
||||
'device': forms.HiddenInput(),
|
||||
@@ -2479,7 +2479,7 @@ class PowerOutletForm(BootstrapMixin, forms.ModelForm):
|
||||
class Meta:
|
||||
model = PowerOutlet
|
||||
fields = [
|
||||
'device', 'name', 'type', 'power_port', 'feed_leg', 'description', 'tags',
|
||||
'device', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description', 'tags',
|
||||
]
|
||||
widgets = {
|
||||
'device': forms.HiddenInput(),
|
||||
@@ -2686,7 +2686,10 @@ class InterfaceForm(InterfaceCommonForm, BootstrapMixin, forms.ModelForm):
|
||||
device_query = Q(device=device)
|
||||
if device.virtual_chassis:
|
||||
device_query |= Q(device__virtual_chassis=device.virtual_chassis)
|
||||
self.fields['lag'].queryset = Interface.objects.filter(device_query, type=InterfaceTypeChoices.TYPE_LAG)
|
||||
self.fields['lag'].queryset = Interface.objects.filter(
|
||||
device_query,
|
||||
type=InterfaceTypeChoices.TYPE_LAG
|
||||
).exclude(pk=self.instance.pk)
|
||||
|
||||
# Add current site to VLANs query params
|
||||
self.fields['untagged_vlan'].widget.add_query_param('site_id', device.site.pk)
|
||||
@@ -2876,17 +2879,22 @@ class InterfaceCSVForm(CSVModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Limit LAG choices to interfaces belonging to this device (or VC master)
|
||||
# Limit LAG choices to interfaces belonging to this device (or virtual chassis)
|
||||
device = None
|
||||
if self.is_bound and 'device' in self.data:
|
||||
try:
|
||||
device = self.fields['device'].to_python(self.data['device'])
|
||||
except forms.ValidationError:
|
||||
pass
|
||||
|
||||
if device:
|
||||
if device and device.virtual_chassis:
|
||||
self.fields['lag'].queryset = Interface.objects.filter(
|
||||
device__in=[device, device.get_vc_master()], type=InterfaceTypeChoices.TYPE_LAG
|
||||
Q(device=device) | Q(device__virtual_chassis=device.virtual_chassis),
|
||||
type=InterfaceTypeChoices.TYPE_LAG
|
||||
)
|
||||
elif device:
|
||||
self.fields['lag'].queryset = Interface.objects.filter(
|
||||
device=device,
|
||||
type=InterfaceTypeChoices.TYPE_LAG
|
||||
)
|
||||
else:
|
||||
self.fields['lag'].queryset = Interface.objects.none()
|
||||
|
||||
17
netbox/dcim/migrations/0115_rackreservation_order.py
Normal file
17
netbox/dcim/migrations/0115_rackreservation_order.py
Normal file
@@ -0,0 +1,17 @@
|
||||
# Generated by Django 3.1 on 2020-08-24 16:03
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('dcim', '0114_update_jsonfield'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='rackreservation',
|
||||
options={'ordering': ['created', 'pk']},
|
||||
),
|
||||
]
|
||||
@@ -702,18 +702,12 @@ class Interface(CableTermination, ComponentModel, BaseInterface):
|
||||
})
|
||||
|
||||
# A virtual interface cannot have a parent LAG
|
||||
if self.type in NONCONNECTABLE_IFACE_TYPES and self.lag is not None:
|
||||
raise ValidationError({
|
||||
'lag': "{} interfaces cannot have a parent LAG interface.".format(self.get_type_display())
|
||||
})
|
||||
if self.type == InterfaceTypeChoices.TYPE_VIRTUAL and self.lag is not None:
|
||||
raise ValidationError({'lag': "Virtual interfaces cannot have a parent LAG interface."})
|
||||
|
||||
# Only a LAG can have LAG members
|
||||
if self.type != InterfaceTypeChoices.TYPE_LAG and self.member_interfaces.exists():
|
||||
raise ValidationError({
|
||||
'type': "Cannot change interface type; it has LAG members ({}).".format(
|
||||
", ".join([iface.name for iface in self.member_interfaces.all()])
|
||||
)
|
||||
})
|
||||
# A LAG interface cannot be its own parent
|
||||
if self.pk and self.lag_id == self.pk:
|
||||
raise ValidationError({'lag': "A LAG interface cannot be its own parent."})
|
||||
|
||||
# Validate untagged VLAN
|
||||
if self.untagged_vlan and self.untagged_vlan.site not in [self.parent.site, None]:
|
||||
|
||||
@@ -633,7 +633,7 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
|
||||
# Check for a duplicate name on a device assigned to the same Site and no Tenant. This is necessary
|
||||
# because Django does not consider two NULL fields to be equal, and thus will not trigger a violation
|
||||
# of the uniqueness constraint without manual intervention.
|
||||
if self.name and self.tenant is None:
|
||||
if self.name and hasattr(self, 'site') and self.tenant is None:
|
||||
if Device.objects.exclude(pk=self.pk).filter(
|
||||
name=self.name,
|
||||
site=self.site,
|
||||
|
||||
@@ -600,7 +600,7 @@ class RackReservation(ChangeLoggedModel):
|
||||
csv_headers = ['site', 'rack_group', 'rack', 'units', 'tenant', 'user', 'description']
|
||||
|
||||
class Meta:
|
||||
ordering = ['created']
|
||||
ordering = ['created', 'pk']
|
||||
|
||||
def __str__(self):
|
||||
return "Reservation for rack {}".format(self.rack)
|
||||
|
||||
@@ -152,6 +152,10 @@ INTERFACE_TAGGED_VLANS = """
|
||||
{% endfor %}
|
||||
"""
|
||||
|
||||
CONNECTION_STATUS = """
|
||||
<span class="label label-{% if record.connection_status %}success{% else %}danger{% endif %}">{{ record.get_connection_status_display }}</span>
|
||||
"""
|
||||
|
||||
|
||||
#
|
||||
# Regions
|
||||
@@ -706,34 +710,48 @@ class DeviceComponentTable(BaseTable):
|
||||
|
||||
|
||||
class ConsolePortTable(DeviceComponentTable):
|
||||
tags = TagColumn(
|
||||
url_name='dcim:consoleport_list'
|
||||
)
|
||||
|
||||
class Meta(DeviceComponentTable.Meta):
|
||||
model = ConsolePort
|
||||
fields = ('pk', 'device', 'name', 'label', 'type', 'description', 'cable')
|
||||
fields = ('pk', 'device', 'name', 'label', 'type', 'description', 'cable', 'tags')
|
||||
default_columns = ('pk', 'device', 'name', 'label', 'type', 'description')
|
||||
|
||||
|
||||
class ConsoleServerPortTable(DeviceComponentTable):
|
||||
tags = TagColumn(
|
||||
url_name='dcim:consoleserverport_list'
|
||||
)
|
||||
|
||||
class Meta(DeviceComponentTable.Meta):
|
||||
model = ConsoleServerPort
|
||||
fields = ('pk', 'device', 'name', 'label', 'type', 'description', 'cable')
|
||||
fields = ('pk', 'device', 'name', 'label', 'type', 'description', 'cable', 'tags')
|
||||
default_columns = ('pk', 'device', 'name', 'label', 'type', 'description')
|
||||
|
||||
|
||||
class PowerPortTable(DeviceComponentTable):
|
||||
tags = TagColumn(
|
||||
url_name='dcim:powerport_list'
|
||||
)
|
||||
|
||||
class Meta(DeviceComponentTable.Meta):
|
||||
model = PowerPort
|
||||
fields = ('pk', 'device', 'name', 'label', 'type', 'description', 'maximum_draw', 'allocated_draw', 'cable')
|
||||
fields = (
|
||||
'pk', 'device', 'name', 'label', 'type', 'description', 'maximum_draw', 'allocated_draw', 'cable', 'tags',
|
||||
)
|
||||
default_columns = ('pk', 'device', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description')
|
||||
|
||||
|
||||
class PowerOutletTable(DeviceComponentTable):
|
||||
tags = TagColumn(
|
||||
url_name='dcim:poweroutlet_list'
|
||||
)
|
||||
|
||||
class Meta(DeviceComponentTable.Meta):
|
||||
model = PowerOutlet
|
||||
fields = ('pk', 'device', 'name', 'label', 'type', 'description', 'power_port', 'feed_leg', 'cable')
|
||||
fields = ('pk', 'device', 'name', 'label', 'type', 'description', 'power_port', 'feed_leg', 'cable', 'tags')
|
||||
default_columns = ('pk', 'device', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description')
|
||||
|
||||
|
||||
@@ -753,12 +771,15 @@ class BaseInterfaceTable(BaseTable):
|
||||
|
||||
|
||||
class InterfaceTable(DeviceComponentTable, BaseInterfaceTable):
|
||||
tags = TagColumn(
|
||||
url_name='dcim:interface_list'
|
||||
)
|
||||
|
||||
class Meta(DeviceComponentTable.Meta):
|
||||
model = Interface
|
||||
fields = (
|
||||
'pk', 'device', 'name', 'label', 'enabled', 'type', 'mgmt_only', 'mtu', 'mode', 'mac_address',
|
||||
'description', 'cable', 'ip_addresses', 'untagged_vlan', 'tagged_vlans',
|
||||
'description', 'cable', 'tags', 'ip_addresses', 'untagged_vlan', 'tagged_vlans',
|
||||
)
|
||||
default_columns = ('pk', 'device', 'name', 'label', 'enabled', 'type', 'description')
|
||||
|
||||
@@ -767,18 +788,26 @@ class FrontPortTable(DeviceComponentTable):
|
||||
rear_port_position = tables.Column(
|
||||
verbose_name='Position'
|
||||
)
|
||||
tags = TagColumn(
|
||||
url_name='dcim:frontport_list'
|
||||
)
|
||||
|
||||
class Meta(DeviceComponentTable.Meta):
|
||||
model = FrontPort
|
||||
fields = ('pk', 'device', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description', 'cable')
|
||||
fields = (
|
||||
'pk', 'device', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description', 'cable', 'tags',
|
||||
)
|
||||
default_columns = ('pk', 'device', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description')
|
||||
|
||||
|
||||
class RearPortTable(DeviceComponentTable):
|
||||
tags = TagColumn(
|
||||
url_name='dcim:rearport_list'
|
||||
)
|
||||
|
||||
class Meta(DeviceComponentTable.Meta):
|
||||
model = RearPort
|
||||
fields = ('pk', 'device', 'name', 'label', 'type', 'positions', 'description', 'cable')
|
||||
fields = ('pk', 'device', 'name', 'label', 'type', 'positions', 'description', 'cable', 'tags')
|
||||
default_columns = ('pk', 'device', 'name', 'label', 'type', 'description')
|
||||
|
||||
|
||||
@@ -786,10 +815,13 @@ class DeviceBayTable(DeviceComponentTable):
|
||||
installed_device = tables.Column(
|
||||
linkify=True
|
||||
)
|
||||
tags = TagColumn(
|
||||
url_name='dcim:devicebay_list'
|
||||
)
|
||||
|
||||
class Meta(DeviceComponentTable.Meta):
|
||||
model = DeviceBay
|
||||
fields = ('pk', 'device', 'name', 'label', 'installed_device', 'description')
|
||||
fields = ('pk', 'device', 'name', 'label', 'installed_device', 'description', 'tags')
|
||||
default_columns = ('pk', 'device', 'name', 'label', 'installed_device', 'description')
|
||||
|
||||
|
||||
@@ -798,12 +830,16 @@ class InventoryItemTable(DeviceComponentTable):
|
||||
linkify=True
|
||||
)
|
||||
discovered = BooleanColumn()
|
||||
tags = TagColumn(
|
||||
url_name='dcim:inventoryitem_list'
|
||||
)
|
||||
cable = None # Override DeviceComponentTable
|
||||
|
||||
class Meta(DeviceComponentTable.Meta):
|
||||
model = InventoryItem
|
||||
fields = (
|
||||
'pk', 'device', 'name', 'label', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'description',
|
||||
'discovered',
|
||||
'discovered', 'tags',
|
||||
)
|
||||
default_columns = ('pk', 'device', 'name', 'label', 'manufacturer', 'part_id', 'serial', 'asset_tag')
|
||||
|
||||
@@ -876,15 +912,20 @@ class ConsoleConnectionTable(BaseTable):
|
||||
verbose_name='Console Server'
|
||||
)
|
||||
connected_endpoint = tables.Column(
|
||||
linkify=True,
|
||||
verbose_name='Port'
|
||||
)
|
||||
device = tables.Column(
|
||||
linkify=True
|
||||
)
|
||||
name = tables.Column(
|
||||
linkify=True,
|
||||
verbose_name='Console Port'
|
||||
)
|
||||
connection_status = BooleanColumn()
|
||||
connection_status = tables.TemplateColumn(
|
||||
template_code=CONNECTION_STATUS,
|
||||
verbose_name='Status'
|
||||
)
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
model = ConsolePort
|
||||
@@ -901,14 +942,20 @@ class PowerConnectionTable(BaseTable):
|
||||
)
|
||||
outlet = tables.Column(
|
||||
accessor=Accessor('_connected_poweroutlet'),
|
||||
linkify=True,
|
||||
verbose_name='Outlet'
|
||||
)
|
||||
device = tables.Column(
|
||||
linkify=True
|
||||
)
|
||||
name = tables.Column(
|
||||
linkify=True,
|
||||
verbose_name='Power Port'
|
||||
)
|
||||
connection_status = tables.TemplateColumn(
|
||||
template_code=CONNECTION_STATUS,
|
||||
verbose_name='Status'
|
||||
)
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
model = PowerPort
|
||||
@@ -940,6 +987,10 @@ class InterfaceConnectionTable(BaseTable):
|
||||
args=[Accessor('_connected_interface__pk')],
|
||||
verbose_name='Interface B'
|
||||
)
|
||||
connection_status = tables.TemplateColumn(
|
||||
template_code=CONNECTION_STATUS,
|
||||
verbose_name='Status'
|
||||
)
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
model = Interface
|
||||
|
||||
@@ -1030,7 +1030,7 @@ class DeviceView(ObjectView):
|
||||
)
|
||||
|
||||
# Interfaces
|
||||
interfaces = device.vc_interfaces.restrict(request.user, 'view').filter(device=device).prefetch_related(
|
||||
interfaces = device.vc_interfaces.restrict(request.user, 'view').prefetch_related(
|
||||
Prefetch('ip_addresses', queryset=IPAddress.objects.restrict(request.user)),
|
||||
Prefetch('member_interfaces', queryset=Interface.objects.restrict(request.user)),
|
||||
'lag', '_connected_interface__device', '_connected_circuittermination__circuit', 'cable',
|
||||
@@ -1228,6 +1228,7 @@ class ConsolePortCreateView(ComponentCreateView):
|
||||
class ConsolePortEditView(ObjectEditView):
|
||||
queryset = ConsolePort.objects.all()
|
||||
model_form = forms.ConsolePortForm
|
||||
template_name = 'dcim/device_component_edit.html'
|
||||
|
||||
|
||||
class ConsolePortDeleteView(ObjectDeleteView):
|
||||
@@ -1287,6 +1288,7 @@ class ConsoleServerPortCreateView(ComponentCreateView):
|
||||
class ConsoleServerPortEditView(ObjectEditView):
|
||||
queryset = ConsoleServerPort.objects.all()
|
||||
model_form = forms.ConsoleServerPortForm
|
||||
template_name = 'dcim/device_component_edit.html'
|
||||
|
||||
|
||||
class ConsoleServerPortDeleteView(ObjectDeleteView):
|
||||
@@ -1346,6 +1348,7 @@ class PowerPortCreateView(ComponentCreateView):
|
||||
class PowerPortEditView(ObjectEditView):
|
||||
queryset = PowerPort.objects.all()
|
||||
model_form = forms.PowerPortForm
|
||||
template_name = 'dcim/device_component_edit.html'
|
||||
|
||||
|
||||
class PowerPortDeleteView(ObjectDeleteView):
|
||||
@@ -1405,6 +1408,7 @@ class PowerOutletCreateView(ComponentCreateView):
|
||||
class PowerOutletEditView(ObjectEditView):
|
||||
queryset = PowerOutlet.objects.all()
|
||||
model_form = forms.PowerOutletForm
|
||||
template_name = 'dcim/device_component_edit.html'
|
||||
|
||||
|
||||
class PowerOutletDeleteView(ObjectDeleteView):
|
||||
@@ -1556,6 +1560,7 @@ class FrontPortCreateView(ComponentCreateView):
|
||||
class FrontPortEditView(ObjectEditView):
|
||||
queryset = FrontPort.objects.all()
|
||||
model_form = forms.FrontPortForm
|
||||
template_name = 'dcim/device_component_edit.html'
|
||||
|
||||
|
||||
class FrontPortDeleteView(ObjectDeleteView):
|
||||
@@ -1615,6 +1620,7 @@ class RearPortCreateView(ComponentCreateView):
|
||||
class RearPortEditView(ObjectEditView):
|
||||
queryset = RearPort.objects.all()
|
||||
model_form = forms.RearPortForm
|
||||
template_name = 'dcim/device_component_edit.html'
|
||||
|
||||
|
||||
class RearPortDeleteView(ObjectDeleteView):
|
||||
@@ -1674,6 +1680,7 @@ class DeviceBayCreateView(ComponentCreateView):
|
||||
class DeviceBayEditView(ObjectEditView):
|
||||
queryset = DeviceBay.objects.all()
|
||||
model_form = forms.DeviceBayForm
|
||||
template_name = 'dcim/device_component_edit.html'
|
||||
|
||||
|
||||
class DeviceBayDeleteView(ObjectDeleteView):
|
||||
|
||||
Reference in New Issue
Block a user