mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-17 04:32:51 -06:00
Merge branch 'develop' into 2365-show-available-toggle
This commit is contained in:
commit
6a7af22dea
@ -3,6 +3,9 @@
|
|||||||
## Enhancements
|
## Enhancements
|
||||||
|
|
||||||
* [#2365](https://github.com/netbox-community/netbox/issues/2365) - Toggle for showing available prefixes/ip addresses
|
* [#2365](https://github.com/netbox-community/netbox/issues/2365) - Toggle for showing available prefixes/ip addresses
|
||||||
|
* [#2892](https://github.com/netbox-community/netbox/issues/2892) - Extend admin UI to allow deleting old report results
|
||||||
|
* [#3062](https://github.com/netbox-community/netbox/issues/3062) - Add `assigned_to_interface` filter for IP addresses
|
||||||
|
* [#3461](https://github.com/netbox-community/netbox/issues/3461) - Fail gracefully on custom link rendering exception
|
||||||
* [#3705](https://github.com/netbox-community/netbox/issues/3705) - Provide request context when executing custom scripts
|
* [#3705](https://github.com/netbox-community/netbox/issues/3705) - Provide request context when executing custom scripts
|
||||||
* [#3762](https://github.com/netbox-community/netbox/issues/3762) - Add date/time picker widgets
|
* [#3762](https://github.com/netbox-community/netbox/issues/3762) - Add date/time picker widgets
|
||||||
* [#3788](https://github.com/netbox-community/netbox/issues/3788) - Enable partial search for inventory items
|
* [#3788](https://github.com/netbox-community/netbox/issues/3788) - Enable partial search for inventory items
|
||||||
|
@ -3,7 +3,10 @@ from django.contrib import admin
|
|||||||
|
|
||||||
from netbox.admin import admin_site
|
from netbox.admin import admin_site
|
||||||
from utilities.forms import LaxURLField
|
from utilities.forms import LaxURLField
|
||||||
from .models import CustomField, CustomFieldChoice, CustomLink, Graph, ExportTemplate, TopologyMap, Webhook
|
from .models import (
|
||||||
|
CustomField, CustomFieldChoice, CustomLink, Graph, ExportTemplate, ReportResult, TopologyMap, Webhook,
|
||||||
|
)
|
||||||
|
from .reports import get_report
|
||||||
|
|
||||||
|
|
||||||
def order_content_types(field):
|
def order_content_types(field):
|
||||||
@ -166,6 +169,36 @@ class ExportTemplateAdmin(admin.ModelAdmin):
|
|||||||
form = ExportTemplateForm
|
form = ExportTemplateForm
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Reports
|
||||||
|
#
|
||||||
|
|
||||||
|
@admin.register(ReportResult, site=admin_site)
|
||||||
|
class ReportResultAdmin(admin.ModelAdmin):
|
||||||
|
list_display = [
|
||||||
|
'report', 'active', 'created', 'user', 'passing',
|
||||||
|
]
|
||||||
|
fields = [
|
||||||
|
'report', 'user', 'passing', 'data',
|
||||||
|
]
|
||||||
|
list_filter = [
|
||||||
|
'failed',
|
||||||
|
]
|
||||||
|
readonly_fields = fields
|
||||||
|
|
||||||
|
def has_add_permission(self, request):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def active(self, obj):
|
||||||
|
module, report_name = obj.report.split('.')
|
||||||
|
return True if get_report(module, report_name) else False
|
||||||
|
active.boolean = True
|
||||||
|
|
||||||
|
def passing(self, obj):
|
||||||
|
return not obj.failed
|
||||||
|
passing.boolean = True
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Topology maps
|
# Topology maps
|
||||||
#
|
#
|
||||||
|
@ -915,6 +915,13 @@ class ReportResult(models.Model):
|
|||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['report']
|
ordering = ['report']
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "{} {} at {}".format(
|
||||||
|
self.report,
|
||||||
|
"passed" if not self.failed else "failed",
|
||||||
|
self.created
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Change logging
|
# Change logging
|
||||||
|
@ -46,12 +46,17 @@ def custom_links(obj):
|
|||||||
|
|
||||||
# Add non-grouped links
|
# Add non-grouped links
|
||||||
else:
|
else:
|
||||||
text_rendered = render_jinja2(cl.text, context)
|
try:
|
||||||
if text_rendered:
|
text_rendered = render_jinja2(cl.text, context)
|
||||||
link_target = ' target="_blank"' if cl.new_window else ''
|
if text_rendered:
|
||||||
template_code += LINK_BUTTON.format(
|
link_rendered = render_jinja2(cl.url, context)
|
||||||
cl.url, link_target, cl.button_class, text_rendered
|
link_target = ' target="_blank"' if cl.new_window else ''
|
||||||
)
|
template_code += LINK_BUTTON.format(
|
||||||
|
link_rendered, link_target, cl.button_class, text_rendered
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
template_code += '<a class="btn btn-sm btn-default" disabled="disabled" title="{}">' \
|
||||||
|
'<i class="fa fa-warning"></i> {}</a>\n'.format(e, cl.name)
|
||||||
|
|
||||||
# Add grouped links to template
|
# Add grouped links to template
|
||||||
for group, links in group_names.items():
|
for group, links in group_names.items():
|
||||||
@ -59,11 +64,17 @@ def custom_links(obj):
|
|||||||
links_rendered = []
|
links_rendered = []
|
||||||
|
|
||||||
for cl in links:
|
for cl in links:
|
||||||
text_rendered = render_jinja2(cl.text, context)
|
try:
|
||||||
if text_rendered:
|
text_rendered = render_jinja2(cl.text, context)
|
||||||
link_target = ' target="_blank"' if cl.new_window else ''
|
if text_rendered:
|
||||||
|
link_target = ' target="_blank"' if cl.new_window else ''
|
||||||
|
links_rendered.append(
|
||||||
|
GROUP_LINK.format(cl.url, link_target, cl.text)
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
links_rendered.append(
|
links_rendered.append(
|
||||||
GROUP_LINK.format(cl.url, link_target, cl.text)
|
'<li><a disabled="disabled" title="{}"><span class="text-muted">'
|
||||||
|
'<i class="fa fa-warning"></i> {}</span></a></li>'.format(e, cl.name)
|
||||||
)
|
)
|
||||||
|
|
||||||
if links_rendered:
|
if links_rendered:
|
||||||
@ -71,7 +82,4 @@ def custom_links(obj):
|
|||||||
links[0].button_class, group, ''.join(links_rendered)
|
links[0].button_class, group, ''.join(links_rendered)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Render template
|
return mark_safe(template_code)
|
||||||
rendered = render_jinja2(template_code, context)
|
|
||||||
|
|
||||||
return mark_safe(rendered)
|
|
||||||
|
@ -309,6 +309,10 @@ class IPAddressFilter(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilt
|
|||||||
queryset=Interface.objects.all(),
|
queryset=Interface.objects.all(),
|
||||||
label='Interface (ID)',
|
label='Interface (ID)',
|
||||||
)
|
)
|
||||||
|
assigned_to_interface = django_filters.BooleanFilter(
|
||||||
|
method='_assigned_to_interface',
|
||||||
|
label='Is assigned to an interface',
|
||||||
|
)
|
||||||
status = django_filters.MultipleChoiceFilter(
|
status = django_filters.MultipleChoiceFilter(
|
||||||
choices=IPADDRESS_STATUS_CHOICES,
|
choices=IPADDRESS_STATUS_CHOICES,
|
||||||
null_value=None
|
null_value=None
|
||||||
@ -366,6 +370,9 @@ class IPAddressFilter(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilt
|
|||||||
except Device.DoesNotExist:
|
except Device.DoesNotExist:
|
||||||
return queryset.none()
|
return queryset.none()
|
||||||
|
|
||||||
|
def _assigned_to_interface(self, queryset, name, value):
|
||||||
|
return queryset.exclude(interface__isnull=value)
|
||||||
|
|
||||||
|
|
||||||
class VLANGroupFilter(NameSlugSearchFilterSet):
|
class VLANGroupFilter(NameSlugSearchFilterSet):
|
||||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
@ -938,7 +938,8 @@ class IPAddressAssignForm(BootstrapMixin, forms.Form):
|
|||||||
class IPAddressFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
|
class IPAddressFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
|
||||||
model = IPAddress
|
model = IPAddress
|
||||||
field_order = [
|
field_order = [
|
||||||
'q', 'parent', 'family', 'mask_length', 'vrf_id', 'status', 'role', 'tenant_group', 'tenant',
|
'q', 'parent', 'family', 'mask_length', 'vrf_id', 'status', 'role', 'assigned_to_interface', 'tenant_group',
|
||||||
|
'tenant',
|
||||||
]
|
]
|
||||||
q = forms.CharField(
|
q = forms.CharField(
|
||||||
required=False,
|
required=False,
|
||||||
@ -984,6 +985,13 @@ class IPAddressFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterFo
|
|||||||
required=False,
|
required=False,
|
||||||
widget=StaticSelect2Multiple()
|
widget=StaticSelect2Multiple()
|
||||||
)
|
)
|
||||||
|
assigned_to_interface = forms.NullBooleanField(
|
||||||
|
required=False,
|
||||||
|
label='Assigned to an interface',
|
||||||
|
widget=StaticSelect2(
|
||||||
|
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
Loading…
Reference in New Issue
Block a user