Просмотр исходного кода

Merge branch 'develop' into 2365-show-available-toggle

hSaria 6 лет назад
Родитель
Сommit
6a7af22dea

+ 3 - 0
docs/release-notes/version-2.6.md

@@ -3,6 +3,9 @@
 ## Enhancements
 
 * [#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
 * [#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

+ 34 - 1
netbox/extras/admin.py

@@ -3,7 +3,10 @@ from django.contrib import admin
 
 from netbox.admin import admin_site
 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):
@@ -166,6 +169,36 @@ class ExportTemplateAdmin(admin.ModelAdmin):
     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
 #

+ 7 - 0
netbox/extras/models.py

@@ -915,6 +915,13 @@ class ReportResult(models.Model):
     class Meta:
         ordering = ['report']
 
+    def __str__(self):
+        return "{} {} at {}".format(
+            self.report,
+            "passed" if not self.failed else "failed",
+            self.created
+        )
+
 
 #
 # Change logging

+ 22 - 14
netbox/extras/templatetags/custom_links.py

@@ -46,12 +46,17 @@ def custom_links(obj):
 
         # Add non-grouped links
         else:
-            text_rendered = render_jinja2(cl.text, context)
-            if text_rendered:
-                link_target = ' target="_blank"' if cl.new_window else ''
-                template_code += LINK_BUTTON.format(
-                    cl.url, link_target, cl.button_class, text_rendered
-                )
+            try:
+                text_rendered = render_jinja2(cl.text, context)
+                if text_rendered:
+                    link_rendered = render_jinja2(cl.url, context)
+                    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
     for group, links in group_names.items():
@@ -59,11 +64,17 @@ def custom_links(obj):
         links_rendered = []
 
         for cl in links:
-            text_rendered = render_jinja2(cl.text, context)
-            if text_rendered:
-                link_target = ' target="_blank"' if cl.new_window else ''
+            try:
+                text_rendered = render_jinja2(cl.text, context)
+                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(
-                    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:
@@ -71,7 +82,4 @@ def custom_links(obj):
                 links[0].button_class, group, ''.join(links_rendered)
             )
 
-    # Render template
-    rendered = render_jinja2(template_code, context)
-
-    return mark_safe(rendered)
+    return mark_safe(template_code)

+ 7 - 0
netbox/ipam/filters.py

@@ -309,6 +309,10 @@ class IPAddressFilter(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilt
         queryset=Interface.objects.all(),
         label='Interface (ID)',
     )
+    assigned_to_interface = django_filters.BooleanFilter(
+        method='_assigned_to_interface',
+        label='Is assigned to an interface',
+    )
     status = django_filters.MultipleChoiceFilter(
         choices=IPADDRESS_STATUS_CHOICES,
         null_value=None
@@ -366,6 +370,9 @@ class IPAddressFilter(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilt
         except Device.DoesNotExist:
             return queryset.none()
 
+    def _assigned_to_interface(self, queryset, name, value):
+        return queryset.exclude(interface__isnull=value)
+
 
 class VLANGroupFilter(NameSlugSearchFilterSet):
     site_id = django_filters.ModelMultipleChoiceFilter(

+ 9 - 1
netbox/ipam/forms.py

@@ -938,7 +938,8 @@ class IPAddressAssignForm(BootstrapMixin, forms.Form):
 class IPAddressFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
     model = IPAddress
     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(
         required=False,
@@ -984,6 +985,13 @@ class IPAddressFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterFo
         required=False,
         widget=StaticSelect2Multiple()
     )
+    assigned_to_interface = forms.NullBooleanField(
+        required=False,
+        label='Assigned to an interface',
+        widget=StaticSelect2(
+            choices=BOOLEAN_WITH_BLANK_CHOICES
+        )
+    )
 
 
 #