mirror of
https://github.com/netbox-community/netbox.git
synced 2025-08-25 08:46:10 -06:00
Merge branch 'develop' into 12489-site-locations
This commit is contained in:
commit
183a7ae76c
@ -4,9 +4,19 @@
|
|||||||
|
|
||||||
### Enhancements
|
### Enhancements
|
||||||
|
|
||||||
|
* [#11670](https://github.com/netbox-community/netbox/issues/11670) - Enable setting device type & module type weight via bulk import
|
||||||
|
* [#11900](https://github.com/netbox-community/netbox/issues/11900) - Add an outline to the reservation markers on rack elevations
|
||||||
|
* [#12131](https://github.com/netbox-community/netbox/issues/12131) - Show custom field description as an icon tooltip under object views
|
||||||
* [#12223](https://github.com/netbox-community/netbox/issues/12223) - Add columns for parent device bay and position to devices list
|
* [#12223](https://github.com/netbox-community/netbox/issues/12223) - Add columns for parent device bay and position to devices list
|
||||||
|
* [#12233](https://github.com/netbox-community/netbox/issues/12233) - Move related IP addresses table to a separate tab
|
||||||
|
* [#12286](https://github.com/netbox-community/netbox/issues/12286) - Show height and total weight under device view
|
||||||
|
* [#12323](https://github.com/netbox-community/netbox/issues/12323) - Add 100GE CXP interface type
|
||||||
* [#12498](https://github.com/netbox-community/netbox/issues/12498) - Hide map button if `MAPS_URL` is empty
|
* [#12498](https://github.com/netbox-community/netbox/issues/12498) - Hide map button if `MAPS_URL` is empty
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* [#12550](https://github.com/netbox-community/netbox/issues/12550) - Fix rear port selection widget under front port creation form
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## v3.5.1 (2023-05-05)
|
## v3.5.1 (2023-05-05)
|
||||||
|
@ -242,6 +242,7 @@ class FrontPortCreateForm(ComponentCreateForm, model_forms.FrontPortForm):
|
|||||||
choices=[],
|
choices=[],
|
||||||
label=_('Rear ports'),
|
label=_('Rear ports'),
|
||||||
help_text=_('Select one rear port assignment for each front port being created.'),
|
help_text=_('Select one rear port assignment for each front port being created.'),
|
||||||
|
widget=forms.SelectMultiple(attrs={'size': 6})
|
||||||
)
|
)
|
||||||
|
|
||||||
# Override fieldsets from FrontPortForm to omit rear_port_position
|
# Override fieldsets from FrontPortForm to omit rear_port_position
|
||||||
|
@ -783,6 +783,14 @@ class IPAddress(PrimaryModel):
|
|||||||
if available_ips:
|
if available_ips:
|
||||||
return next(iter(available_ips))
|
return next(iter(available_ips))
|
||||||
|
|
||||||
|
def get_related_ips(self):
|
||||||
|
"""
|
||||||
|
Return all IPAddresses belonging to the same VRF.
|
||||||
|
"""
|
||||||
|
return IPAddress.objects.exclude(address=str(self.address)).filter(
|
||||||
|
vrf=self.vrf, address__net_contained_or_equal=str(self.address)
|
||||||
|
)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
super().clean()
|
super().clean()
|
||||||
|
|
||||||
|
@ -755,19 +755,9 @@ class IPAddressView(generic.ObjectView):
|
|||||||
# Limit to a maximum of 10 duplicates displayed here
|
# Limit to a maximum of 10 duplicates displayed here
|
||||||
duplicate_ips_table = tables.IPAddressTable(duplicate_ips[:10], orderable=False)
|
duplicate_ips_table = tables.IPAddressTable(duplicate_ips[:10], orderable=False)
|
||||||
|
|
||||||
# Related IP table
|
|
||||||
related_ips = IPAddress.objects.restrict(request.user, 'view').exclude(
|
|
||||||
address=str(instance.address)
|
|
||||||
).filter(
|
|
||||||
vrf=instance.vrf, address__net_contained_or_equal=str(instance.address)
|
|
||||||
)
|
|
||||||
related_ips_table = tables.IPAddressTable(related_ips, orderable=False)
|
|
||||||
related_ips_table.configure(request)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'parent_prefixes_table': parent_prefixes_table,
|
'parent_prefixes_table': parent_prefixes_table,
|
||||||
'duplicate_ips_table': duplicate_ips_table,
|
'duplicate_ips_table': duplicate_ips_table,
|
||||||
'related_ips_table': related_ips_table,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -872,6 +862,24 @@ class IPAddressBulkDeleteView(generic.BulkDeleteView):
|
|||||||
table = tables.IPAddressTable
|
table = tables.IPAddressTable
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(IPAddress, 'related_ips', path='related-ip-addresses')
|
||||||
|
class IPAddressRelatedIPsView(generic.ObjectChildrenView):
|
||||||
|
queryset = IPAddress.objects.prefetch_related('vrf__tenant', 'tenant')
|
||||||
|
child_model = IPAddress
|
||||||
|
table = tables.IPAddressTable
|
||||||
|
filterset = filtersets.IPAddressFilterSet
|
||||||
|
template_name = 'ipam/ipaddress/ip_addresses.html'
|
||||||
|
tab = ViewTab(
|
||||||
|
label=_('Related IPs'),
|
||||||
|
badge=lambda x: x.get_related_ips().count(),
|
||||||
|
weight=500,
|
||||||
|
hide_if_empty=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_children(self, request, parent):
|
||||||
|
return parent.get_related_ips().restrict(request.user, 'view')
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# VLAN groups
|
# VLAN groups
|
||||||
#
|
#
|
||||||
|
@ -3,13 +3,6 @@
|
|||||||
{% load plugins %}
|
{% load plugins %}
|
||||||
{% load render_table from django_tables2 %}
|
{% load render_table from django_tables2 %}
|
||||||
|
|
||||||
{% block breadcrumbs %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% if object.vrf %}
|
|
||||||
<li class="breadcrumb-item"><a href="{% url 'ipam:ipaddress_list' %}?vrf_id={{ object.vrf.pk }}">{{ object.vrf }}</a></li>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col col-md-4">
|
<div class="col col-md-4">
|
||||||
@ -116,7 +109,6 @@
|
|||||||
{% if duplicate_ips_table.rows %}
|
{% if duplicate_ips_table.rows %}
|
||||||
{% include 'inc/panel_table.html' with table=duplicate_ips_table heading='Duplicate IPs' panel_class='danger' %}
|
{% include 'inc/panel_table.html' with table=duplicate_ips_table heading='Duplicate IPs' panel_class='danger' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% include 'inc/panel_table.html' with table=related_ips_table heading='Related IPs' %}
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h5 class="card-header">Services</h5>
|
<h5 class="card-header">Services</h5>
|
||||||
<div class="card-body htmx-container table-responsive"
|
<div class="card-body htmx-container table-responsive"
|
||||||
|
8
netbox/templates/ipam/ipaddress/base.html
Normal file
8
netbox/templates/ipam/ipaddress/base.html
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{% extends 'generic/object.html' %}
|
||||||
|
|
||||||
|
{% block breadcrumbs %}
|
||||||
|
{{ block.super }}
|
||||||
|
{% if object.vrf %}
|
||||||
|
<li class="breadcrumb-item"><a href="{% url 'ipam:ipaddress_list' %}?vrf_id={{ object.vrf.pk }}">{{ object.vrf }}</a></li>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
19
netbox/templates/ipam/ipaddress/ip_addresses.html
Normal file
19
netbox/templates/ipam/ipaddress/ip_addresses.html
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{% extends 'ipam/ipaddress/base.html' %}
|
||||||
|
{% load helpers %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% include 'inc/table_controls_htmx.html' with table_modal="IPAddressTable_config" %}
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body" id="object_list">
|
||||||
|
{% include 'htmx/table.html' %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endblock content %}
|
||||||
|
|
||||||
|
{% block modals %}
|
||||||
|
{{ block.super }}
|
||||||
|
{% table_config_form table %}
|
||||||
|
{% endblock modals %}
|
@ -32,11 +32,11 @@ class BootstrapMixin:
|
|||||||
elif isinstance(field.widget, forms.CheckboxInput):
|
elif isinstance(field.widget, forms.CheckboxInput):
|
||||||
field.widget.attrs['class'] = f'{css} form-check-input'
|
field.widget.attrs['class'] = f'{css} form-check-input'
|
||||||
|
|
||||||
elif isinstance(field.widget, forms.SelectMultiple):
|
elif isinstance(field.widget, forms.SelectMultiple) and 'size' in field.widget.attrs:
|
||||||
if 'size' not in field.widget.attrs:
|
# Use native Bootstrap class for multi-line <select> widgets
|
||||||
field.widget.attrs['class'] = f'{css} netbox-static-select'
|
field.widget.attrs['class'] = f'{css} form-select form-select-sm'
|
||||||
|
|
||||||
elif isinstance(field.widget, forms.Select):
|
elif isinstance(field.widget, (forms.Select, forms.SelectMultiple)):
|
||||||
field.widget.attrs['class'] = f'{css} netbox-static-select'
|
field.widget.attrs['class'] = f'{css} netbox-static-select'
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
Loading…
Reference in New Issue
Block a user