mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-22 12:06:53 -06:00
Merge branch 'develop' of github.com:netbox-community/netbox into 7090-fix-cablebulkedit-length-field
This commit is contained in:
commit
4618cc2b22
@ -1,11 +1,20 @@
|
|||||||
# NetBox v3.0
|
# NetBox v3.0
|
||||||
|
|
||||||
## v2.11.12 (2021-08-23)
|
## v3.0.1 (FUTURE)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
* [#7070](https://github.com/netbox-community/netbox/issues/7070) - Fix exception when filtering by prefix max length in UI
|
* [#7070](https://github.com/netbox-community/netbox/issues/7070) - Fix exception when filtering by prefix max length in UI
|
||||||
* [#7071](https://github.com/netbox-community/netbox/issues/7071) - Fix exception when removing a primary IP from a device/VM
|
* [#7071](https://github.com/netbox-community/netbox/issues/7071) - Fix exception when removing a primary IP from a device/VM
|
||||||
|
* [#7072](https://github.com/netbox-community/netbox/issues/7072) - Fix table configuration under prefix child object views
|
||||||
|
* [#7075](https://github.com/netbox-community/netbox/issues/7075) - Fix UI bug when a custom field has a space in the name
|
||||||
|
* [#7082](https://github.com/netbox-community/netbox/issues/7082) - Avoid exception when referencing invalid content type in table
|
||||||
|
* [#7083](https://github.com/netbox-community/netbox/issues/7083) - Correct labeling for VM memory attribute
|
||||||
|
* [#7084](https://github.com/netbox-community/netbox/issues/7084) - Fix KeyError exception when editing access VLAN on an interface
|
||||||
|
* [#7089](https://github.com/netbox-community/netbox/issues/7089) - Fix ContentTypeFilterSet not filtering on q filter
|
||||||
|
* [#7093](https://github.com/netbox-community/netbox/issues/7093) - Multi-select custom field filters should employ exact match
|
||||||
|
* [#7096](https://github.com/netbox-community/netbox/issues/7096) - Home links should honor `BASE_PATH` configuration
|
||||||
|
* [#7101](https://github.com/netbox-community/netbox/issues/7101) - Enforce `MAX_PAGE_SIZE` for table and REST API pagination
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -129,7 +129,7 @@ class InterfaceCommonForm(forms.Form):
|
|||||||
super().clean()
|
super().clean()
|
||||||
|
|
||||||
parent_field = 'device' if 'device' in self.cleaned_data else 'virtual_machine'
|
parent_field = 'device' if 'device' in self.cleaned_data else 'virtual_machine'
|
||||||
tagged_vlans = self.cleaned_data['tagged_vlans']
|
tagged_vlans = self.cleaned_data.get('tagged_vlans')
|
||||||
|
|
||||||
# Untagged interfaces cannot be assigned tagged VLANs
|
# Untagged interfaces cannot be assigned tagged VLANs
|
||||||
if self.cleaned_data['mode'] == InterfaceModeChoices.MODE_ACCESS and tagged_vlans:
|
if self.cleaned_data['mode'] == InterfaceModeChoices.MODE_ACCESS and tagged_vlans:
|
||||||
@ -142,7 +142,7 @@ class InterfaceCommonForm(forms.Form):
|
|||||||
self.cleaned_data['tagged_vlans'] = []
|
self.cleaned_data['tagged_vlans'] = []
|
||||||
|
|
||||||
# Validate tagged VLANs; must be a global VLAN or in the same site
|
# Validate tagged VLANs; must be a global VLAN or in the same site
|
||||||
elif self.cleaned_data['mode'] == InterfaceModeChoices.MODE_TAGGED:
|
elif self.cleaned_data['mode'] == InterfaceModeChoices.MODE_TAGGED and tagged_vlans:
|
||||||
valid_sites = [None, self.cleaned_data[parent_field].site]
|
valid_sites = [None, self.cleaned_data[parent_field].site]
|
||||||
invalid_vlans = [str(v) for v in tagged_vlans if v.site not in valid_sites]
|
invalid_vlans = [str(v) for v in tagged_vlans if v.site not in valid_sites]
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ EXACT_FILTER_TYPES = (
|
|||||||
CustomFieldTypeChoices.TYPE_DATE,
|
CustomFieldTypeChoices.TYPE_DATE,
|
||||||
CustomFieldTypeChoices.TYPE_INTEGER,
|
CustomFieldTypeChoices.TYPE_INTEGER,
|
||||||
CustomFieldTypeChoices.TYPE_SELECT,
|
CustomFieldTypeChoices.TYPE_SELECT,
|
||||||
|
CustomFieldTypeChoices.TYPE_MULTISELECT,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -35,7 +36,9 @@ class CustomFieldFilter(django_filters.Filter):
|
|||||||
|
|
||||||
self.field_name = f'custom_field_data__{self.field_name}'
|
self.field_name = f'custom_field_data__{self.field_name}'
|
||||||
|
|
||||||
if custom_field.type not in EXACT_FILTER_TYPES:
|
if custom_field.type == CustomFieldTypeChoices.TYPE_MULTISELECT:
|
||||||
|
self.lookup_expr = 'has_key'
|
||||||
|
elif custom_field.type not in EXACT_FILTER_TYPES:
|
||||||
if custom_field.filter_logic == CustomFieldFilterLogicChoices.FILTER_LOOSE:
|
if custom_field.filter_logic == CustomFieldFilterLogicChoices.FILTER_LOOSE:
|
||||||
self.lookup_expr = 'icontains'
|
self.lookup_expr = 'icontains'
|
||||||
|
|
||||||
|
@ -367,7 +367,19 @@ class JobResultFilterSet(BaseFilterSet):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class ContentTypeFilterSet(django_filters.FilterSet):
|
class ContentTypeFilterSet(django_filters.FilterSet):
|
||||||
|
q = django_filters.CharFilter(
|
||||||
|
method='search',
|
||||||
|
label='Search',
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ContentType
|
model = ContentType
|
||||||
fields = ['id', 'app_label', 'model']
|
fields = ['id', 'app_label', 'model']
|
||||||
|
|
||||||
|
def search(self, queryset, name, value):
|
||||||
|
if not value.strip():
|
||||||
|
return queryset
|
||||||
|
return queryset.filter(
|
||||||
|
Q(app_label__icontains=value) |
|
||||||
|
Q(model__icontains=value)
|
||||||
|
)
|
||||||
|
@ -681,7 +681,12 @@ class CustomFieldFilterTest(TestCase):
|
|||||||
cf.content_types.set([obj_type])
|
cf.content_types.set([obj_type])
|
||||||
|
|
||||||
# Selection filtering
|
# Selection filtering
|
||||||
cf = CustomField(name='cf8', type=CustomFieldTypeChoices.TYPE_URL, choices=['Foo', 'Bar', 'Baz'])
|
cf = CustomField(name='cf8', type=CustomFieldTypeChoices.TYPE_SELECT, choices=['Foo', 'Bar', 'Baz'])
|
||||||
|
cf.save()
|
||||||
|
cf.content_types.set([obj_type])
|
||||||
|
|
||||||
|
# Multiselect filtering
|
||||||
|
cf = CustomField(name='cf9', type=CustomFieldTypeChoices.TYPE_MULTISELECT, choices=['A', 'AA', 'B', 'C'])
|
||||||
cf.save()
|
cf.save()
|
||||||
cf.content_types.set([obj_type])
|
cf.content_types.set([obj_type])
|
||||||
|
|
||||||
@ -695,6 +700,7 @@ class CustomFieldFilterTest(TestCase):
|
|||||||
'cf6': 'http://foo.example.com/',
|
'cf6': 'http://foo.example.com/',
|
||||||
'cf7': 'http://foo.example.com/',
|
'cf7': 'http://foo.example.com/',
|
||||||
'cf8': 'Foo',
|
'cf8': 'Foo',
|
||||||
|
'cf9': ['A', 'B'],
|
||||||
}),
|
}),
|
||||||
Site(name='Site 2', slug='site-2', custom_field_data={
|
Site(name='Site 2', slug='site-2', custom_field_data={
|
||||||
'cf1': 200,
|
'cf1': 200,
|
||||||
@ -705,9 +711,9 @@ class CustomFieldFilterTest(TestCase):
|
|||||||
'cf6': 'http://bar.example.com/',
|
'cf6': 'http://bar.example.com/',
|
||||||
'cf7': 'http://bar.example.com/',
|
'cf7': 'http://bar.example.com/',
|
||||||
'cf8': 'Bar',
|
'cf8': 'Bar',
|
||||||
|
'cf9': ['AA', 'B'],
|
||||||
}),
|
}),
|
||||||
Site(name='Site 3', slug='site-3', custom_field_data={
|
Site(name='Site 3', slug='site-3'),
|
||||||
}),
|
|
||||||
])
|
])
|
||||||
|
|
||||||
def test_filter_integer(self):
|
def test_filter_integer(self):
|
||||||
@ -730,3 +736,10 @@ class CustomFieldFilterTest(TestCase):
|
|||||||
|
|
||||||
def test_filter_select(self):
|
def test_filter_select(self):
|
||||||
self.assertEqual(self.filterset({'cf_cf8': 'Foo'}, self.queryset).qs.count(), 1)
|
self.assertEqual(self.filterset({'cf_cf8': 'Foo'}, self.queryset).qs.count(), 1)
|
||||||
|
self.assertEqual(self.filterset({'cf_cf8': 'Bar'}, self.queryset).qs.count(), 1)
|
||||||
|
self.assertEqual(self.filterset({'cf_cf8': 'Baz'}, self.queryset).qs.count(), 0)
|
||||||
|
|
||||||
|
def test_filter_multiselect(self):
|
||||||
|
self.assertEqual(self.filterset({'cf_cf9': 'A'}, self.queryset).qs.count(), 1)
|
||||||
|
self.assertEqual(self.filterset({'cf_cf9': 'B'}, self.queryset).qs.count(), 2)
|
||||||
|
self.assertEqual(self.filterset({'cf_cf9': 'C'}, self.queryset).qs.count(), 0)
|
||||||
|
@ -404,12 +404,11 @@ class PrefixPrefixesView(generic.ObjectView):
|
|||||||
bulk_querystring = 'vrf_id={}&within={}'.format(instance.vrf.pk if instance.vrf else '0', instance.prefix)
|
bulk_querystring = 'vrf_id={}&within={}'.format(instance.vrf.pk if instance.vrf else '0', instance.prefix)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'first_available_prefix': instance.get_first_available_prefix(),
|
|
||||||
'table': table,
|
'table': table,
|
||||||
'bulk_querystring': bulk_querystring,
|
'bulk_querystring': bulk_querystring,
|
||||||
'active_tab': 'prefixes',
|
'active_tab': 'prefixes',
|
||||||
|
'first_available_prefix': instance.get_first_available_prefix(),
|
||||||
'show_available': request.GET.get('show_available', 'true') == 'true',
|
'show_available': request.GET.get('show_available', 'true') == 'true',
|
||||||
'table_config_form': TableConfigForm(table=table),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -421,7 +420,7 @@ class PrefixIPRangesView(generic.ObjectView):
|
|||||||
# Find all IPRanges belonging to this Prefix
|
# Find all IPRanges belonging to this Prefix
|
||||||
ip_ranges = instance.get_child_ranges().restrict(request.user, 'view').prefetch_related('vrf')
|
ip_ranges = instance.get_child_ranges().restrict(request.user, 'view').prefetch_related('vrf')
|
||||||
|
|
||||||
table = tables.IPRangeTable(ip_ranges)
|
table = tables.IPRangeTable(ip_ranges, user=request.user)
|
||||||
if request.user.has_perm('ipam.change_iprange') or request.user.has_perm('ipam.delete_iprange'):
|
if request.user.has_perm('ipam.change_iprange') or request.user.has_perm('ipam.delete_iprange'):
|
||||||
table.columns.show('pk')
|
table.columns.show('pk')
|
||||||
paginate_table(table, request)
|
paginate_table(table, request)
|
||||||
@ -449,7 +448,7 @@ class PrefixIPAddressesView(generic.ObjectView):
|
|||||||
if request.GET.get('show_available', 'true') == 'true':
|
if request.GET.get('show_available', 'true') == 'true':
|
||||||
ipaddresses = add_available_ipaddresses(instance.prefix, ipaddresses, instance.is_pool)
|
ipaddresses = add_available_ipaddresses(instance.prefix, ipaddresses, instance.is_pool)
|
||||||
|
|
||||||
table = tables.IPAddressTable(ipaddresses)
|
table = tables.IPAddressTable(ipaddresses, user=request.user)
|
||||||
if request.user.has_perm('ipam.change_ipaddress') or request.user.has_perm('ipam.delete_ipaddress'):
|
if request.user.has_perm('ipam.change_ipaddress') or request.user.has_perm('ipam.delete_ipaddress'):
|
||||||
table.columns.show('pk')
|
table.columns.show('pk')
|
||||||
paginate_table(table, request)
|
paginate_table(table, request)
|
||||||
@ -457,10 +456,10 @@ class PrefixIPAddressesView(generic.ObjectView):
|
|||||||
bulk_querystring = 'vrf_id={}&parent={}'.format(instance.vrf.pk if instance.vrf else '0', instance.prefix)
|
bulk_querystring = 'vrf_id={}&parent={}'.format(instance.vrf.pk if instance.vrf else '0', instance.prefix)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'first_available_ip': instance.get_first_available_ip(),
|
|
||||||
'table': table,
|
'table': table,
|
||||||
'bulk_querystring': bulk_querystring,
|
'bulk_querystring': bulk_querystring,
|
||||||
'active_tab': 'ip-addresses',
|
'active_tab': 'ip-addresses',
|
||||||
|
'first_available_ip': instance.get_first_available_ip(),
|
||||||
'show_available': request.GET.get('show_available', 'true') == 'true',
|
'show_available': request.GET.get('show_available', 'true') == 'true',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,23 +34,13 @@ class OptionalLimitOffsetPagination(LimitOffsetPagination):
|
|||||||
return list(queryset[self.offset:])
|
return list(queryset[self.offset:])
|
||||||
|
|
||||||
def get_limit(self, request):
|
def get_limit(self, request):
|
||||||
|
limit = super().get_limit(request)
|
||||||
|
|
||||||
if self.limit_query_param:
|
# Enforce maximum page size
|
||||||
try:
|
if settings.MAX_PAGE_SIZE:
|
||||||
limit = int(request.query_params[self.limit_query_param])
|
limit = min(limit, settings.MAX_PAGE_SIZE)
|
||||||
if limit < 0:
|
|
||||||
raise ValueError()
|
|
||||||
# Enforce maximum page size, if defined
|
|
||||||
if settings.MAX_PAGE_SIZE:
|
|
||||||
if limit == 0:
|
|
||||||
return settings.MAX_PAGE_SIZE
|
|
||||||
else:
|
|
||||||
return min(limit, settings.MAX_PAGE_SIZE)
|
|
||||||
return limit
|
|
||||||
except (KeyError, ValueError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
return self.default_limit
|
return limit
|
||||||
|
|
||||||
def get_next_link(self):
|
def get_next_link(self):
|
||||||
|
|
||||||
|
@ -560,6 +560,10 @@ RQ_QUEUES = {
|
|||||||
#
|
#
|
||||||
|
|
||||||
# Pagination
|
# Pagination
|
||||||
|
if MAX_PAGE_SIZE and PAGINATE_COUNT > MAX_PAGE_SIZE:
|
||||||
|
raise ImproperlyConfigured(
|
||||||
|
f"PAGINATE_COUNT ({PAGINATE_COUNT}) must be less than or equal to MAX_PAGE_SIZE ({MAX_PAGE_SIZE}), if set."
|
||||||
|
)
|
||||||
PER_PAGE_DEFAULTS = [
|
PER_PAGE_DEFAULTS = [
|
||||||
25, 50, 100, 250, 500, 1000
|
25, 50, 100, 250, 500, 1000
|
||||||
]
|
]
|
||||||
|
@ -181,7 +181,6 @@ class ObjectListView(ObjectPermissionRequiredMixin, View):
|
|||||||
'table': table,
|
'table': table,
|
||||||
'permissions': permissions,
|
'permissions': permissions,
|
||||||
'action_buttons': self.action_buttons,
|
'action_buttons': self.action_buttons,
|
||||||
'table_config_form': TableConfigForm(table=table),
|
|
||||||
'filter_form': self.filterset_form(request.GET, label_suffix='') if self.filterset_form else None,
|
'filter_form': self.filterset_form(request.GET, label_suffix='') if self.filterset_form else None,
|
||||||
}
|
}
|
||||||
context.update(self.extra_context())
|
context.update(self.extra_context())
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
<head>
|
<head>
|
||||||
<title>Server Error</title>
|
<title>Server Error</title>
|
||||||
<link rel="stylesheet" href="{% static 'netbox.css'%}" />
|
<link rel="stylesheet" href="{% static 'netbox-light.css'%}" />
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
@ -12,7 +12,7 @@
|
|||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col col-md-6 offset-md-3">
|
<div class="col col-md-6 offset-md-3">
|
||||||
<div class="card bg-danger mt-5">
|
<div class="card border-danger mt-5">
|
||||||
<h5 class="card-header">
|
<h5 class="card-header">
|
||||||
<i class="mdi mdi-alert"></i> Server Error
|
<i class="mdi mdi-alert"></i> Server Error
|
||||||
</h5>
|
</h5>
|
||||||
@ -32,7 +32,7 @@
|
|||||||
Python version: {{ python_version }}
|
Python version: {{ python_version }}
|
||||||
NetBox version: {{ netbox_version }}</pre>
|
NetBox version: {{ netbox_version }}</pre>
|
||||||
<p>
|
<p>
|
||||||
If further assistance is required, please post to the <a href="https://groups.google.com/g/netbox-discuss">NetBox mailing list</a>.
|
If further assistance is required, please post to the <a href="https://github.com/netbox-community/netbox/discussions">NetBox discussion forum</a> on GitHub.
|
||||||
</p>
|
</p>
|
||||||
<div class="text-end">
|
<div class="text-end">
|
||||||
<a href="{% url 'home' %}" class="btn btn-primary">Home Page</a>
|
<a href="{% url 'home' %}" class="btn btn-primary">Home Page</a>
|
||||||
|
@ -7,12 +7,12 @@
|
|||||||
{# Brand #}
|
{# Brand #}
|
||||||
|
|
||||||
{# Full Logo #}
|
{# Full Logo #}
|
||||||
<a class="sidenav-brand" href="/">
|
<a class="sidenav-brand" href="{% url 'home' %}">
|
||||||
<img src="{% static 'netbox_logo.svg' %}" height="48" class="sidenav-brand-img" alt="NetBox Logo">
|
<img src="{% static 'netbox_logo.svg' %}" height="48" class="sidenav-brand-img" alt="NetBox Logo">
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
{# Icon Logo #}
|
{# Icon Logo #}
|
||||||
<a class="sidenav-brand-icon" href="/">
|
<a class="sidenav-brand-icon" href="{% url 'home' %}">
|
||||||
<img src="{% static 'netbox_icon.svg' %}" height="32" class="sidenav-brand-img" alt="NetBox Logo">
|
<img src="{% static 'netbox_icon.svg' %}" height="32" class="sidenav-brand-img" alt="NetBox Logo">
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
{% extends 'ipam/prefix/base.html' %}
|
{% extends 'ipam/prefix/base.html' %}
|
||||||
|
{% load helpers %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
{% block extra_controls %}
|
{% block extra_controls %}
|
||||||
{% if perms.ipam.add_ipaddress and active_tab == 'ip-addresses' and first_available_ip %}
|
{% if perms.ipam.add_ipaddress and active_tab == 'ip-addresses' and first_available_ip %}
|
||||||
@ -11,7 +13,13 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col col-md-12">
|
<div class="col col-md-12">
|
||||||
|
{% include 'inc/table_controls.html' with table_modal="IPAddressTable_config" %}
|
||||||
{% include 'utilities/obj_table.html' with heading='IP Addresses' bulk_edit_url='ipam:ipaddress_bulk_edit' bulk_delete_url='ipam:ipaddress_bulk_delete' %}
|
{% include 'utilities/obj_table.html' with heading='IP Addresses' bulk_edit_url='ipam:ipaddress_bulk_edit' bulk_delete_url='ipam:ipaddress_bulk_delete' %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% table_config_form table table_name="IPAddressTable" %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block javascript %}
|
||||||
|
<script src="{% static 'js/tableconfig.js' %}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -1,10 +1,17 @@
|
|||||||
{% extends 'ipam/prefix/base.html' %}
|
{% extends 'ipam/prefix/base.html' %}
|
||||||
|
{% load helpers %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col col-md-12">
|
<div class="col col-md-12">
|
||||||
|
{% include 'inc/table_controls.html' with table_modal="IPRangeTable_config" %}
|
||||||
{% include 'utilities/obj_table.html' with heading='Child IP Ranges' bulk_edit_url='ipam:prefix_bulk_edit' bulk_delete_url='ipam:prefix_bulk_delete' parent=prefix %}
|
{% include 'utilities/obj_table.html' with heading='Child IP Ranges' bulk_edit_url='ipam:prefix_bulk_edit' bulk_delete_url='ipam:prefix_bulk_delete' parent=prefix %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% table_config_form table table_name="IPRangeTable" %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block javascript %}
|
||||||
|
<script src="{% static 'js/tableconfig.js' %}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -2,20 +2,17 @@
|
|||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
|
||||||
{% block buttons %}
|
{% block extra_controls %}
|
||||||
{% include 'ipam/inc/toggle_available.html' %}
|
{% include 'ipam/inc/toggle_available.html' %}
|
||||||
{% if request.user.is_authenticated and table_config_form %}
|
|
||||||
<button type="button" class="btn btn-default" data-toggle="modal" data-target="#PrefixDetailTable_config" title="Configure table"><i class="mdi mdi-cog"></i> Configure</button>
|
|
||||||
{% endif %}
|
|
||||||
{% if perms.ipam.add_prefix and active_tab == 'prefixes' and first_available_prefix %}
|
{% if perms.ipam.add_prefix and active_tab == 'prefixes' and first_available_prefix %}
|
||||||
<a href="{% url 'ipam:prefix_add' %}?prefix={{ first_available_prefix }}&vrf={{ object.vrf.pk }}&site={{ object.site.pk }}&tenant_group={{ object.tenant.group.pk }}&tenant={{ object.tenant.pk }}" class="btn btn-success">
|
<a href="{% url 'ipam:prefix_add' %}?prefix={{ first_available_prefix }}&vrf={{ object.vrf.pk }}&site={{ object.site.pk }}&tenant_group={{ object.tenant.group.pk }}&tenant={{ object.tenant.pk }}" class="btn btn-sm btn-success">
|
||||||
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add Child Prefix
|
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add Child Prefix
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.ipam.add_ipaddress and active_tab == 'ip-addresses' and first_available_ip %}
|
{% if perms.ipam.add_ipaddress and active_tab == 'ip-addresses' and first_available_ip %}
|
||||||
<a href="{% url 'ipam:ipaddress_add' %}?address={{ first_available_ip }}&vrf={{ object.vrf.pk }}&tenant_group={{ object.tenant.group.pk }}&tenant={{ object.tenant.pk }}" class="btn btn-success">
|
<a href="{% url 'ipam:ipaddress_add' %}?address={{ first_available_ip }}&vrf={{ object.vrf.pk }}&tenant_group={{ object.tenant.group.pk }}&tenant={{ object.tenant.pk }}" class="btn btn-sm btn-success">
|
||||||
<span class="mdi mdi-plus-thick" aria-hidden="true"></span>
|
<span class="mdi mdi-plus-thick" aria-hidden="true"></span>
|
||||||
Add an IP Address
|
Add Child IP Address
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
@ -24,12 +21,13 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col col-md-12">
|
<div class="col col-md-12">
|
||||||
|
{% include 'inc/table_controls.html' with table_modal="PrefixDetailTable_config" %}
|
||||||
{% include 'utilities/obj_table.html' with heading='Child Prefixes' bulk_edit_url='ipam:prefix_bulk_edit' bulk_delete_url='ipam:prefix_bulk_delete' parent=prefix %}
|
{% include 'utilities/obj_table.html' with heading='Child Prefixes' bulk_edit_url='ipam:prefix_bulk_edit' bulk_delete_url='ipam:prefix_bulk_delete' parent=prefix %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% table_config_form prefix_table table_name="PrefixDetailTable" %}
|
{% table_config_form table table_name="PrefixDetailTable" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block javascript %}
|
{% block javascript %}
|
||||||
<script src="{% static 'js/tableconfig.js' %}"></script>
|
<script src="{% static 'js/tableconfig.js' %}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -42,7 +42,7 @@
|
|||||||
The file <code>{{ filename }}</code> exists in the static root directory and is readable by the HTTP process.
|
The file <code>{{ filename }}</code> exists in the static root directory and is readable by the HTTP process.
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p>Click <a href="/">here</a> to attempt loading NetBox again.</p>
|
<p>Click <a href="{% url 'home' %}">here</a> to attempt loading NetBox again.</p>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -9,5 +9,5 @@
|
|||||||
{% block title %}{% if name %}{{ name }} | {% endif %}NetBox REST API{% endblock %}
|
{% block title %}{% if name %}{{ name }} | {% endif %}NetBox REST API{% endblock %}
|
||||||
|
|
||||||
{% block branding %}
|
{% block branding %}
|
||||||
<a class="navbar-brand" href="/{{ settings.BASE_PATH }}">NetBox</a>
|
<a class="navbar-brand" href="{% url 'home' %}">NetBox</a>
|
||||||
{% endblock branding %}
|
{% endblock branding %}
|
||||||
|
@ -7,11 +7,11 @@
|
|||||||
<h5 class="modal-title">Table Configuration</h5>
|
<h5 class="modal-title">Table Configuration</h5>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<form class="form-horizontal userconfigform" data-config-root="tables.{{ table_config_form.table_name }}">
|
<form class="form-horizontal userconfigform" data-config-root="tables.{{ form.table_name }}">
|
||||||
<div class="modal-body row">
|
<div class="modal-body row">
|
||||||
<div class="col-5 text-center">
|
<div class="col-5 text-center">
|
||||||
{{ table_config_form.available_columns.label }}
|
{{ form.available_columns.label }}
|
||||||
{{ table_config_form.available_columns }}
|
{{ form.available_columns }}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-2 d-flex align-items-center">
|
<div class="col-2 d-flex align-items-center">
|
||||||
<div>
|
<div>
|
||||||
@ -24,8 +24,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-5 text-center">
|
<div class="col-5 text-center">
|
||||||
{{ table_config_form.columns.label }}
|
{{ form.columns.label }}
|
||||||
{{ table_config_form.columns }}
|
{{ form.columns }}
|
||||||
<a class="btn btn-primary btn-sm mt-2" id="move-option-up" data-target="id_columns">
|
<a class="btn btn-primary btn-sm mt-2" id="move-option-up" data-target="id_columns">
|
||||||
<i class="mdi mdi-arrow-up-bold"></i> Move Up
|
<i class="mdi mdi-arrow-up-bold"></i> Move Up
|
||||||
</a>
|
</a>
|
||||||
|
@ -131,7 +131,7 @@
|
|||||||
<th scope="row"><i class="mdi mdi-chip"></i> Memory</th>
|
<th scope="row"><i class="mdi mdi-chip"></i> Memory</th>
|
||||||
<td>
|
<td>
|
||||||
{% if object.memory %}
|
{% if object.memory %}
|
||||||
{{ object.memory|humanize_megabytes }} MB
|
{{ object.memory|humanize_megabytes }}
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">—</span>
|
<span class="text-muted">—</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -21,8 +21,8 @@ class Command(_Command):
|
|||||||
raise CommandError(
|
raise CommandError(
|
||||||
"This command is available for development purposes only. It will\n"
|
"This command is available for development purposes only. It will\n"
|
||||||
"NOT resolve any issues with missing or unapplied migrations. For assistance,\n"
|
"NOT resolve any issues with missing or unapplied migrations. For assistance,\n"
|
||||||
"please post to the NetBox mailing list:\n"
|
"please post to the NetBox discussion forum on GitHub:\n"
|
||||||
" https://groups.google.com/g/netbox-discuss"
|
" https://github.com/netbox-community/netbox/discussions"
|
||||||
)
|
)
|
||||||
|
|
||||||
super().handle(*args, **kwargs)
|
super().handle(*args, **kwargs)
|
||||||
|
@ -49,21 +49,25 @@ class EnhancedPage(Page):
|
|||||||
|
|
||||||
def get_paginate_count(request):
|
def get_paginate_count(request):
|
||||||
"""
|
"""
|
||||||
Determine the length of a page, using the following in order:
|
Determine the desired length of a page, using the following in order:
|
||||||
|
|
||||||
1. per_page URL query parameter
|
1. per_page URL query parameter
|
||||||
2. Saved user preference
|
2. Saved user preference
|
||||||
3. PAGINATE_COUNT global setting.
|
3. PAGINATE_COUNT global setting.
|
||||||
|
|
||||||
|
Return the lesser of the calculated value and MAX_PAGE_SIZE.
|
||||||
"""
|
"""
|
||||||
if 'per_page' in request.GET:
|
if 'per_page' in request.GET:
|
||||||
try:
|
try:
|
||||||
per_page = int(request.GET.get('per_page'))
|
per_page = int(request.GET.get('per_page'))
|
||||||
if request.user.is_authenticated:
|
if request.user.is_authenticated:
|
||||||
request.user.config.set('pagination.per_page', per_page, commit=True)
|
request.user.config.set('pagination.per_page', per_page, commit=True)
|
||||||
return per_page
|
return min(per_page, settings.MAX_PAGE_SIZE)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if request.user.is_authenticated:
|
if request.user.is_authenticated:
|
||||||
return request.user.config.get('pagination.per_page', settings.PAGINATE_COUNT)
|
per_page = request.user.config.get('pagination.per_page', settings.PAGINATE_COUNT)
|
||||||
return settings.PAGINATE_COUNT
|
return min(per_page, settings.MAX_PAGE_SIZE)
|
||||||
|
|
||||||
|
return min(settings.PAGINATE_COUNT, settings.MAX_PAGE_SIZE)
|
||||||
|
@ -237,9 +237,13 @@ class ContentTypeColumn(tables.Column):
|
|||||||
Display a ContentType instance.
|
Display a ContentType instance.
|
||||||
"""
|
"""
|
||||||
def render(self, value):
|
def render(self, value):
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
return content_type_name(value)
|
return content_type_name(value)
|
||||||
|
|
||||||
def value(self, value):
|
def value(self, value):
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
return f"{value.app_label}.{value.model}"
|
return f"{value.app_label}.{value.model}"
|
||||||
|
|
||||||
|
|
||||||
|
@ -401,7 +401,7 @@ def badge(value, bg_class='secondary', show_empty=False):
|
|||||||
def table_config_form(table, table_name=None):
|
def table_config_form(table, table_name=None):
|
||||||
return {
|
return {
|
||||||
'table_name': table_name or table.__class__.__name__,
|
'table_name': table_name or table.__class__.__name__,
|
||||||
'table_config_form': TableConfigForm(table=table),
|
'form': TableConfigForm(table=table),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user