From 556beeee6c7cf1c98fe7be9393dde6c078cbfc41 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Fri, 12 May 2023 18:34:56 +0530 Subject: [PATCH 001/170] Updates doc for reports and scripts permission (#12565) * updates doc for script permission #12557 * updates doc for reports permission #12557 --- docs/installation/3-netbox.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/installation/3-netbox.md b/docs/installation/3-netbox.md index dc6c38977..e0e656ce9 100644 --- a/docs/installation/3-netbox.md +++ b/docs/installation/3-netbox.md @@ -100,6 +100,8 @@ Create a system user account named `netbox`. We'll configure the WSGI and HTTP s ``` sudo adduser --system --group netbox sudo chown --recursive netbox /opt/netbox/netbox/media/ + sudo chown --recursive netbox /opt/netbox/netbox/reports/ + sudo chown --recursive netbox /opt/netbox/netbox/scripts/ ``` === "CentOS" @@ -108,6 +110,8 @@ Create a system user account named `netbox`. We'll configure the WSGI and HTTP s sudo groupadd --system netbox sudo adduser --system -g netbox netbox sudo chown --recursive netbox /opt/netbox/netbox/media/ + sudo chown --recursive netbox /opt/netbox/netbox/reports/ + sudo chown --recursive netbox /opt/netbox/netbox/scripts/ ``` ## Configuration From 011a936a566a5beaa1fea94d422bd499fc08d552 Mon Sep 17 00:00:00 2001 From: Daniel Sheppard Date: Fri, 12 May 2023 08:07:51 -0500 Subject: [PATCH 002/170] Fixes #10686 - Import cables using VC master device (#12551) * Allow importing cables against master device for subordinate device interfaces * Add tests --- netbox/dcim/forms/bulk_import.py | 6 +++++- netbox/dcim/tests/test_views.py | 11 +++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/netbox/dcim/forms/bulk_import.py b/netbox/dcim/forms/bulk_import.py index cdb59e9eb..de7575acb 100644 --- a/netbox/dcim/forms/bulk_import.py +++ b/netbox/dcim/forms/bulk_import.py @@ -1078,7 +1078,11 @@ class CableImportForm(NetBoxModelImportForm): model = content_type.model_class() try: - termination_object = model.objects.get(device=device, name=name) + if device.virtual_chassis and device.virtual_chassis.master == device and \ + model.objects.filter(device=device, name=name).count() == 0: + termination_object = model.objects.get(device__in=device.virtual_chassis.members.all(), name=name) + else: + termination_object = model.objects.get(device=device, name=name) if termination_object.cable is not None: raise forms.ValidationError(f"Side {side.upper()}: {device} {termination_object} is already connected") except ObjectDoesNotExist: diff --git a/netbox/dcim/tests/test_views.py b/netbox/dcim/tests/test_views.py index 44e6ef2a9..c0cfca2e7 100644 --- a/netbox/dcim/tests/test_views.py +++ b/netbox/dcim/tests/test_views.py @@ -2907,6 +2907,7 @@ class CableTestCase( manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') devicetype = DeviceType.objects.create(model='Device Type 1', manufacturer=manufacturer) devicerole = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1') + vc = VirtualChassis.objects.create(name='Virtual Chassis') devices = ( Device(name='Device 1', site=site, device_type=devicetype, device_role=devicerole), @@ -2916,6 +2917,10 @@ class CableTestCase( ) Device.objects.bulk_create(devices) + vc.members.set((devices[0], devices[1], devices[2])) + vc.master = devices[0] + vc.save() + interfaces = ( Interface(device=devices[0], name='Interface 1', type=InterfaceTypeChoices.TYPE_1GE_FIXED), Interface(device=devices[0], name='Interface 2', type=InterfaceTypeChoices.TYPE_1GE_FIXED), @@ -2929,6 +2934,10 @@ class CableTestCase( Interface(device=devices[3], name='Interface 1', type=InterfaceTypeChoices.TYPE_1GE_FIXED), Interface(device=devices[3], name='Interface 2', type=InterfaceTypeChoices.TYPE_1GE_FIXED), Interface(device=devices[3], name='Interface 3', type=InterfaceTypeChoices.TYPE_1GE_FIXED), + Interface(device=devices[1], name='Device 2 Interface', type=InterfaceTypeChoices.TYPE_1GE_FIXED), + Interface(device=devices[2], name='Device 3 Interface', type=InterfaceTypeChoices.TYPE_1GE_FIXED), + Interface(device=devices[3], name='Interface 4', type=InterfaceTypeChoices.TYPE_1GE_FIXED), + Interface(device=devices[3], name='Interface 5', type=InterfaceTypeChoices.TYPE_1GE_FIXED), ) Interface.objects.bulk_create(interfaces) @@ -2961,6 +2970,8 @@ class CableTestCase( "Device 3,dcim.interface,Interface 1,Device 4,dcim.interface,Interface 1", "Device 3,dcim.interface,Interface 2,Device 4,dcim.interface,Interface 2", "Device 3,dcim.interface,Interface 3,Device 4,dcim.interface,Interface 3", + "Device 1,dcim.interface,Device 2 Interface,Device 4,dcim.interface,Interface 4", + "Device 1,dcim.interface,Device 3 Interface,Device 4,dcim.interface,Interface 5", ) cls.csv_update_data = ( From e71a98499f272f7ff9f614fdee1e2a43ff94525b Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Fri, 12 May 2023 19:24:59 +0530 Subject: [PATCH 003/170] Adds BANNER_MAINTENANCE config (#12555) * adds BANNER_MAINTENANCE config #12554 * changes as per review * lint fix * Fix admin form field widget --------- Co-authored-by: jeremystretch --- docs/configuration/miscellaneous.md | 11 +++++++++++ netbox/extras/admin.py | 2 +- netbox/netbox/config/parameters.py | 11 +++++++++++ netbox/templates/base/layout.html | 4 ++-- 4 files changed, 25 insertions(+), 3 deletions(-) diff --git a/docs/configuration/miscellaneous.md b/docs/configuration/miscellaneous.md index c3fbb40aa..e3728acab 100644 --- a/docs/configuration/miscellaneous.md +++ b/docs/configuration/miscellaneous.md @@ -29,6 +29,17 @@ This defines custom content to be displayed on the login page above the login fo --- +## BANNER_MAINTENANCE + +!!! tip "Dynamic Configuration Parameter" + +!!! note + This parameter was added in NetBox v3.5. + +This adds a banner to the top of every page when maintenance mode is enabled. HTML is allowed. + +--- + ## BANNER_TOP !!! tip "Dynamic Configuration Parameter" diff --git a/netbox/extras/admin.py b/netbox/extras/admin.py index 04a67b521..6d1b14370 100644 --- a/netbox/extras/admin.py +++ b/netbox/extras/admin.py @@ -25,7 +25,7 @@ class ConfigRevisionAdmin(admin.ModelAdmin): 'fields': ('ALLOWED_URL_SCHEMES',), }), ('Banners', { - 'fields': ('BANNER_LOGIN', 'BANNER_TOP', 'BANNER_BOTTOM'), + 'fields': ('BANNER_LOGIN', 'BANNER_MAINTENANCE', 'BANNER_TOP', 'BANNER_BOTTOM'), 'classes': ('monospace',), }), ('Pagination', { diff --git a/netbox/netbox/config/parameters.py b/netbox/netbox/config/parameters.py index 2bfa234f0..9c613217c 100644 --- a/netbox/netbox/config/parameters.py +++ b/netbox/netbox/config/parameters.py @@ -28,6 +28,17 @@ PARAMS = ( ), }, ), + ConfigParam( + name='BANNER_MAINTENANCE', + label=_('Maintenance banner'), + default='NetBox is currently in maintenance mode. Functionality may be limited.', + description=_('Additional content to display when in maintenance mode'), + field_kwargs={ + 'widget': forms.Textarea( + attrs={'class': 'vLargeTextField'} + ), + }, + ), ConfigParam( name='BANNER_TOP', label=_('Top banner'), diff --git a/netbox/templates/base/layout.html b/netbox/templates/base/layout.html index 6b247d81a..9e8b7d7bf 100644 --- a/netbox/templates/base/layout.html +++ b/netbox/templates/base/layout.html @@ -77,10 +77,10 @@ Blocks: {% endif %} - {% if config.MAINTENANCE_MODE %} + {% if config.MAINTENANCE_MODE and config.BANNER_MAINTENANCE %} {% endif %} From 4eb5e90ccc1af8b42cc2973896529a3c81de2bce Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Fri, 12 May 2023 19:56:26 +0530 Subject: [PATCH 004/170] Adds contact tabs (#12460) * adds contact tabs #11599 * fixed lint issues * changes as per review * changes as per review * replaces generic object template with base template --- netbox/circuits/views.py | 17 ++++- netbox/dcim/views.py | 41 ++++++++++++ netbox/ipam/views.py | 6 ++ netbox/templates/circuits/circuit.html | 1 - netbox/templates/circuits/provider.html | 1 - .../templates/circuits/provideraccount.html | 1 - netbox/templates/dcim/device.html | 1 - netbox/templates/dcim/location.html | 1 - netbox/templates/dcim/manufacturer.html | 1 - netbox/templates/dcim/powerpanel.html | 1 - netbox/templates/dcim/rack.html | 1 - netbox/templates/dcim/region.html | 1 - netbox/templates/dcim/site.html | 1 - netbox/templates/dcim/sitegroup.html | 1 - netbox/templates/inc/panels/contacts.html | 63 ------------------- netbox/templates/ipam/l2vpn.html | 1 - netbox/templates/tenancy/object_contacts.html | 27 ++++++++ netbox/templates/tenancy/tenant.html | 1 - netbox/templates/virtualization/cluster.html | 1 - .../virtualization/clustergroup.html | 1 - .../virtualization/virtualmachine.html | 1 - netbox/tenancy/views.py | 32 +++++++++- netbox/virtualization/views.py | 18 +++++- 23 files changed, 137 insertions(+), 83 deletions(-) delete mode 100644 netbox/templates/inc/panels/contacts.html create mode 100644 netbox/templates/tenancy/object_contacts.html diff --git a/netbox/circuits/views.py b/netbox/circuits/views.py index e5f4faee1..f1cfdd1d5 100644 --- a/netbox/circuits/views.py +++ b/netbox/circuits/views.py @@ -1,10 +1,10 @@ from django.contrib import messages from django.db import transaction -from django.db.models import Q from django.shortcuts import get_object_or_404, redirect, render from dcim.views import PathTraceView from netbox.views import generic +from tenancy.views import ObjectContactsView from utilities.forms import ConfirmationForm from utilities.utils import count_related from utilities.views import register_model_view @@ -73,6 +73,11 @@ class ProviderBulkDeleteView(generic.BulkDeleteView): table = tables.ProviderTable +@register_model_view(Provider, 'contacts') +class ProviderContactsView(ObjectContactsView): + queryset = Provider.objects.all() + + # # ProviderAccounts # @@ -134,6 +139,11 @@ class ProviderAccountBulkDeleteView(generic.BulkDeleteView): table = tables.ProviderAccountTable +@register_model_view(ProviderAccount, 'contacts') +class ProviderAccountContactsView(ObjectContactsView): + queryset = ProviderAccount.objects.all() + + # # Provider networks # @@ -389,6 +399,11 @@ class CircuitSwapTerminations(generic.ObjectEditView): }) +@register_model_view(Circuit, 'contacts') +class CircuitContactsView(ObjectContactsView): + queryset = Circuit.objects.all() + + # # Circuit terminations # diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index bcbbf1739..0def4f4a8 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -20,6 +20,7 @@ from extras.views import ObjectConfigContextView from ipam.models import ASN, IPAddress, Prefix, VLAN, VLANGroup from ipam.tables import InterfaceVLANTable from netbox.views import generic +from tenancy.views import ObjectContactsView from utilities.forms import ConfirmationForm from utilities.paginator import EnhancedPaginator, get_paginate_count from utilities.permissions import get_permission_for_model @@ -267,6 +268,11 @@ class RegionBulkDeleteView(generic.BulkDeleteView): table = tables.RegionTable +@register_model_view(Region, 'contacts') +class RegionContactsView(ObjectContactsView): + queryset = Region.objects.all() + + # # Site groups # @@ -342,6 +348,11 @@ class SiteGroupBulkDeleteView(generic.BulkDeleteView): table = tables.SiteGroupTable +@register_model_view(SiteGroup, 'contacts') +class SiteGroupContactsView(ObjectContactsView): + queryset = SiteGroup.objects.all() + + # # Sites # @@ -435,6 +446,11 @@ class SiteBulkDeleteView(generic.BulkDeleteView): table = tables.SiteTable +@register_model_view(Site, 'contacts') +class SiteContactsView(ObjectContactsView): + queryset = Site.objects.all() + + # # Locations # @@ -523,6 +539,11 @@ class LocationBulkDeleteView(generic.BulkDeleteView): table = tables.LocationTable +@register_model_view(Location, 'contacts') +class LocationContactsView(ObjectContactsView): + queryset = Location.objects.all() + + # # Rack roles # @@ -740,6 +761,11 @@ class RackBulkDeleteView(generic.BulkDeleteView): table = tables.RackTable +@register_model_view(Rack, 'contacts') +class RackContactsView(ObjectContactsView): + queryset = Rack.objects.all() + + # # Rack reservations # @@ -874,6 +900,11 @@ class ManufacturerBulkDeleteView(generic.BulkDeleteView): table = tables.ManufacturerTable +@register_model_view(Manufacturer, 'contacts') +class ManufacturerContactsView(ObjectContactsView): + queryset = Manufacturer.objects.all() + + # # Device types # @@ -2088,6 +2119,11 @@ class DeviceBulkRenameView(generic.BulkRenameView): table = tables.DeviceTable +@register_model_view(Device, 'contacts') +class DeviceContactsView(ObjectContactsView): + queryset = Device.objects.all() + + # # Modules # @@ -3469,6 +3505,11 @@ class PowerPanelBulkDeleteView(generic.BulkDeleteView): table = tables.PowerPanelTable +@register_model_view(PowerPanel, 'contacts') +class PowerPanelContactsView(ObjectContactsView): + queryset = PowerPanel.objects.all() + + # # Power feeds # diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index 6b19b502d..6b73a061b 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -9,6 +9,7 @@ from circuits.models import Provider from dcim.filtersets import InterfaceFilterSet from dcim.models import Interface, Site from netbox.views import generic +from tenancy.views import ObjectContactsView from utilities.utils import count_related from utilities.views import ViewTab, register_model_view from virtualization.filtersets import VMInterfaceFilterSet @@ -1300,6 +1301,11 @@ class L2VPNBulkDeleteView(generic.BulkDeleteView): table = tables.L2VPNTable +@register_model_view(L2VPN, 'contacts') +class L2VPNContactsView(ObjectContactsView): + queryset = L2VPN.objects.all() + + # # L2VPN terminations # diff --git a/netbox/templates/circuits/circuit.html b/netbox/templates/circuits/circuit.html index ee994e959..a5913e2ad 100644 --- a/netbox/templates/circuits/circuit.html +++ b/netbox/templates/circuits/circuit.html @@ -70,7 +70,6 @@
{% include 'circuits/inc/circuit_termination.html' with termination=object.termination_a side='A' %} {% include 'circuits/inc/circuit_termination.html' with termination=object.termination_z side='Z' %} - {% include 'inc/panels/contacts.html' %} {% include 'inc/panels/image_attachments.html' %} {% plugin_right_page object %}
diff --git a/netbox/templates/circuits/provider.html b/netbox/templates/circuits/provider.html index 5a565ea29..c721d5a58 100644 --- a/netbox/templates/circuits/provider.html +++ b/netbox/templates/circuits/provider.html @@ -43,7 +43,6 @@
{% include 'inc/panels/related_objects.html' %} {% include 'inc/panels/custom_fields.html' %} - {% include 'inc/panels/contacts.html' %} {% plugin_right_page object %}
diff --git a/netbox/templates/circuits/provideraccount.html b/netbox/templates/circuits/provideraccount.html index 63344ada1..c55663b4a 100644 --- a/netbox/templates/circuits/provideraccount.html +++ b/netbox/templates/circuits/provideraccount.html @@ -38,7 +38,6 @@ {% include 'inc/panels/related_objects.html' %} {% include 'inc/panels/comments.html' %} {% include 'inc/panels/custom_fields.html' %} - {% include 'inc/panels/contacts.html' %} {% plugin_right_page object %}
diff --git a/netbox/templates/dcim/device.html b/netbox/templates/dcim/device.html index aa1b80cf7..b0e67269c 100644 --- a/netbox/templates/dcim/device.html +++ b/netbox/templates/dcim/device.html @@ -298,7 +298,6 @@
{% endif %} - {% include 'inc/panels/contacts.html' %} {% include 'inc/panels/image_attachments.html' %}
Dimensions
diff --git a/netbox/templates/dcim/location.html b/netbox/templates/dcim/location.html index 193d93f9a..795aeb35f 100644 --- a/netbox/templates/dcim/location.html +++ b/netbox/templates/dcim/location.html @@ -65,7 +65,6 @@
{% include 'inc/panels/related_objects.html' %} - {% include 'inc/panels/contacts.html' %} {% include 'dcim/inc/nonracked_devices.html' %} {% include 'inc/panels/image_attachments.html' %} {% plugin_right_page object %} diff --git a/netbox/templates/dcim/manufacturer.html b/netbox/templates/dcim/manufacturer.html index a60b3503c..8233b6fc8 100644 --- a/netbox/templates/dcim/manufacturer.html +++ b/netbox/templates/dcim/manufacturer.html @@ -51,7 +51,6 @@
{% include 'inc/panels/related_objects.html' %} {% include 'inc/panels/custom_fields.html' %} - {% include 'inc/panels/contacts.html' %} {% plugin_right_page object %}
diff --git a/netbox/templates/dcim/powerpanel.html b/netbox/templates/dcim/powerpanel.html index af08f3023..ea9210ba7 100644 --- a/netbox/templates/dcim/powerpanel.html +++ b/netbox/templates/dcim/powerpanel.html @@ -40,7 +40,6 @@
{% include 'inc/panels/related_objects.html' %} {% include 'inc/panels/custom_fields.html' %} - {% include 'inc/panels/contacts.html' %} {% include 'inc/panels/image_attachments.html' %} {% plugin_right_page object %}
diff --git a/netbox/templates/dcim/rack.html b/netbox/templates/dcim/rack.html index 9cb046b4e..52b5d4bfe 100644 --- a/netbox/templates/dcim/rack.html +++ b/netbox/templates/dcim/rack.html @@ -191,7 +191,6 @@ {% include 'inc/panels/related_objects.html' %} {% include 'dcim/inc/nonracked_devices.html' %} - {% include 'inc/panels/contacts.html' %} {% plugin_right_page object %} diff --git a/netbox/templates/dcim/region.html b/netbox/templates/dcim/region.html index 85587e4b5..05cc424d7 100644 --- a/netbox/templates/dcim/region.html +++ b/netbox/templates/dcim/region.html @@ -46,7 +46,6 @@
{% include 'inc/panels/related_objects.html' %} - {% include 'inc/panels/contacts.html' %} {% plugin_right_page object %}
diff --git a/netbox/templates/dcim/site.html b/netbox/templates/dcim/site.html index 91fdba7be..697737ceb 100644 --- a/netbox/templates/dcim/site.html +++ b/netbox/templates/dcim/site.html @@ -131,7 +131,6 @@
{% include 'inc/panels/related_objects.html' with filter_name='site_id' %} - {% include 'inc/panels/contacts.html' %}
Locations
diff --git a/netbox/templates/dcim/sitegroup.html b/netbox/templates/dcim/sitegroup.html index 2cf8e7168..819022a34 100644 --- a/netbox/templates/dcim/sitegroup.html +++ b/netbox/templates/dcim/sitegroup.html @@ -42,7 +42,6 @@
{% include 'inc/panels/tags.html' %} {% include 'inc/panels/custom_fields.html' %} - {% include 'inc/panels/contacts.html' %} {% plugin_left_page object %}
diff --git a/netbox/templates/inc/panels/contacts.html b/netbox/templates/inc/panels/contacts.html deleted file mode 100644 index 359ad8d7e..000000000 --- a/netbox/templates/inc/panels/contacts.html +++ /dev/null @@ -1,63 +0,0 @@ -{% load helpers %} - -
-
Contacts
-
- {% with contacts=object.contacts.all %} - {% if contacts.exists %} - - - - - - - - - - {% for contact in contacts %} - - - - - - - - - {% endfor %} -
NameRolePriorityPhoneEmail
{{ contact.contact|linkify }}{{ contact.role|placeholder }}{{ contact.get_priority_display|placeholder }} - {% if contact.contact.phone %} - {{ contact.contact.phone }} - {% else %} - {{ ''|placeholder }} - {% endif %} - - {% if contact.contact.email %} - {{ contact.contact.email }} - {% else %} - {{ ''|placeholder }} - {% endif %} - - {% if perms.tenancy.change_contactassignment %} - - - - {% endif %} - {% if perms.tenancy.delete_contactassignment %} - - - - {% endif %} -
- {% else %} -
None
- {% endif %} - {% endwith %} -
- {% if perms.tenancy.add_contactassignment %} - - {% endif %} -
diff --git a/netbox/templates/ipam/l2vpn.html b/netbox/templates/ipam/l2vpn.html index 87050eb26..8896dd6c2 100644 --- a/netbox/templates/ipam/l2vpn.html +++ b/netbox/templates/ipam/l2vpn.html @@ -37,7 +37,6 @@ {% plugin_left_page object %}
- {% include 'inc/panels/contacts.html' %} {% include 'inc/panels/custom_fields.html' %} {% include 'inc/panels/comments.html' %} {% plugin_right_page object %} diff --git a/netbox/templates/tenancy/object_contacts.html b/netbox/templates/tenancy/object_contacts.html new file mode 100644 index 000000000..aca63a379 --- /dev/null +++ b/netbox/templates/tenancy/object_contacts.html @@ -0,0 +1,27 @@ +{% extends base_template %} +{% load helpers %} + +{% block extra_controls %} + {% if perms.tenancy.add_contactassignment %} + + Add a contact + + {% endif %} +{% endblock %} + +{% block content %} + {% include 'inc/table_controls_htmx.html' with table_modal="ContactTable_config" %} +
+ {% csrf_token %} +
+
+ {% include 'htmx/table.html' %} +
+
+
+{% endblock content %} + +{% block modals %} + {{ block.super }} + {% table_config_form table %} +{% endblock modals %} diff --git a/netbox/templates/tenancy/tenant.html b/netbox/templates/tenancy/tenant.html index da48f1ef5..34abe5c01 100644 --- a/netbox/templates/tenancy/tenant.html +++ b/netbox/templates/tenancy/tenant.html @@ -30,7 +30,6 @@ {% include 'inc/panels/custom_fields.html' %} {% include 'inc/panels/tags.html' %} {% include 'inc/panels/comments.html' %} - {% include 'inc/panels/contacts.html' %} {% plugin_left_page object %}
diff --git a/netbox/templates/virtualization/cluster.html b/netbox/templates/virtualization/cluster.html index 3dfef108b..508bca547 100644 --- a/netbox/templates/virtualization/cluster.html +++ b/netbox/templates/virtualization/cluster.html @@ -84,7 +84,6 @@
{% include 'inc/panels/custom_fields.html' %} {% include 'inc/panels/tags.html' %} - {% include 'inc/panels/contacts.html' %} {% plugin_right_page object %}
diff --git a/netbox/templates/virtualization/clustergroup.html b/netbox/templates/virtualization/clustergroup.html index 510433068..2496ad085 100644 --- a/netbox/templates/virtualization/clustergroup.html +++ b/netbox/templates/virtualization/clustergroup.html @@ -37,7 +37,6 @@
{% include 'inc/panels/related_objects.html' %} {% include 'inc/panels/custom_fields.html' %} - {% include 'inc/panels/contacts.html' %} {% plugin_right_page object %}
diff --git a/netbox/templates/virtualization/virtualmachine.html b/netbox/templates/virtualization/virtualmachine.html index 5098a2f8f..51fd8aa80 100644 --- a/netbox/templates/virtualization/virtualmachine.html +++ b/netbox/templates/virtualization/virtualmachine.html @@ -158,7 +158,6 @@ {% endif %} - {% include 'inc/panels/contacts.html' %} {% plugin_right_page object %} diff --git a/netbox/tenancy/views.py b/netbox/tenancy/views.py index b9ada8640..5f8a7e314 100644 --- a/netbox/tenancy/views.py +++ b/netbox/tenancy/views.py @@ -7,17 +7,40 @@ from dcim.models import Cable, Device, Location, Rack, RackReservation, Site, Vi from ipam.models import Aggregate, ASN, IPAddress, IPRange, L2VPN, Prefix, VLAN, VRF from netbox.views import generic from utilities.utils import count_related -from utilities.views import register_model_view +from utilities.views import register_model_view, ViewTab from virtualization.models import VirtualMachine, Cluster from wireless.models import WirelessLAN, WirelessLink from . import filtersets, forms, tables from .models import * +class ObjectContactsView(generic.ObjectChildrenView): + child_model = Contact + table = tables.ContactTable + filterset = filtersets.ContactFilterSet + template_name = 'tenancy/object_contacts.html' + tab = ViewTab( + label=_('Contacts'), + badge=lambda obj: obj.contacts.count(), + permission='tenancy.view_contact', + weight=5000 + ) + + def get_children(self, request, parent): + return Contact.objects.annotate( + assignment_count=count_related(ContactAssignment, 'contact') + ).restrict(request.user, 'view').filter(assignments__object_id=parent.pk) + + def get_extra_context(self, request, instance): + return { + 'base_template': f'{instance._meta.app_label}/{instance._meta.model_name}.html', + } + # # Tenant groups # + class TenantGroupListView(generic.ObjectListView): queryset = TenantGroup.objects.add_related_count( TenantGroup.objects.all(), @@ -165,6 +188,11 @@ class TenantBulkDeleteView(generic.BulkDeleteView): table = tables.TenantTable +@register_model_view(Tenant, 'contacts') +class TenantContactsView(ObjectContactsView): + queryset = Tenant.objects.all() + + # # Contact groups # @@ -342,11 +370,11 @@ class ContactBulkDeleteView(generic.BulkDeleteView): filterset = filtersets.ContactFilterSet table = tables.ContactTable - # # Contact assignments # + class ContactAssignmentListView(generic.ObjectListView): queryset = ContactAssignment.objects.all() filterset = filtersets.ContactAssignmentFilterSet diff --git a/netbox/virtualization/views.py b/netbox/virtualization/views.py index 9014aa9dd..4a501e14e 100644 --- a/netbox/virtualization/views.py +++ b/netbox/virtualization/views.py @@ -9,9 +9,10 @@ from dcim.filtersets import DeviceFilterSet from dcim.models import Device from dcim.tables import DeviceTable from extras.views import ObjectConfigContextView -from ipam.models import IPAddress, Service +from ipam.models import IPAddress from ipam.tables import InterfaceVLANTable from netbox.views import generic +from tenancy.views import ObjectContactsView from utilities.utils import count_related from utilities.views import ViewTab, register_model_view from . import filtersets, forms, tables @@ -140,6 +141,11 @@ class ClusterGroupBulkDeleteView(generic.BulkDeleteView): table = tables.ClusterGroupTable +@register_model_view(ClusterGroup, 'contacts') +class ClusterGroupContactsView(ObjectContactsView): + queryset = ClusterGroup.objects.all() + + # # Clusters # @@ -312,6 +318,11 @@ class ClusterRemoveDevicesView(generic.ObjectEditView): }) +@register_model_view(Cluster, 'contacts') +class ClusterContactsView(ObjectContactsView): + queryset = Cluster.objects.all() + + # # Virtual machines # @@ -390,6 +401,11 @@ class VirtualMachineBulkDeleteView(generic.BulkDeleteView): table = tables.VirtualMachineTable +@register_model_view(VirtualMachine, 'contacts') +class VirtualMachineContactsView(ObjectContactsView): + queryset = VirtualMachine.objects.all() + + # # VM interfaces # From cc0c985fecffefac621cf414f560a52655c21335 Mon Sep 17 00:00:00 2001 From: Jon Schewe Date: Fri, 12 May 2023 09:35:09 -0500 Subject: [PATCH 005/170] Feature/remote group autocreate (#12394) * Add REMOTE_AUTH_AUTOCREATE_GROUPS When REMOTE_AUTH_AUTOCREATE_GROUPS is True, Netbox will create groups referenced in the REMOTE_AUTH_GROUP_HEADER that don't exist in the database. Closes #7671 * Fix naming of parameter Apply the fix requested by kkthxbye-code in https://github.com/netbox-community/netbox/pull/8603 --------- Co-authored-by: Lars Kellogg-Stedman --- netbox/netbox/authentication.py | 7 +++- netbox/netbox/settings.py | 1 + netbox/netbox/tests/test_authentication.py | 44 ++++++++++++++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/netbox/netbox/authentication.py b/netbox/netbox/authentication.py index 798cb80e2..61dfe2fdb 100644 --- a/netbox/netbox/authentication.py +++ b/netbox/netbox/authentication.py @@ -156,8 +156,11 @@ class RemoteUserBackend(_RemoteUserBackend): try: group_list.append(Group.objects.get(name=name)) except Group.DoesNotExist: - logging.error( - f"Could not assign group {name} to remotely-authenticated user {user}: Group not found") + if settings.REMOTE_AUTH_AUTO_CREATE_GROUPS: + group_list.append(Group.objects.create(name=name)) + else: + logging.error( + f"Could not assign group {name} to remotely-authenticated user {user}: Group not found") if group_list: user.groups.set(group_list) logger.debug( diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 3f3f96736..b56ae46c4 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -122,6 +122,7 @@ PLUGINS_CONFIG = getattr(configuration, 'PLUGINS_CONFIG', {}) QUEUE_MAPPINGS = getattr(configuration, 'QUEUE_MAPPINGS', {}) RELEASE_CHECK_URL = getattr(configuration, 'RELEASE_CHECK_URL', None) REMOTE_AUTH_AUTO_CREATE_USER = getattr(configuration, 'REMOTE_AUTH_AUTO_CREATE_USER', False) +REMOTE_AUTH_AUTO_CREATE_GROUPS = getattr(configuration, 'REMOTE_AUTH_AUTO_CREATE_GROUPS', False) REMOTE_AUTH_BACKEND = getattr(configuration, 'REMOTE_AUTH_BACKEND', 'netbox.authentication.RemoteUserBackend') REMOTE_AUTH_DEFAULT_GROUPS = getattr(configuration, 'REMOTE_AUTH_DEFAULT_GROUPS', []) REMOTE_AUTH_DEFAULT_PERMISSIONS = getattr(configuration, 'REMOTE_AUTH_DEFAULT_PERMISSIONS', {}) diff --git a/netbox/netbox/tests/test_authentication.py b/netbox/netbox/tests/test_authentication.py index 790cb4bd8..4e46996b5 100644 --- a/netbox/netbox/tests/test_authentication.py +++ b/netbox/netbox/tests/test_authentication.py @@ -310,6 +310,50 @@ class ExternalAuthenticationTestCase(TestCase): list(new_user.groups.all()) ) + @override_settings( + REMOTE_AUTH_ENABLED=True, + REMOTE_AUTH_AUTO_CREATE_USER=True, + REMOTE_AUTH_GROUP_SYNC_ENABLED=True, + REMOTE_AUTH_AUTO_CREATE_GROUPS=True, + LOGIN_REQUIRED=True, + ) + def test_remote_auth_remote_groups_autocreate(self): + """ + Test enabling remote authentication with group sync and autocreate + enabled with the default configuration. + """ + headers = { + "HTTP_REMOTE_USER": "remoteuser2", + "HTTP_REMOTE_USER_GROUP": "Group 1|Group 2", + } + + self.assertTrue(settings.REMOTE_AUTH_ENABLED) + self.assertTrue(settings.REMOTE_AUTH_AUTO_CREATE_USER) + self.assertTrue(settings.REMOTE_AUTH_AUTO_CREATE_GROUPS) + self.assertTrue(settings.REMOTE_AUTH_GROUP_SYNC_ENABLED) + self.assertEqual(settings.REMOTE_AUTH_HEADER, "HTTP_REMOTE_USER") + self.assertEqual(settings.REMOTE_AUTH_GROUP_HEADER, "HTTP_REMOTE_USER_GROUP") + self.assertEqual(settings.REMOTE_AUTH_GROUP_SEPARATOR, "|") + + groups = ( + Group(name="Group 1"), + Group(name="Group 2"), + ) + + response = self.client.get(reverse("home"), follow=True, **headers) + self.assertEqual(response.status_code, 200) + + new_user = User.objects.get(username="remoteuser2") + self.assertEqual( + int(self.client.session.get("_auth_user_id")), + new_user.pk, + msg="Authentication failed", + ) + self.assertListEqual( + [group.name for group in groups], + [group.name for group in list(new_user.groups.all())], + ) + @override_settings( REMOTE_AUTH_ENABLED=True, REMOTE_AUTH_AUTO_CREATE_USER=True, From 9b80ec22bad4b555c0851e6338840d14027b4933 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Fri, 12 May 2023 20:20:51 +0530 Subject: [PATCH 006/170] Adds db read-only middleware (#12490) * adds db read-only middleware #11233 * fixed attribute error * replaces getattr with get_config --- netbox/netbox/middleware.py | 48 ++++++++++++++++++++++++++++++++++--- netbox/netbox/settings.py | 1 + 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/netbox/netbox/middleware.py b/netbox/netbox/middleware.py index f9faa9c5d..461c018b9 100644 --- a/netbox/netbox/middleware.py +++ b/netbox/netbox/middleware.py @@ -3,19 +3,21 @@ import uuid from urllib import parse from django.conf import settings -from django.contrib import auth +from django.contrib import auth, messages from django.contrib.auth.middleware import RemoteUserMiddleware as RemoteUserMiddleware_ from django.core.exceptions import ImproperlyConfigured -from django.db import ProgrammingError +from django.db import connection, ProgrammingError +from django.db.utils import InternalError from django.http import Http404, HttpResponseRedirect from extras.context_managers import change_logging -from netbox.config import clear_config +from netbox.config import clear_config, get_config from netbox.views import handler_500 from utilities.api import is_api_request, rest_api_server_error __all__ = ( 'CoreMiddleware', + 'MaintenanceModeMiddleware', 'RemoteUserMiddleware', ) @@ -166,3 +168,43 @@ class RemoteUserMiddleware(RemoteUserMiddleware_): groups = [] logger.debug(f"Groups are {groups}") return groups + + +class MaintenanceModeMiddleware: + """ + Middleware that checks if the application is in maintenance mode + and restricts write-related operations to the database. + """ + + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + if get_config().MAINTENANCE_MODE: + self._prevent_db_write_operations() + + return self.get_response(request) + + @staticmethod + def _prevent_db_write_operations(): + """ + Prevent any write-related database operations. + """ + with connection.cursor() as cursor: + cursor.execute( + 'SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY;' + ) + + def process_exception(self, request, exception): + """ + Prevent any write-related database operations if an exception is raised. + """ + if isinstance(exception, InternalError): + error_message = 'NetBox is currently operating in maintenance mode and is unable to perform write ' \ + 'operations. Please try again later.' + + if is_api_request(request): + return rest_api_server_error(request, error=error_message) + + messages.error(request, error_message) + return HttpResponseRedirect(request.path_info) diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index b56ae46c4..575755d2b 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -383,6 +383,7 @@ MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'netbox.middleware.RemoteUserMiddleware', 'netbox.middleware.CoreMiddleware', + 'netbox.middleware.MaintenanceModeMiddleware', 'django_prometheus.middleware.PrometheusAfterMiddleware', ] From ff874a24dd62071d85be9d40106414a121bdc91b Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Fri, 12 May 2023 10:56:36 -0400 Subject: [PATCH 007/170] #7671: Document REMOTE_AUTH_AUTO_CREATE_GROUPS config parameter --- docs/configuration/remote-authentication.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/configuration/remote-authentication.md b/docs/configuration/remote-authentication.md index fd95adef5..fb789bd98 100644 --- a/docs/configuration/remote-authentication.md +++ b/docs/configuration/remote-authentication.md @@ -4,6 +4,14 @@ The configuration parameters listed here control remote authentication for NetBo --- +## REMOTE_AUTH_AUTO_CREATE_GROUPS + +Default: `False` + +If true, NetBox will automatically create groups specified in the `REMOTE_AUTH_GROUP_HEADER` header if they don't already exist. (Requires `REMOTE_AUTH_ENABLED`.) + +--- + ## REMOTE_AUTH_AUTO_CREATE_USER Default: `False` From 567285d36ae61b5151feaeb585b1d7c36d7b76fe Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Fri, 12 May 2023 11:00:33 -0400 Subject: [PATCH 008/170] Changelog for #7671, #10686, #11233, #11559, #12554 --- docs/release-notes/version-3.5.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/release-notes/version-3.5.md b/docs/release-notes/version-3.5.md index 36dc97bb9..feb2f177e 100644 --- a/docs/release-notes/version-3.5.md +++ b/docs/release-notes/version-3.5.md @@ -4,6 +4,9 @@ ### Enhancements +* [#7671](https://github.com/netbox-community/netbox/issues/7671) - Introduce `REMOTE_AUTH_AUTO_CREATE_GROUPS` config parameter to enable the automatic creation of new groups when remote authentication is in use +* [#11233](https://github.com/netbox-community/netbox/issues/11233) - Intercept and cleanly report errors upon attempted database writes when maintenance mode is enabled +* [#11599](https://github.com/netbox-community/netbox/issues/11599) - Move contacts panels to separate tabs under object views * [#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 @@ -12,9 +15,11 @@ * [#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 +* [#12554](https://github.com/netbox-community/netbox/issues/12554) - Allow customization or disabling of the maintenance mode banner ### Bug Fixes +* [#10686](https://github.com/netbox-community/netbox/issues/10686) - Enable specifying termination object by virtual chassis master when importing cables * [#12550](https://github.com/netbox-community/netbox/issues/12550) - Fix rear port selection widget under front port creation form --- From 39fd64b2effe4b5ee0ea8b41132624f458fb7ab8 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Fri, 12 May 2023 11:08:32 -0400 Subject: [PATCH 009/170] Fixes #12570: Disable ordering of synchronized object tables by the synced attribute --- docs/release-notes/version-3.5.md | 1 + netbox/extras/tables/tables.py | 3 +++ 2 files changed, 4 insertions(+) diff --git a/docs/release-notes/version-3.5.md b/docs/release-notes/version-3.5.md index feb2f177e..39e159268 100644 --- a/docs/release-notes/version-3.5.md +++ b/docs/release-notes/version-3.5.md @@ -21,6 +21,7 @@ * [#10686](https://github.com/netbox-community/netbox/issues/10686) - Enable specifying termination object by virtual chassis master when importing cables * [#12550](https://github.com/netbox-community/netbox/issues/12550) - Fix rear port selection widget under front port creation form +* [#12570](https://github.com/netbox-community/netbox/issues/12570) - Disable ordering of synchronized object tables by the "synced" attribute --- diff --git a/netbox/extras/tables/tables.py b/netbox/extras/tables/tables.py index e6d014302..8d046b85d 100644 --- a/netbox/extras/tables/tables.py +++ b/netbox/extras/tables/tables.py @@ -73,6 +73,7 @@ class ExportTemplateTable(NetBoxTable): linkify=True ) is_synced = columns.BooleanColumn( + orderable=False, verbose_name='Synced' ) @@ -218,6 +219,7 @@ class ConfigContextTable(NetBoxTable): verbose_name='Active' ) is_synced = columns.BooleanColumn( + orderable=False, verbose_name='Synced' ) @@ -242,6 +244,7 @@ class ConfigTemplateTable(NetBoxTable): linkify=True ) is_synced = columns.BooleanColumn( + orderable=False, verbose_name='Synced' ) tags = columns.TagColumn( From 21f4761335570d06c76294d88905211e4cf2eb20 Mon Sep 17 00:00:00 2001 From: Arthur Hanson Date: Fri, 12 May 2023 13:08:57 -0700 Subject: [PATCH 010/170] 12468 disallow double underscores in custom field names (#12523) * 12468 disallow double underscores in custom field names * 12468 disallow double underscores in custom field names * 12468 review changes * 12468 correct migration * 12468 use inverse match * 12468 use inverse match * Add test for invalid custom field names --------- Co-authored-by: jeremystretch --- .../0066_customfield_name_validation.py | 18 +++++++++++++++++- netbox/extras/models/customfields.py | 6 ++++++ netbox/extras/tests/test_customfields.py | 11 +++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/netbox/extras/migrations/0066_customfield_name_validation.py b/netbox/extras/migrations/0066_customfield_name_validation.py index 7a768c10c..3d2c51399 100644 --- a/netbox/extras/migrations/0066_customfield_name_validation.py +++ b/netbox/extras/migrations/0066_customfield_name_validation.py @@ -13,6 +13,22 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='customfield', name='name', - field=models.CharField(max_length=50, unique=True, validators=[django.core.validators.RegexValidator(flags=re.RegexFlag['IGNORECASE'], message='Only alphanumeric characters and underscores are allowed.', regex='^[a-z0-9_]+$')]), + field=models.CharField( + max_length=50, + unique=True, + validators=[ + django.core.validators.RegexValidator( + flags=re.RegexFlag['IGNORECASE'], + message='Only alphanumeric characters and underscores are allowed.', + regex='^[a-z0-9_]+$', + ), + django.core.validators.RegexValidator( + flags=re.RegexFlag['IGNORECASE'], + inverse_match=True, + message='Double underscores are not permitted in custom field names.', + regex=r'__', + ), + ], + ), ), ] diff --git a/netbox/extras/models/customfields.py b/netbox/extras/models/customfields.py index 439d15edc..be3540f08 100644 --- a/netbox/extras/models/customfields.py +++ b/netbox/extras/models/customfields.py @@ -85,6 +85,12 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel): message="Only alphanumeric characters and underscores are allowed.", flags=re.IGNORECASE ), + RegexValidator( + regex=r'__', + message="Double underscores are not permitted in custom field names.", + flags=re.IGNORECASE, + inverse_match=True + ), ) ) label = models.CharField( diff --git a/netbox/extras/tests/test_customfields.py b/netbox/extras/tests/test_customfields.py index 6a3a3d074..3fd0dc83e 100644 --- a/netbox/extras/tests/test_customfields.py +++ b/netbox/extras/tests/test_customfields.py @@ -29,6 +29,17 @@ class CustomFieldTest(TestCase): cls.object_type = ContentType.objects.get_for_model(Site) + def test_invalid_name(self): + """ + Try creating a CustomField with an invalid name. + """ + with self.assertRaises(ValidationError): + # Invalid character + CustomField(name='?', type=CustomFieldTypeChoices.TYPE_TEXT).full_clean() + with self.assertRaises(ValidationError): + # Double underscores not permitted + CustomField(name='foo__bar', type=CustomFieldTypeChoices.TYPE_TEXT).full_clean() + def test_text_field(self): value = 'Foobar!' From e40e9cb4064b7128e4a590fa3a99d5ff0a45723c Mon Sep 17 00:00:00 2001 From: Austin de Coup-Crank <94914780+decoupca@users.noreply.github.com> Date: Fri, 12 May 2023 15:10:12 -0500 Subject: [PATCH 011/170] Closes #11017: increase maximum power draw (#12587) * Convert power draw/max draw to PositiveIntegerField * Closes #11017: Increase maximum power draw * Rename migration file for clarity --------- Co-authored-by: jeremystretch --- .../0172_larger_power_draw_values.py | 42 +++++++++++++++++++ .../dcim/models/device_component_templates.py | 4 +- netbox/dcim/models/device_components.py | 4 +- 3 files changed, 46 insertions(+), 4 deletions(-) create mode 100644 netbox/dcim/migrations/0172_larger_power_draw_values.py diff --git a/netbox/dcim/migrations/0172_larger_power_draw_values.py b/netbox/dcim/migrations/0172_larger_power_draw_values.py new file mode 100644 index 000000000..729daf836 --- /dev/null +++ b/netbox/dcim/migrations/0172_larger_power_draw_values.py @@ -0,0 +1,42 @@ +# Generated by Django 4.1.9 on 2023-05-12 18:46 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dcim', '0171_cabletermination_change_logging'), + ] + + operations = [ + migrations.AlterField( + model_name='powerport', + name='allocated_draw', + field=models.PositiveIntegerField( + blank=True, null=True, validators=[django.core.validators.MinValueValidator(1)] + ), + ), + migrations.AlterField( + model_name='powerport', + name='maximum_draw', + field=models.PositiveIntegerField( + blank=True, null=True, validators=[django.core.validators.MinValueValidator(1)] + ), + ), + migrations.AlterField( + model_name='powerporttemplate', + name='allocated_draw', + field=models.PositiveIntegerField( + blank=True, null=True, validators=[django.core.validators.MinValueValidator(1)] + ), + ), + migrations.AlterField( + model_name='powerporttemplate', + name='maximum_draw', + field=models.PositiveIntegerField( + blank=True, null=True, validators=[django.core.validators.MinValueValidator(1)] + ), + ), + ] diff --git a/netbox/dcim/models/device_component_templates.py b/netbox/dcim/models/device_component_templates.py index b0dc1677f..6a89655b2 100644 --- a/netbox/dcim/models/device_component_templates.py +++ b/netbox/dcim/models/device_component_templates.py @@ -232,13 +232,13 @@ class PowerPortTemplate(ModularComponentTemplateModel): choices=PowerPortTypeChoices, blank=True ) - maximum_draw = models.PositiveSmallIntegerField( + maximum_draw = models.PositiveIntegerField( blank=True, null=True, validators=[MinValueValidator(1)], help_text=_("Maximum power draw (watts)") ) - allocated_draw = models.PositiveSmallIntegerField( + allocated_draw = models.PositiveIntegerField( blank=True, null=True, validators=[MinValueValidator(1)], diff --git a/netbox/dcim/models/device_components.py b/netbox/dcim/models/device_components.py index 645fc5c5c..9f6837b92 100644 --- a/netbox/dcim/models/device_components.py +++ b/netbox/dcim/models/device_components.py @@ -329,13 +329,13 @@ class PowerPort(ModularComponentModel, CabledObjectModel, PathEndpoint): blank=True, help_text=_('Physical port type') ) - maximum_draw = models.PositiveSmallIntegerField( + maximum_draw = models.PositiveIntegerField( blank=True, null=True, validators=[MinValueValidator(1)], help_text=_("Maximum power draw (watts)") ) - allocated_draw = models.PositiveSmallIntegerField( + allocated_draw = models.PositiveIntegerField( blank=True, null=True, validators=[MinValueValidator(1)], From 0f44f7eb20ebf10dd0418d42c57c6511986a262b Mon Sep 17 00:00:00 2001 From: Devon Mar Date: Sun, 14 May 2023 01:08:25 -0700 Subject: [PATCH 012/170] Use .font-monospace instead of .text-monospace --- netbox/templates/dcim/interface.html | 4 ++-- netbox/templates/virtualization/vminterface.html | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/netbox/templates/dcim/interface.html b/netbox/templates/dcim/interface.html index db0fd7dfd..11f262eeb 100644 --- a/netbox/templates/dcim/interface.html +++ b/netbox/templates/dcim/interface.html @@ -123,11 +123,11 @@ - + - + diff --git a/netbox/templates/virtualization/vminterface.html b/netbox/templates/virtualization/vminterface.html index a7d4d92ba..82071bdaa 100644 --- a/netbox/templates/virtualization/vminterface.html +++ b/netbox/templates/virtualization/vminterface.html @@ -59,7 +59,7 @@ - + From c65b2a080f693d858078faabfbdfb3b1967cfa2a Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Mon, 15 May 2023 09:13:11 -0400 Subject: [PATCH 013/170] Changelog for #11017, #12468 --- docs/release-notes/version-3.5.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/release-notes/version-3.5.md b/docs/release-notes/version-3.5.md index 39e159268..b101a7912 100644 --- a/docs/release-notes/version-3.5.md +++ b/docs/release-notes/version-3.5.md @@ -5,6 +5,7 @@ ### Enhancements * [#7671](https://github.com/netbox-community/netbox/issues/7671) - Introduce `REMOTE_AUTH_AUTO_CREATE_GROUPS` config parameter to enable the automatic creation of new groups when remote authentication is in use +* [#11017](https://github.com/netbox-community/netbox/issues/11017) - Increase the maximum values for allocated and maximum power draws * [#11233](https://github.com/netbox-community/netbox/issues/11233) - Intercept and cleanly report errors upon attempted database writes when maintenance mode is enabled * [#11599](https://github.com/netbox-community/netbox/issues/11599) - Move contacts panels to separate tabs under object views * [#11670](https://github.com/netbox-community/netbox/issues/11670) - Enable setting device type & module type weight via bulk import @@ -20,6 +21,7 @@ ### Bug Fixes * [#10686](https://github.com/netbox-community/netbox/issues/10686) - Enable specifying termination object by virtual chassis master when importing cables +* [#12468](https://github.com/netbox-community/netbox/issues/12468) - Custom field names should not permit double underscores * [#12550](https://github.com/netbox-community/netbox/issues/12550) - Fix rear port selection widget under front port creation form * [#12570](https://github.com/netbox-community/netbox/issues/12570) - Disable ordering of synchronized object tables by the "synced" attribute From 0ad88e24312c256ef0a3189c71009d5b8d97cdf5 Mon Sep 17 00:00:00 2001 From: kkthxbye <400797+kkthxbye-code@users.noreply.github.com> Date: Tue, 16 May 2023 08:19:57 +0200 Subject: [PATCH 014/170] Changed docs to reflect the new URL for the dynamic API documentation --- docs/integrations/rest-api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/integrations/rest-api.md b/docs/integrations/rest-api.md index af2f86e4c..cf3d11126 100644 --- a/docs/integrations/rest-api.md +++ b/docs/integrations/rest-api.md @@ -63,7 +63,7 @@ Each attribute of the IP address is expressed as an attribute of the JSON object ## Interactive Documentation -Comprehensive, interactive documentation of all REST API endpoints is available on a running NetBox instance at `/api/docs/`. This interface provides a convenient sandbox for researching and experimenting with specific endpoints and request types. The API itself can also be explored using a web browser by navigating to its root at `/api/`. +Comprehensive, interactive documentation of all REST API endpoints is available on a running NetBox instance at `/api/schema/swagger-ui/`. This interface provides a convenient sandbox for researching and experimenting with specific endpoints and request types. The API itself can also be explored using a web browser by navigating to its root at `/api/`. ## Endpoint Hierarchy From d5be59ef67c4a48698543ee9fe0c3d53254578bd Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Tue, 16 May 2023 08:39:05 -0400 Subject: [PATCH 015/170] Update README --- README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 480f0f856..81217797f 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,13 @@
+ The :ballot_box_with_check: 2023 NetBox Community Survey is now open! +

Please take a few minutes to tell us about your NetBox deployment.

+ NetBox logo - - The premiere source of truth powering network automation +

The premiere source of truth powering network automation

+ CI status +

-![Master branch build status](https://github.com/netbox-community/netbox/workflows/CI/badge.svg?branch=master) - NetBox is the leading solution for modeling and documenting modern networks. By combining the traditional disciplines of IP address management (IPAM) and datacenter infrastructure management (DCIM) with powerful APIs and extensions, From eeb15ab5d1531b6502734c5592755f498d2bcf7c Mon Sep 17 00:00:00 2001 From: Arthur Date: Mon, 15 May 2023 14:22:45 -0700 Subject: [PATCH 016/170] 12594 add config context to object count / list widget --- netbox/extras/dashboard/widgets.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/netbox/extras/dashboard/widgets.py b/netbox/extras/dashboard/widgets.py index 69d1cc36d..dc68e1388 100644 --- a/netbox/extras/dashboard/widgets.py +++ b/netbox/extras/dashboard/widgets.py @@ -35,7 +35,8 @@ def get_content_type_labels(): return [ (content_type_identifier(ct), content_type_name(ct)) for ct in ContentType.objects.filter( - FeatureQuery('export_templates').get_query() | Q(app_label='extras', model='objectchange') + FeatureQuery('export_templates').get_query() | Q(app_label='extras', model='objectchange') | + Q(app_label='extras', model='configcontext') ).order_by('app_label', 'model') ] From 0df6a5793aefd64172d4e62f02e9d592dc715330 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Tue, 16 May 2023 08:35:21 -0700 Subject: [PATCH 017/170] Adds maintenance exempt paths (#12592) * adds maintenance exempt paths #11233 * adds maintenance exempt paths #11233 * Rename method & remove login/logout from exempt paths --------- Co-authored-by: jeremystretch --- netbox/netbox/middleware.py | 14 +++++++++----- netbox/netbox/settings.py | 5 +++++ 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/netbox/netbox/middleware.py b/netbox/netbox/middleware.py index 461c018b9..76b3e42a8 100644 --- a/netbox/netbox/middleware.py +++ b/netbox/netbox/middleware.py @@ -181,19 +181,23 @@ class MaintenanceModeMiddleware: def __call__(self, request): if get_config().MAINTENANCE_MODE: - self._prevent_db_write_operations() + self._set_session_type( + allow_write=request.path_info.startswith(settings.MAINTENANCE_EXEMPT_PATHS) + ) return self.get_response(request) @staticmethod - def _prevent_db_write_operations(): + def _set_session_type(allow_write): """ Prevent any write-related database operations. + + Args: + allow_write (bool): If True, write operations will be permitted. """ with connection.cursor() as cursor: - cursor.execute( - 'SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY;' - ) + mode = 'READ WRITE' if allow_write else 'READ ONLY' + cursor.execute(f'SET SESSION CHARACTERISTICS AS TRANSACTION {mode};') def process_exception(self, request, exception): """ diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 575755d2b..4d2dd8a81 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -478,6 +478,11 @@ AUTH_EXEMPT_PATHS = ( f'/{BASE_PATH}metrics', ) +# All URLs starting with a string listed here are exempt from maintenance mode enforcement +MAINTENANCE_EXEMPT_PATHS = ( + f'/{BASE_PATH}admin/', +) + SERIALIZATION_MODULES = { 'json': 'utilities.serializers.json', } From 2204735e9fbe93b1c77ba047a85845b9fdaffc41 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Tue, 16 May 2023 11:10:44 -0700 Subject: [PATCH 018/170] Adds rq retry options (#12588) * adds rq retry options #12327 * Clean up docs; disable retries of failed jobs by default * Pass a Retry object only if RQ_RETRY_MAX is non-zero --------- Co-authored-by: jeremystretch --- docs/configuration/miscellaneous.md | 22 ++++++++++++++++++++++ netbox/core/models/jobs.py | 5 +++-- netbox/extras/webhooks.py | 4 +++- netbox/netbox/settings.py | 2 ++ netbox/utilities/rqworker.py | 14 +++++++++++++- 5 files changed, 43 insertions(+), 4 deletions(-) diff --git a/docs/configuration/miscellaneous.md b/docs/configuration/miscellaneous.md index e3728acab..fd410a9d4 100644 --- a/docs/configuration/miscellaneous.md +++ b/docs/configuration/miscellaneous.md @@ -204,3 +204,25 @@ This parameter defines the URL of the repository that will be checked for new Ne Default: `300` The maximum execution time of a background task (such as running a custom script), in seconds. + +--- + +## RQ_RETRY_INTERVAL + +!!! note + This parameter was added in NetBox v3.5. + +Default: `60` + +This parameter controls how frequently a failed job is retried, up to the maximum number of times specified by `RQ_RETRY_MAX`. This must be either an integer specifying the number of seconds to wait between successive attempts, or a list of such values. For example, `[60, 300, 3600]` will retry the task after 1 minute, 5 minutes, and 1 hour. + +--- + +## RQ_RETRY_MAX + +!!! note + This parameter was added in NetBox v3.5. + +Default: `0` (retries disabled) + +The maximum number of times a background task will be retried before being marked as failed. diff --git a/netbox/core/models/jobs.py b/netbox/core/models/jobs.py index 1d0eecd21..a91e75e61 100644 --- a/netbox/core/models/jobs.py +++ b/netbox/core/models/jobs.py @@ -16,7 +16,7 @@ from extras.utils import FeatureQuery from netbox.config import get_config from netbox.constants import RQ_QUEUE_DEFAULT from utilities.querysets import RestrictedQuerySet -from utilities.rqworker import get_queue_for_model +from utilities.rqworker import get_queue_for_model, get_rq_retry __all__ = ( 'Job', @@ -219,5 +219,6 @@ class Job(models.Model): event=event, data=self.data, timestamp=str(timezone.now()), - username=self.user.username + username=self.user.username, + retry=get_rq_retry() ) diff --git a/netbox/extras/webhooks.py b/netbox/extras/webhooks.py index 23702949a..1fc869ee8 100644 --- a/netbox/extras/webhooks.py +++ b/netbox/extras/webhooks.py @@ -9,6 +9,7 @@ from netbox.config import get_config from netbox.constants import RQ_QUEUE_DEFAULT from netbox.registry import registry from utilities.api import get_serializer_for_model +from utilities.rqworker import get_rq_retry from utilities.utils import serialize_object from .choices import * from .models import Webhook @@ -116,5 +117,6 @@ def flush_webhooks(queue): snapshots=data['snapshots'], timestamp=str(timezone.now()), username=data['username'], - request_id=data['request_id'] + request_id=data['request_id'], + retry=get_rq_retry() ) diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 4d2dd8a81..d6d5c84ca 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -140,6 +140,8 @@ REMOTE_AUTH_STAFF_USERS = getattr(configuration, 'REMOTE_AUTH_STAFF_USERS', []) REMOTE_AUTH_GROUP_SEPARATOR = getattr(configuration, 'REMOTE_AUTH_GROUP_SEPARATOR', '|') REPORTS_ROOT = getattr(configuration, 'REPORTS_ROOT', os.path.join(BASE_DIR, 'reports')).rstrip('/') RQ_DEFAULT_TIMEOUT = getattr(configuration, 'RQ_DEFAULT_TIMEOUT', 300) +RQ_RETRY_INTERVAL = getattr(configuration, 'RQ_RETRY_INTERVAL', 60) +RQ_RETRY_MAX = getattr(configuration, 'RQ_RETRY_MAX', 0) SCRIPTS_ROOT = getattr(configuration, 'SCRIPTS_ROOT', os.path.join(BASE_DIR, 'scripts')).rstrip('/') SEARCH_BACKEND = getattr(configuration, 'SEARCH_BACKEND', 'netbox.search.backends.CachedValueSearchBackend') SECURE_SSL_REDIRECT = getattr(configuration, 'SECURE_SSL_REDIRECT', False) diff --git a/netbox/utilities/rqworker.py b/netbox/utilities/rqworker.py index 5866dfee0..61f594767 100644 --- a/netbox/utilities/rqworker.py +++ b/netbox/utilities/rqworker.py @@ -1,11 +1,12 @@ from django_rq.queues import get_connection -from rq import Worker +from rq import Retry, Worker from netbox.config import get_config from netbox.constants import RQ_QUEUE_DEFAULT __all__ = ( 'get_queue_for_model', + 'get_rq_retry', 'get_workers_for_queue', ) @@ -22,3 +23,14 @@ def get_workers_for_queue(queue_name): Returns True if a worker process is currently servicing the specified queue. """ return Worker.count(get_connection(queue_name)) + + +def get_rq_retry(): + """ + If RQ_RETRY_MAX is defined and greater than zero, instantiate and return a Retry object to be + used when queuing a job. Otherwise, return None. + """ + retry_max = get_config().RQ_RETRY_MAX + retry_interval = get_config().RQ_RETRY_INTERVAL + if retry_max: + return Retry(max=retry_max, interval=retry_interval) From 92c49669f96d8f6427f7ec4e4ea83712362f5680 Mon Sep 17 00:00:00 2001 From: Arthur Date: Thu, 18 May 2023 10:40:41 -0700 Subject: [PATCH 019/170] 12548 add prefetch_related for l2vpn and vdcs to interface api --- netbox/dcim/api/views.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/netbox/dcim/api/views.py b/netbox/dcim/api/views.py index 0d9fcdc5f..a62315b57 100644 --- a/netbox/dcim/api/views.py +++ b/netbox/dcim/api/views.py @@ -493,7 +493,8 @@ class PowerOutletViewSet(PathEndpointMixin, NetBoxModelViewSet): class InterfaceViewSet(PathEndpointMixin, NetBoxModelViewSet): queryset = Interface.objects.prefetch_related( 'device', 'module__module_bay', 'parent', 'bridge', 'lag', '_path', 'cable__terminations', 'wireless_lans', - 'untagged_vlan', 'tagged_vlans', 'vrf', 'ip_addresses', 'fhrp_group_assignments', 'tags' + 'untagged_vlan', 'tagged_vlans', 'vrf', 'ip_addresses', 'fhrp_group_assignments', 'tags', 'l2vpn_terminations', + 'vdcs', ) serializer_class = serializers.InterfaceSerializer filterset_class = filtersets.InterfaceFilterSet From 23b21246f078989b6cc8a51eaac54efde001700a Mon Sep 17 00:00:00 2001 From: neope <58367950+apellini@users.noreply.github.com> Date: Thu, 18 May 2023 20:21:32 +0200 Subject: [PATCH 020/170] Adding CDFP and CFP8 400GE connectors (#12646) * Adding CDFP and CFP8 400GE connectors * Update choices.py typo on CFP8 --- netbox/dcim/choices.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/netbox/dcim/choices.py b/netbox/dcim/choices.py index d32f5aaee..4586f2111 100644 --- a/netbox/dcim/choices.py +++ b/netbox/dcim/choices.py @@ -814,6 +814,8 @@ class InterfaceTypeChoices(ChoiceSet): TYPE_200GE_QSFP56 = '200gbase-x-qsfp56' TYPE_400GE_QSFP_DD = '400gbase-x-qsfpdd' TYPE_400GE_OSFP = '400gbase-x-osfp' + TYPE_400GE_CDFP = '400gbase-x-cdfp' + TYPE_400GE_CFP8 = '400gbase-x-cfp8' TYPE_800GE_QSFP_DD = '800gbase-x-qsfpdd' TYPE_800GE_OSFP = '800gbase-x-osfp' @@ -959,6 +961,8 @@ class InterfaceTypeChoices(ChoiceSet): (TYPE_200GE_QSFP56, 'QSFP56 (200GE)'), (TYPE_400GE_QSFP_DD, 'QSFP-DD (400GE)'), (TYPE_400GE_OSFP, 'OSFP (400GE)'), + (TYPE_400GE_CDFP, 'CDFP (400GE)'), + (TYPE_400GE_CFP8, 'CPF8 (400GE)'), (TYPE_800GE_QSFP_DD, 'QSFP-DD (800GE)'), (TYPE_800GE_OSFP, 'OSFP (800GE)'), ) From 311dce0b5f2adaf124f5a8d4a3fb2ccdf0b46f9d Mon Sep 17 00:00:00 2001 From: Austin de Coup-Crank Date: Mon, 15 May 2023 10:20:35 -0500 Subject: [PATCH 021/170] Closes #12605: Add LX.5 port type --- netbox/dcim/choices.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/netbox/dcim/choices.py b/netbox/dcim/choices.py index 4586f2111..c256761c7 100644 --- a/netbox/dcim/choices.py +++ b/netbox/dcim/choices.py @@ -1227,6 +1227,10 @@ class PortTypeChoices(ChoiceSet): TYPE_LSH_PC = 'lsh-pc' TYPE_LSH_UPC = 'lsh-upc' TYPE_LSH_APC = 'lsh-apc' + TYPE_LX5 = 'lx5' + TYPE_LX5_PC = 'lx5-pc' + TYPE_LX5_UPC = 'lx5-upc' + TYPE_LX5_APC = 'lx5-apc' TYPE_SPLICE = 'splice' TYPE_CS = 'cs' TYPE_SN = 'sn' @@ -1273,6 +1277,10 @@ class PortTypeChoices(ChoiceSet): (TYPE_LSH_PC, 'LSH/PC'), (TYPE_LSH_UPC, 'LSH/UPC'), (TYPE_LSH_APC, 'LSH/APC'), + (TYPE_LX5, 'LX.5'), + (TYPE_LX5_PC, 'LX.5/PC'), + (TYPE_LX5_UPC, 'LX.5/UPC'), + (TYPE_LX5_APC, 'LX.5/APC'), (TYPE_MPO, 'MPO'), (TYPE_MTRJ, 'MTRJ'), (TYPE_SC, 'SC'), From c8d9a3b4eb58221b3806113718e50b29dcfe947c Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Thu, 18 May 2023 14:34:13 -0400 Subject: [PATCH 022/170] Changelog for #12327, #12548, #12594, #12605, #12629 --- docs/release-notes/version-3.5.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/release-notes/version-3.5.md b/docs/release-notes/version-3.5.md index b101a7912..4a77934b5 100644 --- a/docs/release-notes/version-3.5.md +++ b/docs/release-notes/version-3.5.md @@ -15,8 +15,12 @@ * [#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 +* [#12327](https://github.com/netbox-community/netbox/issues/12327) - Introduce the ability to automatically retry failed background jobs * [#12498](https://github.com/netbox-community/netbox/issues/12498) - Hide map button if `MAPS_URL` is empty +* [#12548](https://github.com/netbox-community/netbox/issues/12548) - Optimize REST API performance when retrieving interfaces with L2VPN assignments * [#12554](https://github.com/netbox-community/netbox/issues/12554) - Allow customization or disabling of the maintenance mode banner +* [#12605](https://github.com/netbox-community/netbox/issues/12605) - Add LX.5 port types +* [#12629](https://github.com/netbox-community/netbox/issues/12629) - Add 400GE CDFP and CFP8 interface types ### Bug Fixes @@ -24,6 +28,7 @@ * [#12468](https://github.com/netbox-community/netbox/issues/12468) - Custom field names should not permit double underscores * [#12550](https://github.com/netbox-community/netbox/issues/12550) - Fix rear port selection widget under front port creation form * [#12570](https://github.com/netbox-community/netbox/issues/12570) - Disable ordering of synchronized object tables by the "synced" attribute +* [#12594](https://github.com/netbox-community/netbox/issues/12594) - Enable selecting config context as object type in object counts dashboard widget --- From fa3bedb947f6cb166bf8f1a6b6d0e57a875006c1 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Mon, 22 May 2023 13:07:40 -0400 Subject: [PATCH 023/170] Fixes #12642: Fix bulk tenant assignment via cluster import form --- docs/release-notes/version-3.5.md | 1 + netbox/virtualization/forms/bulk_import.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/version-3.5.md b/docs/release-notes/version-3.5.md index 4a77934b5..79d9d12b7 100644 --- a/docs/release-notes/version-3.5.md +++ b/docs/release-notes/version-3.5.md @@ -29,6 +29,7 @@ * [#12550](https://github.com/netbox-community/netbox/issues/12550) - Fix rear port selection widget under front port creation form * [#12570](https://github.com/netbox-community/netbox/issues/12570) - Disable ordering of synchronized object tables by the "synced" attribute * [#12594](https://github.com/netbox-community/netbox/issues/12594) - Enable selecting config context as object type in object counts dashboard widget +* [#12642](https://github.com/netbox-community/netbox/issues/12642) - Fix bulk tenant assignment via cluster import form --- diff --git a/netbox/virtualization/forms/bulk_import.py b/netbox/virtualization/forms/bulk_import.py index a229bd935..15651f2ae 100644 --- a/netbox/virtualization/forms/bulk_import.py +++ b/netbox/virtualization/forms/bulk_import.py @@ -65,7 +65,7 @@ class ClusterImportForm(NetBoxModelImportForm): class Meta: model = Cluster - fields = ('name', 'type', 'group', 'status', 'site', 'description', 'comments', 'tags') + fields = ('name', 'type', 'group', 'status', 'site', 'tenant', 'description', 'comments', 'tags') class VirtualMachineImportForm(NetBoxModelImportForm): From 80fc8db514486448f406e1f695b18ac255f54997 Mon Sep 17 00:00:00 2001 From: "Daniel W. Anner" Date: Mon, 22 May 2023 14:22:33 +0000 Subject: [PATCH 024/170] Adding interface type `200gbase-x-qsfpdd` --- netbox/dcim/choices.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/netbox/dcim/choices.py b/netbox/dcim/choices.py index c256761c7..cc388b750 100644 --- a/netbox/dcim/choices.py +++ b/netbox/dcim/choices.py @@ -812,6 +812,7 @@ class InterfaceTypeChoices(ChoiceSet): TYPE_100GE_QSFP28 = '100gbase-x-qsfp28' TYPE_200GE_CFP2 = '200gbase-x-cfp2' TYPE_200GE_QSFP56 = '200gbase-x-qsfp56' + TYPE_200GE_QSFP_DD = '200gbase-x-qsfpdd' TYPE_400GE_QSFP_DD = '400gbase-x-qsfpdd' TYPE_400GE_OSFP = '400gbase-x-osfp' TYPE_400GE_CDFP = '400gbase-x-cdfp' @@ -959,6 +960,7 @@ class InterfaceTypeChoices(ChoiceSet): (TYPE_100GE_CPAK, 'Cisco CPAK (100GE)'), (TYPE_100GE_QSFP28, 'QSFP28 (100GE)'), (TYPE_200GE_QSFP56, 'QSFP56 (200GE)'), + (TYPE_200GE_QSFP_DD, 'QSFP-DD (200GE)'), (TYPE_400GE_QSFP_DD, 'QSFP-DD (400GE)'), (TYPE_400GE_OSFP, 'OSFP (400GE)'), (TYPE_400GE_CDFP, 'CDFP (400GE)'), From 078893e0341f433ec975eb595acedfaf1c537955 Mon Sep 17 00:00:00 2001 From: Dillon Henschen Date: Mon, 22 May 2023 15:37:31 -0400 Subject: [PATCH 025/170] Closes #11619: Include VLANs with a null site in query during bulk interface edit for Devices > DEVICE COMPONENTS > Interfaces (#12659) * Closes #11619: Allow VLANs without a site during multi-port edits This commit allows users to be able to select VLANs without a site assignment during bulk interfaces edits under Devices > DEVICE COMPONENTS > Interfaces. Prior to this commit, only VLANs that were assigned the same site as the device were available for selection. * Replace 'null' with FILTERS_NULL_CHOICE_VALUE constant --------- Co-authored-by: jeremystretch --- netbox/dcim/forms/bulk_edit.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/netbox/dcim/forms/bulk_edit.py b/netbox/dcim/forms/bulk_edit.py index 6ed483c79..bc9693afb 100644 --- a/netbox/dcim/forms/bulk_edit.py +++ b/netbox/dcim/forms/bulk_edit.py @@ -1,4 +1,5 @@ from django import forms +from django.conf import settings from django.contrib.auth.models import User from django.utils.translation import gettext as _ from timezone_field import TimeZoneFormField @@ -1292,8 +1293,13 @@ class InterfaceBulkEditForm( break if site is not None: - self.fields['untagged_vlan'].widget.add_query_param('site_id', site.pk) - self.fields['tagged_vlans'].widget.add_query_param('site_id', site.pk) + # Query for VLANs assigned to the same site and VLANs with no site assigned (null). + self.fields['untagged_vlan'].widget.add_query_param( + 'site_id', [site.pk, settings.FILTERS_NULL_CHOICE_VALUE] + ) + self.fields['tagged_vlans'].widget.add_query_param( + 'site_id', [site.pk, settings.FILTERS_NULL_CHOICE_VALUE] + ) self.fields['parent'].choices = () self.fields['parent'].widget.attrs['disabled'] = True From 005e3fd6926aa12ebd8e5c22ce6c1538bea19ad0 Mon Sep 17 00:00:00 2001 From: Austin de Coup-Crank <94914780+decoupca@users.noreply.github.com> Date: Mon, 22 May 2023 15:16:17 -0500 Subject: [PATCH 026/170] Closes #9068: validate addresses assigned to interfaces (#12618) * Begin logic * Closes #9068: Disallow assigning bcast/networks to interfaces * Allow net IDs in /31, /32, /127, /128 * linting error * Implement requested changes * Condensed the "if" logic a bit --------- Co-authored-by: Austin de Coup-Crank Co-authored-by: jeremystretch --- netbox/ipam/forms/model_forms.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/netbox/ipam/forms/model_forms.py b/netbox/ipam/forms/model_forms.py index cf8117bf7..ac75e2cc3 100644 --- a/netbox/ipam/forms/model_forms.py +++ b/netbox/ipam/forms/model_forms.py @@ -351,6 +351,18 @@ class IPAddressForm(TenancyForm, NetBoxModelForm): 'primary_for_parent', "Only IP addresses assigned to an interface can be designated as primary IPs." ) + # Do not allow assigning a network ID or broadcast address to an interface. + if interface and (address := self.cleaned_data.get('address')): + if address.ip == address.network: + msg = f"{address} is a network ID, which may not be assigned to an interface." + if address.version == 4 and address.prefixlen not in (31, 32): + raise ValidationError(msg) + if address.version == 6 and address.prefixlen not in (127, 128): + raise ValidationError(msg) + if address.ip == address.broadcast: + msg = f"{address} is a broadcast address, which may not be assigned to an interface." + raise ValidationError(msg) + def save(self, *args, **kwargs): ipaddress = super().save(*args, **kwargs) From fbc7811f56040aee5d7dd0f6be568665172d5ba2 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Mon, 22 May 2023 16:24:30 -0400 Subject: [PATCH 027/170] Release v3.5.2 --- .github/ISSUE_TEMPLATE/bug_report.yaml | 2 +- .github/ISSUE_TEMPLATE/feature_request.yaml | 2 +- docs/release-notes/version-3.5.md | 5 ++++- netbox/netbox/settings.py | 2 +- requirements.txt | 14 +++++++------- 5 files changed, 14 insertions(+), 11 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index be2aacff5..1a0e68929 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -14,7 +14,7 @@ body: attributes: label: NetBox version description: What version of NetBox are you currently running? - placeholder: v3.5.1 + placeholder: v3.5.2 validations: required: true - type: dropdown diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml index fcb3516b4..526057454 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yaml +++ b/.github/ISSUE_TEMPLATE/feature_request.yaml @@ -14,7 +14,7 @@ body: attributes: label: NetBox version description: What version of NetBox are you currently running? - placeholder: v3.5.1 + placeholder: v3.5.2 validations: required: true - type: dropdown diff --git a/docs/release-notes/version-3.5.md b/docs/release-notes/version-3.5.md index 79d9d12b7..24d1ce2dc 100644 --- a/docs/release-notes/version-3.5.md +++ b/docs/release-notes/version-3.5.md @@ -1,10 +1,11 @@ # NetBox v3.5 -## v3.5.2 (FUTURE) +## v3.5.2 (2023-05-22) ### Enhancements * [#7671](https://github.com/netbox-community/netbox/issues/7671) - Introduce `REMOTE_AUTH_AUTO_CREATE_GROUPS` config parameter to enable the automatic creation of new groups when remote authentication is in use +* [#9068](https://github.com/netbox-community/netbox/issues/9068) - Disallow the assignment of network/broadcast IP addresses to interfaces * [#11017](https://github.com/netbox-community/netbox/issues/11017) - Increase the maximum values for allocated and maximum power draws * [#11233](https://github.com/netbox-community/netbox/issues/11233) - Intercept and cleanly report errors upon attempted database writes when maintenance mode is enabled * [#11599](https://github.com/netbox-community/netbox/issues/11599) - Move contacts panels to separate tabs under object views @@ -21,10 +22,12 @@ * [#12554](https://github.com/netbox-community/netbox/issues/12554) - Allow customization or disabling of the maintenance mode banner * [#12605](https://github.com/netbox-community/netbox/issues/12605) - Add LX.5 port types * [#12629](https://github.com/netbox-community/netbox/issues/12629) - Add 400GE CDFP and CFP8 interface types +* [#12678](https://github.com/netbox-community/netbox/issues/12678) - Add 200GE QSFP-DD interface type ### Bug Fixes * [#10686](https://github.com/netbox-community/netbox/issues/10686) - Enable specifying termination object by virtual chassis master when importing cables +* [#11619](https://github.com/netbox-community/netbox/issues/11619) - Enable assigning VLANs without a site to interfaces during bulk edit * [#12468](https://github.com/netbox-community/netbox/issues/12468) - Custom field names should not permit double underscores * [#12550](https://github.com/netbox-community/netbox/issues/12550) - Fix rear port selection widget under front port creation form * [#12570](https://github.com/netbox-community/netbox/issues/12570) - Disable ordering of synchronized object tables by the "synced" attribute diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index d6d5c84ca..a5923c9f0 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -25,7 +25,7 @@ from netbox.constants import RQ_QUEUE_DEFAULT, RQ_QUEUE_HIGH, RQ_QUEUE_LOW # Environment setup # -VERSION = '3.5.2-dev' +VERSION = '3.5.2' # Hostname HOSTNAME = platform.node() diff --git a/requirements.txt b/requirements.txt index c3d9c8c38..6757f3260 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,8 @@ bleach==6.0.0 -boto3==1.26.127 +boto3==1.26.138 Django==4.1.9 -django-cors-headers==3.14.0 -django-debug-toolbar==4.0.0 +django-cors-headers==4.0.0 +django-debug-toolbar==4.1.0 django-filter==23.2 django-graphiql-debug-toolbar==0.2.0 django-mptt==0.14 @@ -10,7 +10,7 @@ django-pglocks==1.0.4 django-prometheus==2.3.1 django-redis==5.2.0 django-rich==1.5.0 -django-rq==2.8.0 +django-rq==2.8.1 django-tables2==2.5.3 django-taggit==4.0.0 django-timezone-field==5.0 @@ -19,17 +19,17 @@ drf-spectacular==0.26.2 drf-spectacular-sidecar==2023.5.1 dulwich==0.21.5 feedparser==6.0.10 -graphene-django==3.0.0 +graphene-django==3.0.2 gunicorn==20.1.0 Jinja2==3.1.2 Markdown==3.3.7 -mkdocs-material==9.1.9 +mkdocs-material==9.1.14 mkdocstrings[python-legacy]==0.21.2 netaddr==0.8.0 Pillow==9.5.0 psycopg2-binary==2.9.6 PyYAML==6.0 -sentry-sdk==1.22.1 +sentry-sdk==1.23.1 social-auth-app-django==5.2.0 social-auth-core[openidconnect]==4.4.2 svgwrite==1.4.3 From 6160e034263e44334d55055a1a0a4286b25008cf Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Mon, 22 May 2023 17:00:29 -0400 Subject: [PATCH 028/170] PRVB --- docs/release-notes/version-3.5.md | 4 ++++ netbox/netbox/settings.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/version-3.5.md b/docs/release-notes/version-3.5.md index 24d1ce2dc..e36a1f3b4 100644 --- a/docs/release-notes/version-3.5.md +++ b/docs/release-notes/version-3.5.md @@ -1,5 +1,9 @@ # NetBox v3.5 +## v3.5.3 (FUTURE) + +--- + ## v3.5.2 (2023-05-22) ### Enhancements diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index a5923c9f0..354e6e3a9 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -25,7 +25,7 @@ from netbox.constants import RQ_QUEUE_DEFAULT, RQ_QUEUE_HIGH, RQ_QUEUE_LOW # Environment setup # -VERSION = '3.5.2' +VERSION = '3.5.3-dev' # Hostname HOSTNAME = platform.node() From b31b086a4d6518d143f2f1837391379ea9b94fc9 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Tue, 23 May 2023 15:34:03 -0400 Subject: [PATCH 029/170] Link to the plugin ideas board --- .github/ISSUE_TEMPLATE/config.yml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 1f8fdebd4..e6a5e76c2 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -3,10 +3,13 @@ blank_issues_enabled: false contact_links: - name: 📖 Contributing Policy url: https://github.com/netbox-community/netbox/blob/develop/CONTRIBUTING.md - about: "Please read through our contributing policy before opening an issue or pull request" + about: "Please read through our contributing policy before opening an issue or pull request." - name: ❓ Discussion url: https://github.com/netbox-community/netbox/discussions - about: "If you're just looking for help, try starting a discussion instead" + about: "If you're just looking for help, try starting a discussion instead." + - name: 💡 Plugin Idea + url: https://plugin-ideas.netbox.dev + about: "Have an idea for a plugin? Head over to the ideas board!" - name: 💬 Community Slack - url: https://netdev.chat/ - about: "Join #netbox on the NetDev Community Slack for assistance with installation issues and other problems" + url: https://netdev.chat + about: "Join #netbox on the NetDev Community Slack for assistance with installation issues and other problems." From bf1c191b2e9a52ecb122d3e4c456b9be3bcfa032 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Wed, 24 May 2023 15:45:24 -0400 Subject: [PATCH 030/170] Fixes #12694: Strip leading & trailing whitespace from custom link URL & text --- docs/release-notes/version-3.5.md | 4 ++++ netbox/extras/models/models.py | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/release-notes/version-3.5.md b/docs/release-notes/version-3.5.md index e36a1f3b4..1154d4fcd 100644 --- a/docs/release-notes/version-3.5.md +++ b/docs/release-notes/version-3.5.md @@ -2,6 +2,10 @@ ## v3.5.3 (FUTURE) +### Bug Fixes + +* [#12694](https://github.com/netbox-community/netbox/issues/12694) - Strip leading & trailing whitespace from custom link URL & text + --- ## v3.5.2 (2023-05-22) diff --git a/netbox/extras/models/models.py b/netbox/extras/models/models.py index 16e4fb577..9433ab6b0 100644 --- a/netbox/extras/models/models.py +++ b/netbox/extras/models/models.py @@ -274,10 +274,10 @@ class CustomLink(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel): :param context: The context passed to Jinja2 """ - text = render_jinja2(self.link_text, context) + text = render_jinja2(self.link_text, context).strip() if not text: return {} - link = render_jinja2(self.link_url, context) + link = render_jinja2(self.link_url, context).strip() link_target = ' target="_blank"' if self.new_window else '' # Sanitize link text From 24a51dd86e213cedf561bfb84853a4bb484ec211 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 25 May 2023 15:20:08 -0400 Subject: [PATCH 031/170] Fixes #11539: Use BooleanFilter for 'empty' lookups (#11784) * Use BooleanFilter for 'empty' lookups * Always use BooleanFilter for 'empty' lookups * Restore Empty lookup logic --- netbox/extras/lookups.py | 12 +++++++----- netbox/netbox/filtersets.py | 11 ++++++++++- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/netbox/extras/lookups.py b/netbox/extras/lookups.py index 77fe2301e..a8d89c943 100644 --- a/netbox/extras/lookups.py +++ b/netbox/extras/lookups.py @@ -7,12 +7,14 @@ class Empty(Lookup): Filter on whether a string is empty. """ lookup_name = 'empty' + prepare_rhs = False - def as_sql(self, qn, connection): - lhs, lhs_params = self.process_lhs(qn, connection) - rhs, rhs_params = self.process_rhs(qn, connection) - params = lhs_params + rhs_params - return 'CAST(LENGTH(%s) AS BOOLEAN) != %s' % (lhs, rhs), params + def as_sql(self, compiler, connection): + sql, params = compiler.compile(self.lhs) + if self.rhs: + return f"CAST(LENGTH({sql}) AS BOOLEAN) IS NOT TRUE", params + else: + return f"CAST(LENGTH({sql}) AS BOOLEAN) IS TRUE", params class NetContainsOrEquals(Lookup): diff --git a/netbox/netbox/filtersets.py b/netbox/netbox/filtersets.py index a0c1edee8..9a2385c45 100644 --- a/netbox/netbox/filtersets.py +++ b/netbox/netbox/filtersets.py @@ -177,7 +177,8 @@ class BaseFilterSet(django_filters.FilterSet): # create the new filter with the same type because there is no guarantee the defined type # is the same as the default type for the field resolve_field(field, lookup_expr) # Will raise FieldLookupError if the lookup is invalid - new_filter = type(existing_filter)( + filter_cls = django_filters.BooleanFilter if lookup_expr == 'empty' else type(existing_filter) + new_filter = filter_cls( field_name=field_name, lookup_expr=lookup_expr, label=existing_filter.label, @@ -224,6 +225,14 @@ class BaseFilterSet(django_filters.FilterSet): return filters + @classmethod + def filter_for_lookup(cls, field, lookup_type): + + if lookup_type == 'empty': + return django_filters.BooleanFilter, {} + + return super().filter_for_lookup(field, lookup_type) + class ChangeLoggedModelFilterSet(BaseFilterSet): """ From b64b19a3f4011be1fc3889d2668b26e3135cadf2 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Thu, 25 May 2023 16:42:24 -0400 Subject: [PATCH 032/170] Fixes #11934: Prevent reassignment of an IP address designated as primary for its parent object --- docs/release-notes/version-3.5.md | 1 + netbox/ipam/forms/model_forms.py | 13 ++++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/version-3.5.md b/docs/release-notes/version-3.5.md index 1154d4fcd..816fe124c 100644 --- a/docs/release-notes/version-3.5.md +++ b/docs/release-notes/version-3.5.md @@ -4,6 +4,7 @@ ### Bug Fixes +* [#11934](https://github.com/netbox-community/netbox/issues/11934) - Prevent reassignment of an IP address designated as primary for its parent object * [#12694](https://github.com/netbox-community/netbox/issues/12694) - Strip leading & trailing whitespace from custom link URL & text --- diff --git a/netbox/ipam/forms/model_forms.py b/netbox/ipam/forms/model_forms.py index ac75e2cc3..eb6dbe598 100644 --- a/netbox/ipam/forms/model_forms.py +++ b/netbox/ipam/forms/model_forms.py @@ -328,6 +328,12 @@ class IPAddressForm(TenancyForm, NetBoxModelForm): ): self.initial['primary_for_parent'] = True + # Disable object assignment fields if the IP address is designated as primary + if self.initial.get('primary_for_parent'): + self.fields['interface'].disabled = True + self.fields['vminterface'].disabled = True + self.fields['fhrpgroup'].disabled = True + def clean(self): super().clean() @@ -340,7 +346,12 @@ class IPAddressForm(TenancyForm, NetBoxModelForm): selected_objects[1]: "An IP address can only be assigned to a single object." }) elif selected_objects: - self.instance.assigned_object = self.cleaned_data[selected_objects[0]] + assigned_object = self.cleaned_data[selected_objects[0]] + if self.cleaned_data['primary_for_parent'] and assigned_object != self.instance.assigned_object: + raise ValidationError( + "Cannot reassign IP address while it is designated as the primary IP for the parent object" + ) + self.instance.assigned_object = assigned_object else: self.instance.assigned_object = None From e2f9a3c07a3c3c586ed43e6dbcedded002bb271c Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Fri, 26 May 2023 13:21:42 +0530 Subject: [PATCH 033/170] fixes contact assignments filter to include parent content type #12730 --- netbox/tenancy/views.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/netbox/tenancy/views.py b/netbox/tenancy/views.py index 5f8a7e314..c2ef4b487 100644 --- a/netbox/tenancy/views.py +++ b/netbox/tenancy/views.py @@ -29,7 +29,10 @@ class ObjectContactsView(generic.ObjectChildrenView): def get_children(self, request, parent): return Contact.objects.annotate( assignment_count=count_related(ContactAssignment, 'contact') - ).restrict(request.user, 'view').filter(assignments__object_id=parent.pk) + ).restrict(request.user, 'view').filter( + assignments__content_type=ContentType.objects.get_for_model(parent), + assignments__object_id=parent.pk + ) def get_extra_context(self, request, instance): return { From 5869894a48ba8b3384d44f564d47adad5df141c3 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Fri, 26 May 2023 18:16:49 +0530 Subject: [PATCH 034/170] Adds ip to failed logs (#12725) * adds ip to failed logs #12562 * added additional logging when client ip cannot be determined --- netbox/users/signals.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/netbox/users/signals.py b/netbox/users/signals.py index 8915af1dc..98036d5d1 100644 --- a/netbox/users/signals.py +++ b/netbox/users/signals.py @@ -1,10 +1,18 @@ import logging from django.dispatch import receiver from django.contrib.auth.signals import user_login_failed +from utilities.request import get_client_ip @receiver(user_login_failed) def log_user_login_failed(sender, credentials, request, **kwargs): logger = logging.getLogger('netbox.auth.login') username = credentials.get("username") - logger.info(f"Failed login attempt for username: {username}") + if client_ip := get_client_ip(request): + logger.info(f"Failed login attempt for username: {username} from {client_ip}") + else: + logger.warning( + "Client IP address could not be determined for validation. Check that the HTTP server is properly " + "configured to pass the required header(s)." + ) + logger.info(f"Failed login attempt for username: {username}") From 5a5fcf7d377d61e114a738c59edb9e015f465efe Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Fri, 26 May 2023 19:02:58 +0530 Subject: [PATCH 035/170] Changes render config card with accordian (#12724) * changes render config card with accordian #12470 * fixed indentation #12470 * Use -flush CSS class to reduce whitespace --------- Co-authored-by: jeremystretch --- .../templates/dcim/device/render_config.html | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/netbox/templates/dcim/device/render_config.html b/netbox/templates/dcim/device/render_config.html index b6e16701f..dfda7cdf6 100644 --- a/netbox/templates/dcim/device/render_config.html +++ b/netbox/templates/dcim/device/render_config.html @@ -28,8 +28,22 @@
-
Context Data
-
{{ context_data|pprint }}
+
+
+
+

+ +

+
+
+
{{ context_data|pprint }}
+
+
+
+
+
From 1f71d3570a7e4c66563223a8044d8e6b62c4323a Mon Sep 17 00:00:00 2001 From: kkthxbye-code Date: Tue, 30 May 2023 08:12:51 +0200 Subject: [PATCH 036/170] Escape text passed as display values to slim-select --- netbox/project-static/dist/netbox.js | Bin 438912 -> 530549 bytes netbox/project-static/dist/netbox.js.map | Bin 402467 -> 450821 bytes netbox/project-static/package.json | 1 + .../src/select/api/apiSelect.ts | 5 +++-- netbox/project-static/yarn.lock | 5 +++++ 5 files changed, 9 insertions(+), 2 deletions(-) diff --git a/netbox/project-static/dist/netbox.js b/netbox/project-static/dist/netbox.js index 8caaaa9a06b98ae6307412f09c4e3e29af0efc04..bb83314f5fbec1fb8100db2cbf9db944ef6e25a8 100644 GIT binary patch literal 530549 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 z z!LC*-@vwx{;KS@|W_%z;SNqWC*-Fq!TX#Z3hIs*UenM~;^Vdh@Jc_PLYo} z)d5WayCm)|R7F`dT34Xm$pSUp4wtXsSW7!%vXSpNZjyH)8_xDzF)S6~f5&0B05au75eUR0)PQ z7?3eP^Wniw1m}i^Js`wi^+m^V(9>J;DRp;GG1u|%G9AGGV0Q@~KT9cK(T{kc$O~xE zzxbbY0s7~DA0yO)aZRas#aV5$ zx%XZ*E))GFm(!hqf?tbFdp(0dFogW#*QOS~pfqWAoO=44`3QT!s0s$1 z*~vKTy-V+88ra(wo`YdPcSg4(0zt1szkz5cGC{u#(oFjj4i~aDIxb@jPwBWpS5t~E z5Wpz9KpcI7z6(a9U4@ASib2>D5WnD!DrCnQl@1C(AZa35BIY+Tgkgl<;r#hi(dNV= znRO2>3U4ajL;HFnH^#cOMh;0NSwOA0B|UP~zcUK?x)+r8%^_bv^Nl zK*SW0&)mMf>w(WXWpw5@R{R!=#CzJRUb1_?UxukA~AgWg52OA(7a zK`g5gHcGxT^+aJLHN@Q8joC7mNrVZe!T&3Beb%ludjTFxjD)f~2$K5N4WB z^yEgBE~IV|0eliMrUIjl(LEbBlE{=po0%8D9GVun+G=Q6k;{o$ytmbmCBb9w;&%eI z%%rsiO~~8T{d5ds)=ujZ{af{bN4?}KohTQDT-LV^oTQ_a?D_6h)7QyN9d3a zys@>qyx?IMpcEYzOkiF@r$pM#>XKhbrY-0?zZ}Kx=<}SHVT7A5)HmdxSS5tm%Mu(d zo`LHdQrn`Oe$-=#NDikn`$8@m!p=(k3C&<7Fv@NOtHs3Vk+ z0Qm|Sq1rWmSZ6vIG!*3rz>b~!2kkiX0ITQ4FSN0H|gSarjZ>s z;nPPB(kBFZrV@u-3KQOtk2c0|y+Ft!GXh`>iBOj)t5qe&C}XNHI^enF2GFow_=3`o zY#)&C$z0dZ+*Y4rVN+h=tL2%Qnf*|=9o>Oec-R5ikBkO-`wV;!IwiYUlWjpU9dvpN zin#$rGs0E`+eh?HVJAiMk0AGw54siDfv=j+C9wZEh9bm$qY47ay0uYT-`LwH7TvhH!sQJ(sD$mcIvcM`&jq545Z z#ekCGF|GOGCai!m2SZ29`{FlNLJb56hzVWUy>T(V>1#c>I1}8&76v!x*w~j^$gIZq ztl)0}(e6QPI4ga=(#O>X0K*@WH-U=<{voATxJc+FIlKV@lGF7CNToBx$+}0TU{k74 zd&GrcE7Mb4=wFY<3n{WYQaEzch1P<_hlOFbfKs_?XK=QVo7*n|EIx<`Ff85DAaLma zTS1cw`iY;YZc{_C~laO8DtP3^BK0L%T!luuxo?i{{F@jG@J?27`!#kvT!em z04xT~Kf_9z*$c2iw--oS9J3CzW#RMCF32DdznN>YN<
74aRfO~d*+TuT2pJF6Y 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

+vA|xtplm<)(;jhAfCa*`HYTCRE_?F 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 z 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 literal 438912 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 z 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 diff --git a/netbox/project-static/dist/netbox.js.map b/netbox/project-static/dist/netbox.js.map index 0201e7bf85d9b6fba1d1c66fbdf7087f428c36e4..9b2017e3f91382cc1a9f2a3ab813d65c83e641cf 100644 GIT binary patch delta 48484 zcmb`Q&yQr+RmWwBi6VX_fRIgu5E3kavSP=MtthMNa%a-%Ogf#;WHQ-JCXGFbJsywk zIEe^v5g~SLEaVMhgM^eFJ1>zyLi`0RSZ4wM01H-}`?>eMCt$~D9zP3hG6`90^J z`{TWORs9eD{?uRn%cuY8=THCPt8c&l+B4S=-oF3#{p+_r^Vr1j40*N^Vsd*+=7_wT*&-Rt-M@^?S|2fM!BdgsxjH(t5>_VqIl z@7{m!Q_ns9sU4m7E`RU$fBN&MKlu66PyE}x>4lT^`tIsty}npouh-YB8|(Gu>Tcm} z*6Ypc=6Zc|bzY>4Do%n zUSCa>Nqk&N74Zt5)MY^zaL!k^*X!HKp=MH=N!?nnZ&id9!c>ARXlgiugqyP!b{&NW zc*p8#0cdRF<)T!5I$P;xq^MLdvvIZ^>5X`LA=6f zscuHRk*yY%f3clbibuvH-eR0=^1+&v-Yxvr<~t`Mk1b&{OPUtbdt#h&O^dvK@gL z=;ihAaK;1U;m{o!xY>5=-^dtVcMhPe@RV{?>CO-%9*IO5S$C&f1;|-FPIMHdh9{-5 zJSi&JSpX2;h?;cx(NID-iYn`$_@x9h;wj}oNOtO(r+P}|6@XJMKjVp;5oHjiqagCM zkT;@kvHlTQTqCSXb*-MR)l(|ZA(<12^=~FKWe}wX(T%Q^YAbI7th3zy^`hjg$^Yl>P)W(i^USnn;a|1*6_5FuZu#xoW(M{%Z$d z{71(5G(O@H2|c1i$NdW_|qw4;v!;gj%!iQ1i`a`UH*8sBXCaQH~@9u2v6!ivP+Ww1iytL67 zQ3g>u3L;Mnc|FDYCqk(K*3>DwR!`T5z1UlkNbJAyny(Yh&LHv(q8r00)mGk!s{1bx zxakYkIgAu@_uEqFAw5iupi;N8|KMmSU+=4LT31KAN$7|Og=A@zyN7P7WZsZj7z#4-pzfN;gtE98vlcOsBVh{nJFMGmf|Iz6z9j zqrmhQ_XE7$zlr|al=W}tE+}btHyH35f}M?m=p!!{3MwF+r$waTZTqi)r8JgD5Zrt} zK2rJqNxyarQO&L_+X>|Jry1N^ukR(-A;@<~1m=9@vm^BI0%gwd%s%O{{)(_O1}i*K zV=ROpMICL46FS`m2ov7UD#{+&Kk+@MGupbRKwmD=Z9K(i8=`(nU+vB7*c=w}-} zy%BHTXm7+NeuK!%Hdai{H#XsuzRnl0Xfg<sOCav zIyYSo_ig}vw*k!~p7k^6*H5DV<}9L=f3 z?&ysu^#rMF;o{lA0L&@AS^7U_JSy80!+DXH(VTyFL~z_2Qs5n2){e$2`J2OzOMvAG zLm;@i&sN<`K-2JA!wam@|2PV=of@HLaR#TcfYsBB6)>>9U?bzDzyF(a$$4j5w?Lxo zf|IgBS`aiPOaFJ`@Q``uXDzH=P}Ai~{38l5qmPkvC}m&^E>AtFNi@0t zZKFIOc}3qhEAkASvf4!{#^9GdH{3t~`7i9o>i^}|LnFHmNvW~XV|5;$!a;Sti z!OhOIcCL&^nOqC~T}J=44shBC!g0z0@_v}z9-~7y&}Y@MU4P~p*f92nK!mAOD(9ZE zT#}U6fJ|{kVnChCGxUfubZ*XEq1+F7%c?tW%RiT~wB8ri@o#lnu9t{F#q7y8^sXZT zH!qd;*V)RH^BzBsarB=((grYO=gDkV-R{(uF1js6=$uIl2tSW-qYHmpHLN>It%mS3 za;uQD%wby-V40z1{qoq~3n#1V=>PLoR>~dJ%8a8tba_bgEX*~@`YGR4P9^+I<(Nx= ze8^|a;12R)toK37{9K0&GY@#=KVL=XtnyUO5SSJ;BRdNX$h3fR`nd0m^zed3z5fI0 zOmd#t`y)495b9!g2sM{HCsW|I`KR*UK!7yY4kS|Z5{A<+ch)i+%K6Ljum)x>ui&jm zMVt~c4;10!C4Z2v@2u(i6!Sj|EU6hpA70n6c>-B60YY)QMC$D$I!1Wfru%a$g*TL;whXBOywJa##U z3?R?&>@P#lI@Px2p9Q&3b1;E97&+5yr-n7m@@1Hae^uLSVWwvi9J`U!b~OwX4VS@Z zdYOFDLsQN`cNr_WbR8_1THMuY z8yOgYOiR~DD<*{=DG?@Cfp&<2r$4CL|Kfq7*e;y9rUm0AA5S_dL2|*nw(WeE_*eB3 zNMyKl0fIsg=hn&4+M?H0$@J7~iI(%{D4ebd$5Lu0xzX~})oN5dQ;j0DtD8xu*->t? z+zkfMPG{LF4?&)A))Bip{<;-oS1Jd=;)e3^A2p2zp+5hDn~c4cm!ju+oGX!On^yQz zCSsZk)jofqD>aeQb)?;Pf2AE2Z*x76!Xq4Y15BzQ_a7{c;X2m>?1Gq`l>UT}`$2BK z{_I>LCg2FSVXO!Nqa*V|gBLS}+U3Rhr$gJBbV2sQ3PGDv%h=Y5O>!e1o+l4UQka-0 zsNuQv$m`&vXE?oRo2eEs#9D z7*49`m1__c4I-rpLE>$DlrU7z%I?`(UoY~6Z<_LoUOR*4T_UfpD(im4%bQ{@dG?i8 z^Mx+?=QES#JC>pqb2?>6T}f8+b@5y8euIFZ!=N z?cw~C(nQLf@}iXw&{UD&*3%<%UY=elp8;-N1V)^EQ^;WED|+3->o&vALMc>BXnXuc z>3VJEIusUC$}4X)AWDT_s5bt(F`SAco>E~+DU}j}dg%;mF;yH7fxca>j4b38z4i>4Eu(7{ zyC5NG7tVol=P4g}+U8-<_Y!YPCzyeFczvD^6+*LO@;b_iM+yZ*9)FNKl2T^lw9MTx z=H{N+S)Pa$@TPYB5Gi?p&y1whvsy~!jVRO5Vlqoj6vON0g%G5)fWo4)4Z+U!UVR4F zeiZ@@LKQk`2L?S{3q}i*Q%|Z`?2NwGX9G&@%?gKR&j2t`@Hz@F@PeApe=tF4xST=0z{vC8pw>l7U-GbE z7>YEHKPNtb#7kb+bfXjsH@a4;MORVs#-dVD|9k<`ix;Uu4pRhv3@>pH<_L#fJHL4>bVUi}VUNb{#(qQGl5Q}2x%pt7)X-A zA3XI&T~)7t_bQsTZh+8*>tF$4#IsI~D(7FB7F0?pIpJEK8IQv0MX6ONyte+UIHGK( z5d?UQ=ST`vss#k7WD)ak=MISB%v1dG4(CpuhJ(^}uEKl|9{h?E!hN67g-RYe0ZPq5 z@)MOxiBZ>oNE||0%n>Z~bR%f%VPHLL;Si_$e~LBHwbIVDWwq1*)YjqD%R=Sf09csm znn<33<#kY5KMgE}h3CI?&g#y82eGD!N?_aws{=z7gsNxDA1d?t&#`mM$O~w`n9BUT zux2{zFE^W`Ve=yZMSq$fKs663GZM4XuDs;CC(En)n3>gBcEf!AYvzkMb%L&SBL%Eb zIj>LD17-xZ2hI4)G-C%usZ}trQh8D)A{b+Yzxg{bBeAZJrE+KFPZflbd!%Sj^AmVk zU^uW+R&&}r5Z!!G&y1m*i0M11A;~u$gqrSkJt@5iCO0PX2+9ZM@f`s1fDt1o z9pOB;&Aj!Zhg4hq2}jCAt#$hRLq#bR!5aQ|9)VC#skJeC5{dG)M+Bhod>x;^o!8&~ z1a)4u4;Y0_zBeEYDTB*vA!iswtoefo6Dh=&Oo#H!6|f8eK^zSH0l}>C++c0r|1}7% zI}`gsJqs_-jO7U$-U6$ge>;;!T$@v4AjI1|lENZ)vNOnsOw!nY@(j>;FJ;D~z$2bj zKuU5mFXq3fH4dHj+-+`2HIL124wLVE%8RORUH_3YK`1eq(n+A_Xfj}`Wc12v+w zRXRyZnWsk*I>gdJX<_B;B)n)ra#&>E_Vu4Zr1DHR1H&8+Z8c3LZ}n;Y8#$LJW!6$w z485FTEY)T$h)}tX;ai?T?8@{1b{JE$v!!pS7nIVi^H1*cT%i2PMcC)NeWMMDG#nf$ z!cvzlUY!47B97X%r#Lz{8h3h~Lkm3`1_L{oss9z5CyILu2pFJ3p{Iq7X*gpv+tUBK zQLKp+4kN`68{tu4c}6Rq^ZoCTO+K4 z2VRF}XHM2b2qP&gdMKamIh^K=YymW-Zy<@>K;}P!F_JE>lV^E)skYtqf9H@wnX6KG zmXWM+4NpCJIs23-^PiTGiF0_ELs=1c@sd9Q7)jCV_6POmifo<#6iaov&`wC94udhJ zkv}}w`EL*@jpdEp$a6xL~CC0=eVR4zV73=^%tk+Vb+}Uz*ILlW#EV znn_Ce2@EAbDVtOXQdN)sAK`a4?>h};em*_r+l~43lyw$*d;ia$a*I?-pk^sh^^T?L zcQB&v<41}eVZ)7vMkyBf6RKW>wOEYre>>KiND0A?p(ny@yecT|U|li(3z~F?Tg8!d z1W-Kz3R-_#w)1a*t0<*Id1j15dLM^U&T;B70waX_9ki|k3?~FlbeQjIQDQ&{Y~)9e z==JNLVKhFLr-fCLQkr3>&wtHp6mZY@-Kv)_T~-W%UX;Si)h_ejbz2kBV$42v1TP%q zyU3%pK`cj98m^QDlY|YPgc$6Kgxw7l*Xf?E0sE|hcu$+eU{|v`X@rGpzGW?Be~Yv zFl1d)>M2dX!K(2nh&>=>&e}?$N6waA+ordD|IfhrB$VlV=(0QVioP7Jww56S8>7H# z{cmZ+s-l_bMhi2Rm(P9qJg9x6mi%oyC>3>TAf(;sIv6915wBSA`SXW;4O(LPZFO-9Jae^P1B; zqy`!B45FTtG32G|e;rDx2oL<`B&D9zkruiCJBLgZcu*r)HWJyP2T#hPhl9MzRDjX{ z`F6U-h;btlo0uS?%1#DRb`*N>!~vyV z{TpN|LItk&fhCIYM2R#6`L7vt(s+BPLLH4?E>!s&C^&3Ox@h6Y2ZPhI5N290$V;1qP4Vpj1i_3~s!gZ~fEcG5_21 z2TMuP@-A+vF0i0GN(;fro2mKw17pW0A9*!bo*!e(=Sy`nH$MuO_vYYF%dak*7p>J5 zis~?G4u+IcDWG%~WR2(9HuCQZkBNC?yzQ65)aXFQ+nwJ5nG4-+C1ab8m2LkS;bcez z(uPD$^FT8f+&m3$W#Hkb-hZWc<9%LG5o&M2TteG z1dyT!#BQS!c3BI`^Os$&X>{txb;=D6FW&C_@dSC+%#d^2wS&M#^nYH@IupW?7nJ!+ z5w6q20)PazP95m7!0-GGB(dUJH&6t|p%i*ukS>ufaPrLc7h+9xEj0=PuWMetK!wUu z>Y(JcX`Oz9d;|h>ji?R0W2rnT^A*a%FXL~3-1k}CaBjQ#dQ^F~%HHxamUW{4{T0wG z&}G(e2cY=WA_~_&k@v*Fz#&D?j_EAYZ-80_LwA%-hMRcXmRl(TJCt2Gr8B@-|J!`m zXoS|#=#euN`DweCnggD#+OY6#&-ynwx`1TUIUkkQia9J+3@_%-W#z7PYyT$~6`|na zMyWg;b{4{For64|0aBl}-#{~fl;Ne28g%|8-*Tv0rc}%*HLQZLlj3E#z30EdgQ8Oe zV;puo^a$5k07eK7R!0G*>c#rswotK}r#!1*?v_|-9xS**>{6*oMf~v}AQJ>=$-pp5 z^Jzh7Qrf_?Iu#h_fA%mGJB(zg2_RSr%nC|4hRY3V9eaxW9pBM1V=hpJ29BXSJ*0O{6hL0y;|!L4<&MZTsXFcYH@nHkncrygH`LCR67m?cwlz^(YO%A=6wQ48 z@5TnB&}s9wlL`ZhFm?~NfKU$}UQ$Of{$^ug<=GRlV6|)h5f6*xy38#7NogC1=I`Gr z&J&6Zz-<}Tl)`0~DRAULg{!Obg`@WMf#AjdXZPa}MX6C?nU=a{ZMQbUlCqjWASHO^ z`QLm45V5=tj1G0v<=$CXg)U8E)aNfpCa3H5X&ODVGjN_Kat#P&Dxq>#9#&`_gtUJD ztTRSYp6wF8@_EkzfI=$Iz+HNJE)d%Mhl!$%)(~!WvBn+c5*~uHt;2|e1_iLCtJ?e* zE#@eii@a^){%KOB*v896e|HzyNGrws@0rWY6#~?Py3QFvOC{IEi=ItMD*VOw&yFym z2f84-fr?0NxrrWY^BW$ZM~T3zpFekn(=|QN8QT{lN-*RO1Y1~Mvn|(uGV`UDMjs6& z*vPicqr0Qe9}%0gWhWQP31y}NBo*hv$P3b?!ufCDS_iR?cuIvZ(o-rxC}7n@=zkT> z9fv4(uBnGr5R8=mMwEI%?LL2}iHaSBc$=S0Rqyes+W%pfzfngO2twel14aN32R;D5 z^#5Vw!_ot;kMi8s|HseiFf64b&xsfP*S>$E$%y(z^Gr86w}vy$KZM)++fz=Rgt~xc zZpd>+$Q#)zg(|;6pU*#C?gr_oPA|&SzB<%RSz1{hxdXNFme2n@)0v5sm-|n-{FQS|cpS2rEium;r5;o>G8;;=2Q`xQbj>6*Fy{s<+(2y?=W z3cr`9bE=7+#ny8>VJ$Uo`GuR9z<8+gp$KLDl32_p|T7F;Qm z5YfX6l|V{h4_@%9l=F{@j&B;Hx>VpYEZewRTq&eDhazBSfz;a6=dW-rZp37&Yos^U z>l^8`plhVK)(cX+Kq2*x;cOB6pUtLXLipG}f%sUq{r3+r2CLMJwO!wIIWe|W(7;-_ zt#wi?()GW&Yb=DWLn3D0HP9Okm_0*E&Xk49_x}xS8+NeFy>qRtRA&nWQ>s+g|1izY zvSMr%+GhpIp$u)K38Je~g%k02er-=TJ=*0O*E*P$JYgx7C)A*}?tg(*F%F%AfYT7ic`OeTTubc@j2?Bff+UB9 zunN`g|Lk>b5SJ+-bU`{VW(>6*xZQQ2|8XcnD3%o{jCBpK0|U=$xg^Pp=&hdozq64L*eYlOuNy_BQsyog zq|`_-McU4PDnfNt*fuFMXZ*>=2y{M2SvfSz(6;%HOG69C&?Dz$1=ucUGL;8~o}O#H z=zp08F>qHpTqDI{ytM0FywI+MlQKD}SsM3$XRA zQiO+8Fj5_?oLwY`9z2p$TB{ZFuRS=98^pSXqQF*$j5{|@sz?w5 zL*YisAO_Yodid)oyjU%ywf_yQn@+LIm<~4xuIHTcCn|bTN(}aq`un?$+&zc`iz*09 zYUDZ)?L20Kmu}lyCTRZs)y^R~Ss5vefkg?a!e~gGfJ(8zbB)%m{|l~kr$-(pU0~{= zL!|K3>!d;mmFdp@9{@#>X$Ljx09>1$V=T|AM!21WeLDY)6!TrYyo#%9ASlOKsskyE zn!kB9Ua)a8yBc!4r6zIq0Ju1s0~74L1e z4q(16(8k(Ck~_Eax>Q;n%HQ9!Bv59h>wITVE)+I8w|4r0LJw)5za~%=Plp;ilIR&& z*iZtrvc?cr;f>F~A<4I;EvyblK?F@WcT8YmOYTXp+yBU2tlUPy4aQQPo)!Z17K}U- znW@}*YDajt@HMidapb1xNw{9_w<>G!`;;KRJb@JCSPrSD(Dw2{Cnj}dbeJIe9I zBJj3LQT;(VUzVoMzrQwWDY5FB0t3*>sHap)sWGZ;xBux7MKZw+_@x&7Y4K7@xGwh* zFX}cw{~|&t0(V6Z|M|0WGtpL99n~dbnrZ#-9E#t-x{-&T*--_g29XCWq*&D7A8lkb z&0dNiU7O*ia|l56geoPhCAqf#5m;PMq0=6p+UwA8IK%?mc-i@4Ej;vy233ClY3D0M zj=O>79d>OHDZ@$4{$l-8QP;CLAEhJNJHyAx!YqG?dvCLT_|pB;Re3o7ZoRl;jfzii zF6*&!Q@%-jbG^Q~I*R!G{Oj_kC2P>BNj1rH~56mL7;NKf9#;*9yo4lR%gj8b7n}9xZyi_FrKs#^wB@`_T$O6apB!yta1rH?!M>;iLvB%bF2_R&OU9vR zqDhxHtVR3%=l=dTHfd@FodUR>z%`+w!D?)|8n{|1>bG+QB z4Fbe?dpZAJL?C@0znAkjdoDNY2d|!#%5ByK80F=<X{&9SF2bR@0CR8#{y>a_REWtH%|)V(Me&v3;jdr_(iGUKZJhsZ`Z%_JLS)Olskd( zZ9{xTcIU+?*zIU}x!yeYrNUxtHi4JOx7VBRmKl0$b^puj%{NIhjGhhm4^GO>h1;uV zZ>~?jRV?2r&Nn|Ov7=bm>&;h7i7w)0^M$XKQND@_-uO-#hX@h|l)dr$sRc~0kkQwmGK->5$0?X6@JnC=wkXXT~o0VCdCCcWU5@^60l)v8TH z#`~8m=o>95FkP>vu)Y^lW%Cjs?iQP5~pMJfLb%dpXc^0^2xcQ#rRl9nMaS8~fFE?MCsnf3& z99OFsqDJ2+}i-%c5OAC%U~pW&LJLXfFYwQS&&z z1=b`r#7z77|s(eleuUANSJt!DqJ z$QZboD?pEz;-RdNv_O!ojS(@AVXUYjVTSjbZIA-xt>U8oonmg4lt7!;mNwcy_#h~2 zcCFe&#)T?4)@|XwS4QMkWZM?*+x7j`yl~%LmFtv#C9nIDOZkHmAuZ+0P8vxvM>emu z`UIvZUELRU*YjG%GRrn^ZwivYTlOE<^q8LU{yN6~^vA7F!)3)&(%#e|htbxuIsvE& z^OrI{T#Pq2N}$iuk9A^(aWwq5OT&GU`PT_!(TPI>3tM2J8|zHET}+;DvtVw_*&DDU zlGEq91ZMp|FWz3-I^CciJ|0f^`_TE*mZk=16C=WW#tkzopgm%k9&`GX-$@HE{rov+ z{zUC#hyrFDi9)GP`!W?-sbZXBseD9Ub%@-wVzHQa@jB}4TCrHvQ4eQr(El_xu4smU ze_-P}-#ohcy_3KG*-xH`|GyOffBA!-{p4ex{M2uS@95>2&*3o6iC3F1eelnJ^Ra*Z zizlA{;HSU(qHL delta 112 zcmZoYB)#~A#D*u5&HJUc@0VgcmfT)y#RSC6K+Lke)QXi~kgeQFC)n9-`!sLX!!?Yi z+oMjf3h;8eJ39L7ggQDWO|QJl>I~rqI=bd;KXsM$B@d@zg;SNYj=!VP_C1eS{n#1J Lw{yQ@{q+q10M;mP diff --git a/netbox/project-static/package.json b/netbox/project-static/package.json index f10b5b7ac..98e1a5c60 100644 --- a/netbox/project-static/package.json +++ b/netbox/project-static/package.json @@ -30,6 +30,7 @@ "dayjs": "^1.11.5", "flatpickr": "4.6.13", "gridstack": "^7.2.3", + "html-entities": "^2.3.3", "htmx.org": "^1.8.0", "just-debounce-it": "^3.1.1", "query-string": "^7.1.1", diff --git a/netbox/project-static/src/select/api/apiSelect.ts b/netbox/project-static/src/select/api/apiSelect.ts index f5b605d58..53996910e 100644 --- a/netbox/project-static/src/select/api/apiSelect.ts +++ b/netbox/project-static/src/select/api/apiSelect.ts @@ -1,5 +1,6 @@ import { readableColor } from 'color2k'; import debounce from 'just-debounce-it'; +import { encode } from 'html-entities'; import queryString from 'query-string'; import SlimSelect from 'slim-select'; import { createToast } from '../../bs'; @@ -446,7 +447,7 @@ export class APISelect { // Build SlimSelect options from all already-selected options. const preSelectedOptions = preSelected.map(option => ({ value: option.value, - text: option.innerText, + text: encode(option.innerText), selected: true, disabled: false, })) as Option[]; @@ -454,7 +455,7 @@ export class APISelect { let options = [] as Option[]; for (const result of data.results) { - let text = result.display; + let text = encode(result.display); if (typeof result._depth === 'number' && result._depth > 0) { // If the object has a `_depth` property, indent its display text. diff --git a/netbox/project-static/yarn.lock b/netbox/project-static/yarn.lock index c4bee7557..2adc50001 100644 --- a/netbox/project-static/yarn.lock +++ b/netbox/project-static/yarn.lock @@ -1818,6 +1818,11 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" +html-entities@^2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.3.3.tgz#117d7626bece327fc8baace8868fa6f5ef856e46" + integrity sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA== + htmx.org@^1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/htmx.org/-/htmx.org-1.8.0.tgz#f3a2f681f3e2b6357b5a29bba24a2572a8e48fd3" From 9b9a559e0cc49c0a7f8b5171fbf2e082775e46b2 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Tue, 30 May 2023 19:11:32 +0530 Subject: [PATCH 037/170] Adds image preview back on the table (#12739) * adds image preview on image attachment #12627 * adds bootstrap initialization for hx-trigger=load #12627 --------- Co-authored-by: jeremystretch --- netbox/extras/tables/tables.py | 11 +++++++++++ netbox/project-static/dist/netbox.js | Bin 530549 -> 530632 bytes netbox/project-static/dist/netbox.js.map | Bin 450821 -> 450874 bytes netbox/project-static/src/htmx.ts | 7 ++++++- 4 files changed, 17 insertions(+), 1 deletion(-) diff --git a/netbox/extras/tables/tables.py b/netbox/extras/tables/tables.py index 8d046b85d..9e4924532 100644 --- a/netbox/extras/tables/tables.py +++ b/netbox/extras/tables/tables.py @@ -22,6 +22,14 @@ __all__ = ( 'WebhookTable', ) +IMAGEATTACHMENT_IMAGE = ''' +{% if record.image %} + {{ record }} +{% else %} + — +{% endif %} +''' + class CustomFieldTable(NetBoxTable): name = tables.Column( @@ -96,6 +104,9 @@ class ImageAttachmentTable(NetBoxTable): parent = tables.Column( linkify=True ) + image = tables.TemplateColumn( + template_code=IMAGEATTACHMENT_IMAGE, + ) size = tables.Column( orderable=False, verbose_name='Size (bytes)' diff --git a/netbox/project-static/dist/netbox.js b/netbox/project-static/dist/netbox.js index bb83314f5fbec1fb8100db2cbf9db944ef6e25a8..9642d15851c5238db66f5a46ac81880ce2f10e1f 100644 GIT binary patch delta 30650 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%0A2lVM5+?54CIk>0n9ACql(gqFsvvq1t)! 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 delta 30574 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 BA)^2Q delta 50 zcmdmWNV@fqbVCbc3sVbo3rh>@7Ph8)jE2*j@3CpJdOA8AJ5S$!kFB1?+u5UFd**$% Gi;Mu@GZLx* diff --git a/netbox/project-static/src/htmx.ts b/netbox/project-static/src/htmx.ts index 09d423cbd..2410a5fd9 100644 --- a/netbox/project-static/src/htmx.ts +++ b/netbox/project-static/src/htmx.ts @@ -2,9 +2,10 @@ import { getElements, isTruthy } from './util'; import { initButtons } from './buttons'; import { initSelect } from './select'; import { initObjectSelector } from './objectSelector'; +import { initBootstrap } from './bs'; function initDepedencies(): void { - for (const init of [initButtons, initSelect, initObjectSelector]) { + for (const init of [initButtons, initSelect, initObjectSelector, initBootstrap]) { init(); } } @@ -22,4 +23,8 @@ export function initHtmx(): void { } } } + + for (const element of getElements('[hx-trigger=load]')) { + element.addEventListener('htmx:afterSettle', initDepedencies); + } } From d7ca453f2608e7d8caa432cd42d4daf6a5ca3167 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Tue, 30 May 2023 19:12:37 +0530 Subject: [PATCH 038/170] Adds hide-if-unset to custom field (#12723) * adds hide-if-unset to custom field #12597 * moved hide logic from template to python * fix indentation * Update logic for omit_hidden under get_custom_fields() * Update docs * Account for False values --------- Co-authored-by: jeremystretch --- docs/models/extras/customfield.md | 11 ++++++----- netbox/extras/choices.py | 2 ++ netbox/netbox/models/features.py | 14 ++++++++++---- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/docs/models/extras/customfield.md b/docs/models/extras/customfield.md index 0d92ec656..df0408f7c 100644 --- a/docs/models/extras/customfield.md +++ b/docs/models/extras/customfield.md @@ -68,11 +68,12 @@ Defines how filters are evaluated against custom field values. Controls how and whether the custom field is displayed within the NetBox user interface. -| Option | Description | -|------------|--------------------------------------| -| Read/write | Display and permit editing (default) | -| Read-only | Display field but disallow editing | -| Hidden | Do not display field in the UI | +| Option | Description | +|-------------------|--------------------------------------------------| +| Read/write | Display and permit editing (default) | +| Read-only | Display field but disallow editing | +| Hidden | Do not display field in the UI | +| Hidden (if unset) | Display in the UI only when a value has been set | ### Default diff --git a/netbox/extras/choices.py b/netbox/extras/choices.py index e10516c4c..6fc14b965 100644 --- a/netbox/extras/choices.py +++ b/netbox/extras/choices.py @@ -56,11 +56,13 @@ class CustomFieldVisibilityChoices(ChoiceSet): VISIBILITY_READ_WRITE = 'read-write' VISIBILITY_READ_ONLY = 'read-only' VISIBILITY_HIDDEN = 'hidden' + VISIBILITY_HIDDEN_IFUNSET = 'hidden-ifunset' CHOICES = ( (VISIBILITY_READ_WRITE, 'Read/Write'), (VISIBILITY_READ_ONLY, 'Read-only'), (VISIBILITY_HIDDEN, 'Hidden'), + (VISIBILITY_HIDDEN_IFUNSET, 'Hidden (if unset)'), ) diff --git a/netbox/netbox/models/features.py b/netbox/netbox/models/features.py index 6d82e2a2b..8bacba534 100644 --- a/netbox/netbox/models/features.py +++ b/netbox/netbox/models/features.py @@ -197,11 +197,15 @@ class CustomFieldsMixin(models.Model): data = {} for field in CustomField.objects.get_for_model(self): - # Skip fields that are hidden if 'omit_hidden' is set - if omit_hidden and field.ui_visibility == CustomFieldVisibilityChoices.VISIBILITY_HIDDEN: - continue - value = self.custom_field_data.get(field.name) + + # Skip fields that are hidden if 'omit_hidden' is set + if omit_hidden: + if field.ui_visibility == CustomFieldVisibilityChoices.VISIBILITY_HIDDEN: + continue + if field.ui_visibility == CustomFieldVisibilityChoices.VISIBILITY_HIDDEN_IFUNSET and not value: + continue + data[field] = field.deserialize(value) return data @@ -227,6 +231,8 @@ class CustomFieldsMixin(models.Model): for cf in visible_custom_fields: value = self.custom_field_data.get(cf.name) + if value in (None, []) and cf.ui_visibility == CustomFieldVisibilityChoices.VISIBILITY_HIDDEN_IFUNSET: + continue value = cf.deserialize(value) groups[cf.group_name][cf] = value From 18c863e393c32731e0cbf13ff56e854a79249c4f Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Tue, 30 May 2023 09:52:14 -0400 Subject: [PATCH 039/170] Changelog for #11539, #12370, #12470, #12562, #12597, #12627, #12745 --- docs/release-notes/version-3.5.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/release-notes/version-3.5.md b/docs/release-notes/version-3.5.md index 816fe124c..ad5828e8e 100644 --- a/docs/release-notes/version-3.5.md +++ b/docs/release-notes/version-3.5.md @@ -2,10 +2,20 @@ ## v3.5.3 (FUTURE) +### Enhancements + +* [#12470](https://github.com/netbox-community/netbox/issues/12470) - Collapse context data by default when viewing a rendered device configuration +* [#12562](https://github.com/netbox-community/netbox/issues/12562) - Record client IP address when logging authentication failures +* [#12597](https://github.com/netbox-community/netbox/issues/12597) - Add an option to hide custom fields only if unset + ### Bug Fixes +* [#11539](https://github.com/netbox-community/netbox/issues/11539) - Fix exception when applying "empty" filter lookup with invalid value * [#11934](https://github.com/netbox-community/netbox/issues/11934) - Prevent reassignment of an IP address designated as primary for its parent object +* [#12370](https://github.com/netbox-community/netbox/issues/12370) - Fix extraneous contacts listed in object contact assignments view +* [#12627](https://github.com/netbox-community/netbox/issues/12627) - Restore hover preview for embedded image attachment tables * [#12694](https://github.com/netbox-community/netbox/issues/12694) - Strip leading & trailing whitespace from custom link URL & text +* [#12745](https://github.com/netbox-community/netbox/issues/12745) - Escape display text in API-backed selection widgets --- From b3bd03a1e9441742d056fe28f1114230c75dad24 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Tue, 30 May 2023 14:51:16 -0400 Subject: [PATCH 040/170] Fixes #12715: Use contact assignments table to display the contacts assigned to an object --- docs/release-notes/version-3.5.md | 3 ++- netbox/tenancy/views.py | 25 ++++++++++++++++--------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/docs/release-notes/version-3.5.md b/docs/release-notes/version-3.5.md index ad5828e8e..32f4ded32 100644 --- a/docs/release-notes/version-3.5.md +++ b/docs/release-notes/version-3.5.md @@ -12,9 +12,10 @@ * [#11539](https://github.com/netbox-community/netbox/issues/11539) - Fix exception when applying "empty" filter lookup with invalid value * [#11934](https://github.com/netbox-community/netbox/issues/11934) - Prevent reassignment of an IP address designated as primary for its parent object -* [#12370](https://github.com/netbox-community/netbox/issues/12370) - Fix extraneous contacts listed in object contact assignments view +* [#12730](https://github.com/netbox-community/netbox/issues/12730) - Fix extraneous contacts listed in object contact assignments view * [#12627](https://github.com/netbox-community/netbox/issues/12627) - Restore hover preview for embedded image attachment tables * [#12694](https://github.com/netbox-community/netbox/issues/12694) - Strip leading & trailing whitespace from custom link URL & text +* [#12715](https://github.com/netbox-community/netbox/issues/12715) - Use contact assignments table to display the contacts assigned to an object * [#12745](https://github.com/netbox-community/netbox/issues/12745) - Escape display text in API-backed selection widgets --- diff --git a/netbox/tenancy/views.py b/netbox/tenancy/views.py index c2ef4b487..bbe901bde 100644 --- a/netbox/tenancy/views.py +++ b/netbox/tenancy/views.py @@ -15,25 +15,32 @@ from .models import * class ObjectContactsView(generic.ObjectChildrenView): - child_model = Contact - table = tables.ContactTable - filterset = filtersets.ContactFilterSet + child_model = ContactAssignment + table = tables.ContactAssignmentTable + filterset = filtersets.ContactAssignmentFilterSet template_name = 'tenancy/object_contacts.html' tab = ViewTab( label=_('Contacts'), badge=lambda obj: obj.contacts.count(), - permission='tenancy.view_contact', + permission='tenancy.view_contactassignment', weight=5000 ) def get_children(self, request, parent): - return Contact.objects.annotate( - assignment_count=count_related(ContactAssignment, 'contact') - ).restrict(request.user, 'view').filter( - assignments__content_type=ContentType.objects.get_for_model(parent), - assignments__object_id=parent.pk + return ContactAssignment.objects.restrict(request.user, 'view').filter( + content_type=ContentType.objects.get_for_model(parent), + object_id=parent.pk ) + def get_table(self, *args, **kwargs): + table = super().get_table(*args, **kwargs) + + # Hide object columns + table.columns.hide('content_type') + table.columns.hide('object') + + return table + def get_extra_context(self, request, instance): return { 'base_template': f'{instance._meta.app_label}/{instance._meta.model_name}.html', From 9b8ab1c1f7b364e3694bead05173e746a1f4571f Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Tue, 30 May 2023 15:44:43 -0400 Subject: [PATCH 041/170] Fixes #12742: Object counts dashboard widget should support URL-compatible query filters --- docs/release-notes/version-3.5.md | 1 + netbox/extras/dashboard/widgets.py | 19 +++++++++---------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/release-notes/version-3.5.md b/docs/release-notes/version-3.5.md index 32f4ded32..6656aa1bb 100644 --- a/docs/release-notes/version-3.5.md +++ b/docs/release-notes/version-3.5.md @@ -16,6 +16,7 @@ * [#12627](https://github.com/netbox-community/netbox/issues/12627) - Restore hover preview for embedded image attachment tables * [#12694](https://github.com/netbox-community/netbox/issues/12694) - Strip leading & trailing whitespace from custom link URL & text * [#12715](https://github.com/netbox-community/netbox/issues/12715) - Use contact assignments table to display the contacts assigned to an object +* [#12742](https://github.com/netbox-community/netbox/issues/12742) - Object counts dashboard widget should support URL-compatible query filters * [#12745](https://github.com/netbox-community/netbox/issues/12745) - Escape display text in API-backed selection widgets --- diff --git a/netbox/extras/dashboard/widgets.py b/netbox/extras/dashboard/widgets.py index dc68e1388..ed50a8451 100644 --- a/netbox/extras/dashboard/widgets.py +++ b/netbox/extras/dashboard/widgets.py @@ -10,8 +10,9 @@ from django.conf import settings from django.contrib.contenttypes.models import ContentType from django.core.cache import cache from django.db.models import Q +from django.http import QueryDict from django.template.loader import render_to_string -from django.urls import NoReverseMatch, reverse +from django.urls import NoReverseMatch, resolve, reverse from django.utils.translation import gettext as _ from extras.utils import FeatureQuery @@ -149,7 +150,7 @@ class ObjectCountsWidget(DashboardWidget): filters = forms.JSONField( required=False, label='Object filters', - help_text=_("Only objects matching the specified filters will be counted") + help_text=_("Filters to apply when counting the number of objects") ) def clean_filters(self): @@ -158,13 +159,6 @@ class ObjectCountsWidget(DashboardWidget): dict(data) except TypeError: raise forms.ValidationError("Invalid format. Object filters must be passed as a dictionary.") - for model in get_models_from_content_types(self.cleaned_data.get('models')): - try: - # Validate the filters by creating a QuerySet - model.objects.filter(**data).none() - except Exception: - model_name = model._meta.verbose_name_plural - raise forms.ValidationError(f"Invalid filter specification for {model_name}.") return data def render(self, request): @@ -172,9 +166,14 @@ class ObjectCountsWidget(DashboardWidget): for model in get_models_from_content_types(self.config['models']): permission = get_permission_for_model(model, 'view') if request.user.has_perm(permission): + url = reverse(get_viewname(model, 'list')) qs = model.objects.restrict(request.user, 'view') + # Apply any specified filters if filters := self.config.get('filters'): - qs = qs.filter(**filters) + params = QueryDict(mutable=True) + params.update(filters) + filterset = getattr(resolve(url).func.view_class, 'filterset', None) + qs = filterset(params, qs).qs object_count = qs.count counts.append((model, object_count)) else: From bca9d0fa8ae243459191355edaf625748b1fde42 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Tue, 30 May 2023 16:31:34 -0400 Subject: [PATCH 042/170] Closes #12599: Apply filter parameters to links in object count dashboard widgets --- docs/release-notes/version-3.5.md | 1 + netbox/extras/dashboard/widgets.py | 5 +++-- netbox/templates/extras/dashboard/widgets/objectcounts.html | 6 ++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/release-notes/version-3.5.md b/docs/release-notes/version-3.5.md index 6656aa1bb..ac02f0f79 100644 --- a/docs/release-notes/version-3.5.md +++ b/docs/release-notes/version-3.5.md @@ -7,6 +7,7 @@ * [#12470](https://github.com/netbox-community/netbox/issues/12470) - Collapse context data by default when viewing a rendered device configuration * [#12562](https://github.com/netbox-community/netbox/issues/12562) - Record client IP address when logging authentication failures * [#12597](https://github.com/netbox-community/netbox/issues/12597) - Add an option to hide custom fields only if unset +* [#12599](https://github.com/netbox-community/netbox/issues/12599) - Apply filter parameters to links in object count dashboard widgets ### Bug Fixes diff --git a/netbox/extras/dashboard/widgets.py b/netbox/extras/dashboard/widgets.py index ed50a8451..b3a4d090c 100644 --- a/netbox/extras/dashboard/widgets.py +++ b/netbox/extras/dashboard/widgets.py @@ -174,10 +174,11 @@ class ObjectCountsWidget(DashboardWidget): params.update(filters) filterset = getattr(resolve(url).func.view_class, 'filterset', None) qs = filterset(params, qs).qs + url = f'{url}?{params.urlencode()}' object_count = qs.count - counts.append((model, object_count)) + counts.append((model, object_count, url)) else: - counts.append((model, None)) + counts.append((model, None, None)) return render_to_string(self.template_name, { 'counts': counts, diff --git a/netbox/templates/extras/dashboard/widgets/objectcounts.html b/netbox/templates/extras/dashboard/widgets/objectcounts.html index d0e604c9a..8b68dc166 100644 --- a/netbox/templates/extras/dashboard/widgets/objectcounts.html +++ b/netbox/templates/extras/dashboard/widgets/objectcounts.html @@ -1,10 +1,8 @@ -{% load helpers %} - {% if counts %}

- {% for model, count in counts %} + {% for model, count, url in counts %} {% if count != None %} - +
{{ model|meta:"verbose_name_plural"|bettertitle }}
{{ count }}
From 8b051ea2f3cf17da3fd3440b12a0bbb7d0119487 Mon Sep 17 00:00:00 2001 From: Arthur Hanson Date: Wed, 31 May 2023 06:06:09 -0700 Subject: [PATCH 043/170] 7503 do device validate-create in serial (#12222) * 7503 do device validate-create in serial * 7503 fix single instance * 7503 atomic transaction * 7503 fix return data for bulk operations * 7503 add test * Move sequential creation logic to a mixin --------- Co-authored-by: jeremystretch --- netbox/dcim/api/views.py | 13 +++++++---- netbox/dcim/tests/test_api.py | 35 +++++++++++++++++++++++++++- netbox/netbox/api/viewsets/mixins.py | 27 ++++++++++++++++++++- 3 files changed, 69 insertions(+), 6 deletions(-) diff --git a/netbox/dcim/api/views.py b/netbox/dcim/api/views.py index a62315b57..5b87c4e5d 100644 --- a/netbox/dcim/api/views.py +++ b/netbox/dcim/api/views.py @@ -1,12 +1,12 @@ from django.http import Http404, HttpResponse from django.shortcuts import get_object_or_404 -from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiParameter from drf_spectacular.types import OpenApiTypes +from drf_spectacular.utils import extend_schema, OpenApiParameter from rest_framework.decorators import action from rest_framework.renderers import JSONRenderer from rest_framework.response import Response -from rest_framework.status import HTTP_400_BAD_REQUEST from rest_framework.routers import APIRootView +from rest_framework.status import HTTP_400_BAD_REQUEST from rest_framework.viewsets import ViewSet from circuits.models import Circuit @@ -14,7 +14,6 @@ from dcim import filtersets from dcim.constants import CABLE_TRACE_SVG_DEFAULT_WIDTH from dcim.models import * from dcim.svg import CableTraceSVG -from extras.api.nested_serializers import NestedConfigTemplateSerializer from extras.api.mixins import ConfigContextQuerySetMixin, ConfigTemplateRenderMixin from ipam.models import Prefix, VLAN from netbox.api.authentication import IsAuthenticatedOrLoginNotRequired @@ -22,6 +21,7 @@ from netbox.api.metadata import ContentTypeMetadata from netbox.api.pagination import StripCountAnnotationsPaginator from netbox.api.renderers import TextRenderer from netbox.api.viewsets import NetBoxModelViewSet +from netbox.api.viewsets.mixins import SequentialBulkCreatesMixin from netbox.constants import NESTED_SERIALIZER_PREFIX from utilities.api import get_serializer_for_model from utilities.utils import count_related @@ -386,7 +386,12 @@ class PlatformViewSet(NetBoxModelViewSet): # Devices/modules # -class DeviceViewSet(ConfigContextQuerySetMixin, ConfigTemplateRenderMixin, NetBoxModelViewSet): +class DeviceViewSet( + SequentialBulkCreatesMixin, + ConfigContextQuerySetMixin, + ConfigTemplateRenderMixin, + NetBoxModelViewSet +): queryset = Device.objects.prefetch_related( 'device_type__manufacturer', 'device_role', 'tenant', 'platform', 'site', 'location', 'rack', 'parent_bay', 'virtual_chassis__master', 'primary_ip4__nat_outside', 'primary_ip6__nat_outside', 'config_template', 'tags', diff --git a/netbox/dcim/tests/test_api.py b/netbox/dcim/tests/test_api.py index 3445b7e75..af15e1343 100644 --- a/netbox/dcim/tests/test_api.py +++ b/netbox/dcim/tests/test_api.py @@ -1115,7 +1115,7 @@ class DeviceTest(APIViewTestCases.APIViewTestCase): device_types = ( DeviceType(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'), - DeviceType(manufacturer=manufacturer, model='Device Type 2', slug='device-type-2'), + DeviceType(manufacturer=manufacturer, model='Device Type 2', slug='device-type-2', u_height=2), ) DeviceType.objects.bulk_create(device_types) @@ -1229,6 +1229,39 @@ class DeviceTest(APIViewTestCases.APIViewTestCase): self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST) + def test_rack_fit(self): + """ + Check that creating multiple devices with overlapping position fails. + """ + device = Device.objects.first() + device_type = DeviceType.objects.all()[1] + data = [ + { + 'device_type': device_type.pk, + 'device_role': device.device_role.pk, + 'site': device.site.pk, + 'name': 'Test Device 7', + 'rack': device.rack.pk, + 'face': 'front', + 'position': 1 + }, + { + 'device_type': device_type.pk, + 'device_role': device.device_role.pk, + 'site': device.site.pk, + 'name': 'Test Device 8', + 'rack': device.rack.pk, + 'face': 'front', + 'position': 2 + } + ] + + self.add_permissions('dcim.add_device') + url = reverse('dcim-api:device-list') + response = self.client.post(url, data, format='json', **self.header) + + self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST) + class ModuleTest(APIViewTestCases.APIViewTestCase): model = Module diff --git a/netbox/netbox/api/viewsets/mixins.py b/netbox/netbox/api/viewsets/mixins.py index 8b629bbc6..fde486fe9 100644 --- a/netbox/netbox/api/viewsets/mixins.py +++ b/netbox/netbox/api/viewsets/mixins.py @@ -15,11 +15,12 @@ from utilities.api import get_serializer_for_model __all__ = ( 'BriefModeMixin', + 'BulkDestroyModelMixin', 'BulkUpdateModelMixin', 'CustomFieldsMixin', 'ExportTemplatesMixin', - 'BulkDestroyModelMixin', 'ObjectValidationMixin', + 'SequentialBulkCreatesMixin', ) @@ -94,6 +95,30 @@ class ExportTemplatesMixin: return super().list(request, *args, **kwargs) +class SequentialBulkCreatesMixin: + """ + Perform bulk creation of new objects sequentially, rather than all at once. This ensures that any validation + which depends on the evaluation of existing objects (such as checking for free space within a rack) functions + appropriately. + """ + @transaction.atomic + def create(self, request, *args, **kwargs): + if not isinstance(request.data, list): + # Creating a single object + return super().create(request, *args, **kwargs) + + return_data = [] + for data in request.data: + serializer = self.get_serializer(data=data) + serializer.is_valid(raise_exception=True) + self.perform_create(serializer) + return_data.append(serializer.data) + + headers = self.get_success_headers(serializer.data) + + return Response(return_data, status=status.HTTP_201_CREATED, headers=headers) + + class BulkUpdateModelMixin: """ Support bulk modification of objects using the list endpoint for a model. Accepts a PATCH action with a list of one From a9b0b49ef94d6c52862c3e0f63169e39f9824d42 Mon Sep 17 00:00:00 2001 From: Daniel Sheppard Date: Wed, 31 May 2023 08:49:23 -0500 Subject: [PATCH 044/170] Fixes #12702 - Adds widget to FrontPortTemplateCreateForm --- netbox/dcim/forms/object_create.py | 1 + 1 file changed, 1 insertion(+) diff --git a/netbox/dcim/forms/object_create.py b/netbox/dcim/forms/object_create.py index d4c9e6ec3..9589ab533 100644 --- a/netbox/dcim/forms/object_create.py +++ b/netbox/dcim/forms/object_create.py @@ -101,6 +101,7 @@ class FrontPortTemplateCreateForm(ComponentCreateForm, model_forms.FrontPortTemp choices=[], label=_('Rear ports'), help_text=_('Select one rear port assignment for each front port being created.'), + widget=forms.SelectMultiple(attrs={'size': 6}) ) # Override fieldsets from FrontPortTemplateForm to omit rear_port_position From bd88ee7063c65d3634b2d6e6c548e1c4b2b14259 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Wed, 31 May 2023 19:29:22 +0530 Subject: [PATCH 045/170] Adds device type and role to device component filter (#12504) * adds device type and role to device component filter #12015 * changes as per review * Add filterset tests for device type & role filters --------- Co-authored-by: jeremystretch --- netbox/dcim/filtersets.py | 22 ++ netbox/dcim/forms/filtersets.py | 45 +++-- netbox/dcim/tests/test_filtersets.py | 289 +++++++++++++++++++++------ 3 files changed, 278 insertions(+), 78 deletions(-) diff --git a/netbox/dcim/filtersets.py b/netbox/dcim/filtersets.py index fccaa72f0..e784be8e8 100644 --- a/netbox/dcim/filtersets.py +++ b/netbox/dcim/filtersets.py @@ -1219,6 +1219,28 @@ class DeviceComponentFilterSet(django_filters.FilterSet): to_field_name='name', label=_('Device (name)'), ) + device_type_id = django_filters.ModelMultipleChoiceFilter( + field_name='device__device_type', + queryset=DeviceType.objects.all(), + label=_('Device type (ID)'), + ) + device_type = django_filters.ModelMultipleChoiceFilter( + field_name='device__device_type__model', + queryset=DeviceType.objects.all(), + to_field_name='model', + label=_('Device type (model)'), + ) + device_role_id = django_filters.ModelMultipleChoiceFilter( + field_name='device__device_role', + queryset=DeviceRole.objects.all(), + label=_('Device role (ID)'), + ) + device_role = django_filters.ModelMultipleChoiceFilter( + field_name='device__device_role__slug', + queryset=DeviceRole.objects.all(), + to_field_name='slug', + label=_('Device role (slug)'), + ) virtual_chassis_id = django_filters.ModelMultipleChoiceFilter( field_name='device__virtual_chassis', queryset=VirtualChassis.objects.all(), diff --git a/netbox/dcim/forms/filtersets.py b/netbox/dcim/forms/filtersets.py index d31bba030..4edee6014 100644 --- a/netbox/dcim/forms/filtersets.py +++ b/netbox/dcim/forms/filtersets.py @@ -102,13 +102,25 @@ class DeviceComponentFilterForm(NetBoxModelFilterSetForm): required=False, label=_('Virtual Chassis') ) + device_type_id = DynamicModelMultipleChoiceField( + queryset=DeviceType.objects.all(), + required=False, + label=_('Device type') + ) + device_role_id = DynamicModelMultipleChoiceField( + queryset=DeviceRole.objects.all(), + required=False, + label=_('Device role') + ) device_id = DynamicModelMultipleChoiceField( queryset=Device.objects.all(), required=False, query_params={ 'site_id': '$site_id', 'location_id': '$location_id', - 'virtual_chassis_id': '$virtual_chassis_id' + 'virtual_chassis_id': '$virtual_chassis_id', + 'device_type_id': '$device_type_id', + 'role_id': '$device_role_id' }, label=_('Device') ) @@ -1070,7 +1082,8 @@ class ConsolePortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm): fieldsets = ( (None, ('q', 'filter_id', 'tag')), ('Attributes', ('name', 'label', 'type', 'speed')), - ('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')), + ('Location', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')), + ('Device', ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')), ('Connection', ('cabled', 'connected', 'occupied')), ) type = forms.MultipleChoiceField( @@ -1089,7 +1102,8 @@ class ConsoleServerPortFilterForm(PathEndpointFilterForm, DeviceComponentFilterF fieldsets = ( (None, ('q', 'filter_id', 'tag')), ('Attributes', ('name', 'label', 'type', 'speed')), - ('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')), + ('Location', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')), + ('Device', ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')), ('Connection', ('cabled', 'connected', 'occupied')), ) type = forms.MultipleChoiceField( @@ -1108,7 +1122,8 @@ class PowerPortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm): fieldsets = ( (None, ('q', 'filter_id', 'tag')), ('Attributes', ('name', 'label', 'type')), - ('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')), + ('Location', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')), + ('Device', ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')), ('Connection', ('cabled', 'connected', 'occupied')), ) type = forms.MultipleChoiceField( @@ -1123,7 +1138,8 @@ class PowerOutletFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm): fieldsets = ( (None, ('q', 'filter_id', 'tag')), ('Attributes', ('name', 'label', 'type')), - ('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')), + ('Location', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')), + ('Device', ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')), ('Connection', ('cabled', 'connected', 'occupied')), ) type = forms.MultipleChoiceField( @@ -1141,8 +1157,8 @@ class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm): ('Addressing', ('vrf_id', 'l2vpn_id', 'mac_address', 'wwn')), ('PoE', ('poe_mode', 'poe_type')), ('Wireless', ('rf_role', 'rf_channel', 'rf_channel_width', 'tx_power')), - ('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', - 'device_id', 'vdc_id')), + ('Location', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')), + ('Device', ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id', 'vdc_id')), ('Connection', ('cabled', 'connected', 'occupied')), ) vdc_id = DynamicModelMultipleChoiceField( @@ -1242,7 +1258,8 @@ class FrontPortFilterForm(CabledFilterForm, DeviceComponentFilterForm): fieldsets = ( (None, ('q', 'filter_id', 'tag')), ('Attributes', ('name', 'label', 'type', 'color')), - ('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')), + ('Location', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')), + ('Device', ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')), ('Cable', ('cabled', 'occupied')), ) model = FrontPort @@ -1261,7 +1278,8 @@ class RearPortFilterForm(CabledFilterForm, DeviceComponentFilterForm): fieldsets = ( (None, ('q', 'filter_id', 'tag')), ('Attributes', ('name', 'label', 'type', 'color')), - ('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')), + ('Location', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')), + ('Device', ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')), ('Cable', ('cabled', 'occupied')), ) type = forms.MultipleChoiceField( @@ -1279,7 +1297,8 @@ class ModuleBayFilterForm(DeviceComponentFilterForm): fieldsets = ( (None, ('q', 'filter_id', 'tag')), ('Attributes', ('name', 'label', 'position')), - ('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')), + ('Location', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')), + ('Device', ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')), ) tag = TagFilterField(model) position = forms.CharField( @@ -1292,7 +1311,8 @@ class DeviceBayFilterForm(DeviceComponentFilterForm): fieldsets = ( (None, ('q', 'filter_id', 'tag')), ('Attributes', ('name', 'label')), - ('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')), + ('Location', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')), + ('Device', ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')), ) tag = TagFilterField(model) @@ -1302,7 +1322,8 @@ class InventoryItemFilterForm(DeviceComponentFilterForm): fieldsets = ( (None, ('q', 'filter_id', 'tag')), ('Attributes', ('name', 'label', 'role_id', 'manufacturer_id', 'serial', 'asset_tag', 'discovered')), - ('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')), + ('Location', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')), + ('Device', ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')), ) role_id = DynamicModelMultipleChoiceField( queryset=InventoryItemRole.objects.all(), diff --git a/netbox/dcim/tests/test_filtersets.py b/netbox/dcim/tests/test_filtersets.py index 346b35005..3f9712f2a 100644 --- a/netbox/dcim/tests/test_filtersets.py +++ b/netbox/dcim/tests/test_filtersets.py @@ -12,6 +12,23 @@ from virtualization.models import Cluster, ClusterType from wireless.choices import WirelessChannelChoices, WirelessRoleChoices +class DeviceComponentFilterSetTests: + + def test_device_type(self): + device_types = DeviceType.objects.all()[:2] + params = {'device_type_id': [device_types[0].pk, device_types[1].pk]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + params = {'device_type': [device_types[0].model, device_types[1].model]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + + def test_device_role(self): + device_role = DeviceRole.objects.all()[:2] + params = {'device_role_id': [device_role[0].pk, device_role[1].pk]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + params = {'device_role': [device_role[0].slug, device_role[1].slug]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + + class RegionTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = Region.objects.all() filterset = RegionFilterSet @@ -1998,7 +2015,7 @@ class ModuleTestCase(TestCase, ChangeLoggedFilterSetTests): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) -class ConsolePortTestCase(TestCase, ChangeLoggedFilterSetTests): +class ConsolePortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFilterSetTests): queryset = ConsolePort.objects.all() filterset = ConsolePortFilterSet @@ -2027,10 +2044,23 @@ class ConsolePortTestCase(TestCase, ChangeLoggedFilterSetTests): Site(name='Site 3', slug='site-3', region=regions[2], group=groups[2]), Site(name='Site X', slug='site-x'), )) + manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') - device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1') + device_types = ( + DeviceType(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'), + DeviceType(manufacturer=manufacturer, model='Device Type 2', slug='device-type-2'), + DeviceType(manufacturer=manufacturer, model='Device Type 3', slug='device-type-3'), + ) + DeviceType.objects.bulk_create(device_types) + module_type = ModuleType.objects.create(manufacturer=manufacturer, model='Module Type 1') - device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1') + + device_roles = ( + DeviceRole(name='Device Role 1', slug='device-role-1'), + DeviceRole(name='Device Role 2', slug='device-role-2'), + DeviceRole(name='Device Role 3', slug='device-role-3'), + ) + DeviceRole.objects.bulk_create(device_roles) locations = ( Location(name='Location 1', slug='location-1', site=sites[0]), @@ -2048,10 +2078,10 @@ class ConsolePortTestCase(TestCase, ChangeLoggedFilterSetTests): Rack.objects.bulk_create(racks) devices = ( - Device(name='Device 1', device_type=device_type, device_role=device_role, site=sites[0], location=locations[0], rack=racks[0]), - Device(name='Device 2', device_type=device_type, device_role=device_role, site=sites[1], location=locations[1], rack=racks[1]), - Device(name='Device 3', device_type=device_type, device_role=device_role, site=sites[2], location=locations[2], rack=racks[2]), - Device(name=None, device_type=device_type, device_role=device_role, site=sites[3]), # For cable connections + Device(name='Device 1', device_type=device_types[0], device_role=device_roles[0], site=sites[0], location=locations[0], rack=racks[0]), + Device(name='Device 2', device_type=device_types[1], device_role=device_roles[1], site=sites[1], location=locations[1], rack=racks[1]), + Device(name='Device 3', device_type=device_types[2], device_role=device_roles[2], site=sites[2], location=locations[2], rack=racks[2]), + Device(name=None, device_type=device_types[0], device_role=device_roles[0], site=sites[3]), # For cable connections ) Device.objects.bulk_create(devices) @@ -2165,7 +2195,7 @@ class ConsolePortTestCase(TestCase, ChangeLoggedFilterSetTests): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) -class ConsoleServerPortTestCase(TestCase, ChangeLoggedFilterSetTests): +class ConsoleServerPortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFilterSetTests): queryset = ConsoleServerPort.objects.all() filterset = ConsoleServerPortFilterSet @@ -2194,10 +2224,23 @@ class ConsoleServerPortTestCase(TestCase, ChangeLoggedFilterSetTests): Site(name='Site 3', slug='site-3', region=regions[2], group=groups[2]), Site(name='Site X', slug='site-x'), )) + manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') - device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1') + device_types = ( + DeviceType(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'), + DeviceType(manufacturer=manufacturer, model='Device Type 2', slug='device-type-2'), + DeviceType(manufacturer=manufacturer, model='Device Type 3', slug='device-type-3'), + ) + DeviceType.objects.bulk_create(device_types) + module_type = ModuleType.objects.create(manufacturer=manufacturer, model='Module Type 1') - device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1') + + device_roles = ( + DeviceRole(name='Device Role 1', slug='device-role-1'), + DeviceRole(name='Device Role 2', slug='device-role-2'), + DeviceRole(name='Device Role 3', slug='device-role-3'), + ) + DeviceRole.objects.bulk_create(device_roles) locations = ( Location(name='Location 1', slug='location-1', site=sites[0]), @@ -2215,10 +2258,10 @@ class ConsoleServerPortTestCase(TestCase, ChangeLoggedFilterSetTests): Rack.objects.bulk_create(racks) devices = ( - Device(name='Device 1', device_type=device_type, device_role=device_role, site=sites[0], location=locations[0], rack=racks[0]), - Device(name='Device 2', device_type=device_type, device_role=device_role, site=sites[1], location=locations[1], rack=racks[1]), - Device(name='Device 3', device_type=device_type, device_role=device_role, site=sites[2], location=locations[2], rack=racks[2]), - Device(name=None, device_type=device_type, device_role=device_role, site=sites[3]), # For cable connections + Device(name='Device 1', device_type=device_types[0], device_role=device_roles[0], site=sites[0], location=locations[0], rack=racks[0]), + Device(name='Device 2', device_type=device_types[1], device_role=device_roles[1], site=sites[1], location=locations[1], rack=racks[1]), + Device(name='Device 3', device_type=device_types[2], device_role=device_roles[2], site=sites[2], location=locations[2], rack=racks[2]), + Device(name=None, device_type=device_types[2], device_role=device_roles[2], site=sites[3]), # For cable connections ) Device.objects.bulk_create(devices) @@ -2332,7 +2375,7 @@ class ConsoleServerPortTestCase(TestCase, ChangeLoggedFilterSetTests): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) -class PowerPortTestCase(TestCase, ChangeLoggedFilterSetTests): +class PowerPortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFilterSetTests): queryset = PowerPort.objects.all() filterset = PowerPortFilterSet @@ -2361,10 +2404,23 @@ class PowerPortTestCase(TestCase, ChangeLoggedFilterSetTests): Site(name='Site 3', slug='site-3', region=regions[2], group=groups[2]), Site(name='Site X', slug='site-x'), )) + manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') - device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1') + device_types = ( + DeviceType(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'), + DeviceType(manufacturer=manufacturer, model='Device Type 2', slug='device-type-2'), + DeviceType(manufacturer=manufacturer, model='Device Type 3', slug='device-type-3'), + ) + DeviceType.objects.bulk_create(device_types) + module_type = ModuleType.objects.create(manufacturer=manufacturer, model='Module Type 1') - device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1') + + device_roles = ( + DeviceRole(name='Device Role 1', slug='device-role-1'), + DeviceRole(name='Device Role 2', slug='device-role-2'), + DeviceRole(name='Device Role 3', slug='device-role-3'), + ) + DeviceRole.objects.bulk_create(device_roles) locations = ( Location(name='Location 1', slug='location-1', site=sites[0]), @@ -2382,10 +2438,10 @@ class PowerPortTestCase(TestCase, ChangeLoggedFilterSetTests): Rack.objects.bulk_create(racks) devices = ( - Device(name='Device 1', device_type=device_type, device_role=device_role, site=sites[0], location=locations[0], rack=racks[0]), - Device(name='Device 2', device_type=device_type, device_role=device_role, site=sites[1], location=locations[1], rack=racks[1]), - Device(name='Device 3', device_type=device_type, device_role=device_role, site=sites[2], location=locations[2], rack=racks[2]), - Device(name=None, device_type=device_type, device_role=device_role, site=sites[3]), # For cable connections + Device(name='Device 1', device_type=device_types[0], device_role=device_roles[0], site=sites[0], location=locations[0], rack=racks[0]), + Device(name='Device 2', device_type=device_types[1], device_role=device_roles[1], site=sites[1], location=locations[1], rack=racks[1]), + Device(name='Device 3', device_type=device_types[2], device_role=device_roles[2], site=sites[2], location=locations[2], rack=racks[2]), + Device(name=None, device_type=device_types[2], device_role=device_roles[2], site=sites[3]), # For cable connections ) Device.objects.bulk_create(devices) @@ -2507,7 +2563,7 @@ class PowerPortTestCase(TestCase, ChangeLoggedFilterSetTests): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) -class PowerOutletTestCase(TestCase, ChangeLoggedFilterSetTests): +class PowerOutletTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFilterSetTests): queryset = PowerOutlet.objects.all() filterset = PowerOutletFilterSet @@ -2536,10 +2592,23 @@ class PowerOutletTestCase(TestCase, ChangeLoggedFilterSetTests): Site(name='Site 3', slug='site-3', region=regions[2], group=groups[2]), Site(name='Site X', slug='site-x'), )) + manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') - device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1') + device_types = ( + DeviceType(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'), + DeviceType(manufacturer=manufacturer, model='Device Type 2', slug='device-type-2'), + DeviceType(manufacturer=manufacturer, model='Device Type 3', slug='device-type-3'), + ) + DeviceType.objects.bulk_create(device_types) + module_type = ModuleType.objects.create(manufacturer=manufacturer, model='Module Type 1') - device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1') + + device_roles = ( + DeviceRole(name='Device Role 1', slug='device-role-1'), + DeviceRole(name='Device Role 2', slug='device-role-2'), + DeviceRole(name='Device Role 3', slug='device-role-3'), + ) + DeviceRole.objects.bulk_create(device_roles) locations = ( Location(name='Location 1', slug='location-1', site=sites[0]), @@ -2557,10 +2626,10 @@ class PowerOutletTestCase(TestCase, ChangeLoggedFilterSetTests): Rack.objects.bulk_create(racks) devices = ( - Device(name='Device 1', device_type=device_type, device_role=device_role, site=sites[0], location=locations[0], rack=racks[0]), - Device(name='Device 2', device_type=device_type, device_role=device_role, site=sites[1], location=locations[1], rack=racks[1]), - Device(name='Device 3', device_type=device_type, device_role=device_role, site=sites[2], location=locations[2], rack=racks[2]), - Device(name=None, device_type=device_type, device_role=device_role, site=sites[3]), # For cable connections + Device(name='Device 1', device_type=device_types[0], device_role=device_roles[0], site=sites[0], location=locations[0], rack=racks[0]), + Device(name='Device 2', device_type=device_types[1], device_role=device_roles[1], site=sites[1], location=locations[1], rack=racks[1]), + Device(name='Device 3', device_type=device_types[2], device_role=device_roles[2], site=sites[2], location=locations[2], rack=racks[2]), + Device(name=None, device_type=device_types[2], device_role=device_roles[2], site=sites[3]), # For cable connections ) Device.objects.bulk_create(devices) @@ -2678,7 +2747,7 @@ class PowerOutletTestCase(TestCase, ChangeLoggedFilterSetTests): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) -class InterfaceTestCase(TestCase, ChangeLoggedFilterSetTests): +class InterfaceTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFilterSetTests): queryset = Interface.objects.all() filterset = InterfaceFilterSet @@ -2707,10 +2776,23 @@ class InterfaceTestCase(TestCase, ChangeLoggedFilterSetTests): Site(name='Site 3', slug='site-3', region=regions[2], group=groups[2]), Site(name='Site X', slug='site-x'), )) + manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') - device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1') + device_types = ( + DeviceType(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'), + DeviceType(manufacturer=manufacturer, model='Device Type 2', slug='device-type-2'), + DeviceType(manufacturer=manufacturer, model='Device Type 3', slug='device-type-3'), + ) + DeviceType.objects.bulk_create(device_types) + module_type = ModuleType.objects.create(manufacturer=manufacturer, model='Module Type 1') - device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1') + + device_roles = ( + DeviceRole(name='Device Role 1', slug='device-role-1'), + DeviceRole(name='Device Role 2', slug='device-role-2'), + DeviceRole(name='Device Role 3', slug='device-role-3'), + ) + DeviceRole.objects.bulk_create(device_roles) locations = ( Location(name='Location 1', slug='location-1', site=sites[0]), @@ -2728,10 +2810,10 @@ class InterfaceTestCase(TestCase, ChangeLoggedFilterSetTests): Rack.objects.bulk_create(racks) devices = ( - Device(name='Device 1', device_type=device_type, device_role=device_role, site=sites[0], location=locations[0], rack=racks[0]), - Device(name='Device 2', device_type=device_type, device_role=device_role, site=sites[1], location=locations[1], rack=racks[1]), - Device(name='Device 3', device_type=device_type, device_role=device_role, site=sites[2], location=locations[2], rack=racks[2]), - Device(name=None, device_type=device_type, device_role=device_role, site=sites[3]), # For cable connections + Device(name='Device 1', device_type=device_types[0], device_role=device_roles[0], site=sites[0], location=locations[0], rack=racks[0]), + Device(name='Device 2', device_type=device_types[1], device_role=device_roles[1], site=sites[1], location=locations[1], rack=racks[1]), + Device(name='Device 3', device_type=device_types[2], device_role=device_roles[2], site=sites[2], location=locations[2], rack=racks[2]), + Device(name=None, device_type=device_types[2], device_role=device_roles[2], site=sites[3]), # For cable connections ) Device.objects.bulk_create(devices) @@ -3101,7 +3183,7 @@ class InterfaceTestCase(TestCase, ChangeLoggedFilterSetTests): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3) -class FrontPortTestCase(TestCase, ChangeLoggedFilterSetTests): +class FrontPortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFilterSetTests): queryset = FrontPort.objects.all() filterset = FrontPortFilterSet @@ -3130,10 +3212,23 @@ class FrontPortTestCase(TestCase, ChangeLoggedFilterSetTests): Site(name='Site 3', slug='site-3', region=regions[2], group=groups[2]), Site(name='Site X', slug='site-x'), )) + manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') - device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1') + device_types = ( + DeviceType(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'), + DeviceType(manufacturer=manufacturer, model='Device Type 2', slug='device-type-2'), + DeviceType(manufacturer=manufacturer, model='Device Type 3', slug='device-type-3'), + ) + DeviceType.objects.bulk_create(device_types) + module_type = ModuleType.objects.create(manufacturer=manufacturer, model='Module Type 1') - device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1') + + device_roles = ( + DeviceRole(name='Device Role 1', slug='device-role-1'), + DeviceRole(name='Device Role 2', slug='device-role-2'), + DeviceRole(name='Device Role 3', slug='device-role-3'), + ) + DeviceRole.objects.bulk_create(device_roles) locations = ( Location(name='Location 1', slug='location-1', site=sites[0]), @@ -3151,10 +3246,10 @@ class FrontPortTestCase(TestCase, ChangeLoggedFilterSetTests): Rack.objects.bulk_create(racks) devices = ( - Device(name='Device 1', device_type=device_type, device_role=device_role, site=sites[0], location=locations[0], rack=racks[0]), - Device(name='Device 2', device_type=device_type, device_role=device_role, site=sites[1], location=locations[1], rack=racks[1]), - Device(name='Device 3', device_type=device_type, device_role=device_role, site=sites[2], location=locations[2], rack=racks[2]), - Device(name=None, device_type=device_type, device_role=device_role, site=sites[3]), # For cable connections + Device(name='Device 1', device_type=device_types[0], device_role=device_roles[0], site=sites[0], location=locations[0], rack=racks[0]), + Device(name='Device 2', device_type=device_types[1], device_role=device_roles[1], site=sites[1], location=locations[1], rack=racks[1]), + Device(name='Device 3', device_type=device_types[2], device_role=device_roles[2], site=sites[2], location=locations[2], rack=racks[2]), + Device(name=None, device_type=device_types[2], device_role=device_roles[2], site=sites[3]), # For cable connections ) Device.objects.bulk_create(devices) @@ -3277,7 +3372,7 @@ class FrontPortTestCase(TestCase, ChangeLoggedFilterSetTests): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) -class RearPortTestCase(TestCase, ChangeLoggedFilterSetTests): +class RearPortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFilterSetTests): queryset = RearPort.objects.all() filterset = RearPortFilterSet @@ -3306,10 +3401,23 @@ class RearPortTestCase(TestCase, ChangeLoggedFilterSetTests): Site(name='Site 3', slug='site-3', region=regions[2], group=groups[2]), Site(name='Site X', slug='site-x'), )) + manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') - device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1') + device_types = ( + DeviceType(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'), + DeviceType(manufacturer=manufacturer, model='Device Type 2', slug='device-type-2'), + DeviceType(manufacturer=manufacturer, model='Device Type 3', slug='device-type-3'), + ) + DeviceType.objects.bulk_create(device_types) + module_type = ModuleType.objects.create(manufacturer=manufacturer, model='Module Type 1') - device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1') + + device_roles = ( + DeviceRole(name='Device Role 1', slug='device-role-1'), + DeviceRole(name='Device Role 2', slug='device-role-2'), + DeviceRole(name='Device Role 3', slug='device-role-3'), + ) + DeviceRole.objects.bulk_create(device_roles) locations = ( Location(name='Location 1', slug='location-1', site=sites[0]), @@ -3327,10 +3435,10 @@ class RearPortTestCase(TestCase, ChangeLoggedFilterSetTests): Rack.objects.bulk_create(racks) devices = ( - Device(name='Device 1', device_type=device_type, device_role=device_role, site=sites[0], location=locations[0], rack=racks[0]), - Device(name='Device 2', device_type=device_type, device_role=device_role, site=sites[1], location=locations[1], rack=racks[1]), - Device(name='Device 3', device_type=device_type, device_role=device_role, site=sites[2], location=locations[2], rack=racks[2]), - Device(name=None, device_type=device_type, device_role=device_role, site=sites[3]), # For cable connections + Device(name='Device 1', device_type=device_types[0], device_role=device_roles[0], site=sites[0], location=locations[0], rack=racks[0]), + Device(name='Device 2', device_type=device_types[1], device_role=device_roles[1], site=sites[1], location=locations[1], rack=racks[1]), + Device(name='Device 3', device_type=device_types[2], device_role=device_roles[2], site=sites[2], location=locations[2], rack=racks[2]), + Device(name=None, device_type=device_types[2], device_role=device_roles[2], site=sites[3]), # For cable connections ) Device.objects.bulk_create(devices) @@ -3447,7 +3555,7 @@ class RearPortTestCase(TestCase, ChangeLoggedFilterSetTests): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) -class ModuleBayTestCase(TestCase, ChangeLoggedFilterSetTests): +class ModuleBayTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFilterSetTests): queryset = ModuleBay.objects.all() filterset = ModuleBayFilterSet @@ -3476,9 +3584,21 @@ class ModuleBayTestCase(TestCase, ChangeLoggedFilterSetTests): Site(name='Site 3', slug='site-3', region=regions[2], group=groups[2]), Site(name='Site X', slug='site-x'), )) + manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') - device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Model 1', slug='model-1') - device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1') + device_types = ( + DeviceType(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'), + DeviceType(manufacturer=manufacturer, model='Device Type 2', slug='device-type-2'), + DeviceType(manufacturer=manufacturer, model='Device Type 3', slug='device-type-3'), + ) + DeviceType.objects.bulk_create(device_types) + + device_roles = ( + DeviceRole(name='Device Role 1', slug='device-role-1'), + DeviceRole(name='Device Role 2', slug='device-role-2'), + DeviceRole(name='Device Role 3', slug='device-role-3'), + ) + DeviceRole.objects.bulk_create(device_roles) locations = ( Location(name='Location 1', slug='location-1', site=sites[0]), @@ -3496,9 +3616,9 @@ class ModuleBayTestCase(TestCase, ChangeLoggedFilterSetTests): Rack.objects.bulk_create(racks) devices = ( - Device(name='Device 1', device_type=device_type, device_role=device_role, site=sites[0], location=locations[0], rack=racks[0]), - Device(name='Device 2', device_type=device_type, device_role=device_role, site=sites[1], location=locations[1], rack=racks[1]), - Device(name='Device 3', device_type=device_type, device_role=device_role, site=sites[2], location=locations[2], rack=racks[2]), + Device(name='Device 1', device_type=device_types[0], device_role=device_roles[0], site=sites[0], location=locations[0], rack=racks[0]), + Device(name='Device 2', device_type=device_types[1], device_role=device_roles[1], site=sites[1], location=locations[1], rack=racks[1]), + Device(name='Device 3', device_type=device_types[2], device_role=device_roles[2], site=sites[2], location=locations[2], rack=racks[2]), ) Device.objects.bulk_create(devices) @@ -3564,7 +3684,7 @@ class ModuleBayTestCase(TestCase, ChangeLoggedFilterSetTests): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) -class DeviceBayTestCase(TestCase, ChangeLoggedFilterSetTests): +class DeviceBayTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFilterSetTests): queryset = DeviceBay.objects.all() filterset = DeviceBayFilterSet @@ -3593,9 +3713,21 @@ class DeviceBayTestCase(TestCase, ChangeLoggedFilterSetTests): Site(name='Site 3', slug='site-3', region=regions[2], group=groups[2]), Site(name='Site X', slug='site-x'), )) + manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') - device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Model 1', slug='model-1') - device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1') + device_types = ( + DeviceType(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'), + DeviceType(manufacturer=manufacturer, model='Device Type 2', slug='device-type-2'), + DeviceType(manufacturer=manufacturer, model='Device Type 3', slug='device-type-3'), + ) + DeviceType.objects.bulk_create(device_types) + + device_roles = ( + DeviceRole(name='Device Role 1', slug='device-role-1'), + DeviceRole(name='Device Role 2', slug='device-role-2'), + DeviceRole(name='Device Role 3', slug='device-role-3'), + ) + DeviceRole.objects.bulk_create(device_roles) locations = ( Location(name='Location 1', slug='location-1', site=sites[0]), @@ -3613,9 +3745,9 @@ class DeviceBayTestCase(TestCase, ChangeLoggedFilterSetTests): Rack.objects.bulk_create(racks) devices = ( - Device(name='Device 1', device_type=device_type, device_role=device_role, site=sites[0], location=locations[0], rack=racks[0]), - Device(name='Device 2', device_type=device_type, device_role=device_role, site=sites[1], location=locations[1], rack=racks[1]), - Device(name='Device 3', device_type=device_type, device_role=device_role, site=sites[2], location=locations[2], rack=racks[2]), + Device(name='Device 1', device_type=device_types[0], device_role=device_roles[0], site=sites[0], location=locations[0], rack=racks[0]), + Device(name='Device 2', device_type=device_types[1], device_role=device_roles[1], site=sites[1], location=locations[1], rack=racks[1]), + Device(name='Device 3', device_type=device_types[2], device_role=device_roles[2], site=sites[2], location=locations[2], rack=racks[2]), ) Device.objects.bulk_create(devices) @@ -3694,8 +3826,19 @@ class InventoryItemTestCase(TestCase, ChangeLoggedFilterSetTests): ) Manufacturer.objects.bulk_create(manufacturers) - device_type = DeviceType.objects.create(manufacturer=manufacturers[0], model='Model 1', slug='model-1') - device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1') + device_types = ( + DeviceType(manufacturer=manufacturers[0], model='Device Type 1', slug='device-type-1'), + DeviceType(manufacturer=manufacturers[0], model='Device Type 2', slug='device-type-2'), + DeviceType(manufacturer=manufacturers[0], model='Device Type 3', slug='device-type-3'), + ) + DeviceType.objects.bulk_create(device_types) + + device_roles = ( + DeviceRole(name='Device Role 1', slug='device-role-1'), + DeviceRole(name='Device Role 2', slug='device-role-2'), + DeviceRole(name='Device Role 3', slug='device-role-3'), + ) + DeviceRole.objects.bulk_create(device_roles) regions = ( Region(name='Region 1', slug='region-1'), @@ -3736,9 +3879,9 @@ class InventoryItemTestCase(TestCase, ChangeLoggedFilterSetTests): Rack.objects.bulk_create(racks) devices = ( - Device(name='Device 1', device_type=device_type, device_role=device_role, site=sites[0], location=locations[0], rack=racks[0]), - Device(name='Device 2', device_type=device_type, device_role=device_role, site=sites[1], location=locations[1], rack=racks[1]), - Device(name='Device 3', device_type=device_type, device_role=device_role, site=sites[2], location=locations[2], rack=racks[2]), + Device(name='Device 1', device_type=device_types[0], device_role=device_roles[0], site=sites[0], location=locations[0], rack=racks[0]), + Device(name='Device 2', device_type=device_types[1], device_role=device_roles[1], site=sites[1], location=locations[1], rack=racks[1]), + Device(name='Device 3', device_type=device_types[2], device_role=device_roles[2], site=sites[2], location=locations[2], rack=racks[2]), ) Device.objects.bulk_create(devices) @@ -3829,6 +3972,20 @@ class InventoryItemTestCase(TestCase, ChangeLoggedFilterSetTests): params = {'rack': [racks[0].name, racks[1].name]} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) + def test_device_type(self): + device_types = DeviceType.objects.all()[:2] + params = {'device_type_id': [device_types[0].pk, device_types[1].pk]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) + params = {'device_type': [device_types[0].model, device_types[1].model]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) + + def test_device_role(self): + device_role = DeviceRole.objects.all()[:2] + params = {'device_role_id': [device_role[0].pk, device_role[1].pk]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) + params = {'device_role': [device_role[0].slug, device_role[1].slug]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) + def test_device(self): devices = Device.objects.all()[:2] params = {'device_id': [devices[0].pk, devices[1].pk]} From 3e77daff012b9a0e127330c42d713362cf10a74b Mon Sep 17 00:00:00 2001 From: Arthur Date: Wed, 31 May 2023 08:08:32 -0700 Subject: [PATCH 046/170] 12767 pin graphene-django version --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 6757f3260..29f5bbc7b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -19,7 +19,7 @@ drf-spectacular==0.26.2 drf-spectacular-sidecar==2023.5.1 dulwich==0.21.5 feedparser==6.0.10 -graphene-django==3.0.2 +graphene-django==3.0.0 gunicorn==20.1.0 Jinja2==3.1.2 Markdown==3.3.7 From dbd3c6de24afd318d6204f96c12b64e86082475f Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Thu, 1 Jun 2023 00:52:37 +0530 Subject: [PATCH 047/170] Fixes return_url for image attachment (#12721) * fixes return_url for image attachment #12538 * simplified conditions * handle nonetype error * fixed request check * Introduce htmx_table template tag for embedding HTMX-backed object tables --------- Co-authored-by: jeremystretch --- netbox/netbox/tables/columns.py | 8 ++++++-- .../inc/panels/image_attachments.html | 8 ++------ .../templates/builtins/htmx_table.html | 4 ++++ .../utilities/templatetags/builtins/tags.py | 20 +++++++++++++++++++ 4 files changed, 32 insertions(+), 8 deletions(-) create mode 100644 netbox/utilities/templates/builtins/htmx_table.html diff --git a/netbox/netbox/tables/columns.py b/netbox/netbox/tables/columns.py index 66ee787a8..9ef327026 100644 --- a/netbox/netbox/tables/columns.py +++ b/netbox/netbox/tables/columns.py @@ -234,8 +234,12 @@ class ActionsColumn(tables.Column): return '' model = table.Meta.model - request = getattr(table, 'context', {}).get('request') - url_appendix = f'?return_url={quote(request.get_full_path())}' if request else '' + if request := getattr(table, 'context', {}).get('request'): + return_url = request.GET.get('return_url', request.get_full_path()) + url_appendix = f'?return_url={quote(return_url)}' + else: + url_appendix = '' + html = '' # Compile actions menu diff --git a/netbox/templates/inc/panels/image_attachments.html b/netbox/templates/inc/panels/image_attachments.html index 0c1d212d9..a09fe78d5 100644 --- a/netbox/templates/inc/panels/image_attachments.html +++ b/netbox/templates/inc/panels/image_attachments.html @@ -1,12 +1,8 @@ {% load helpers %}
-
- Images -
-
+
Images
+ {% htmx_table 'extras:imageattachment_list' content_type_id=object|content_type_id object_id=object.pk %} {% if perms.extras.add_imageattachment %}
- - - - From 3754e00ee0c21a89a4c49a64e049f311983b047d Mon Sep 17 00:00:00 2001 From: Arthur Date: Tue, 27 Jun 2023 08:44:54 -0700 Subject: [PATCH 096/170] 12809 document not to use underscores in model names --- docs/plugins/development/models.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/plugins/development/models.md b/docs/plugins/development/models.md index b3bcb292a..c51d025f4 100644 --- a/docs/plugins/development/models.md +++ b/docs/plugins/development/models.md @@ -19,6 +19,9 @@ class MyModel(models.Model): Every model includes by default a numeric primary key. This value is generated automatically by the database, and can be referenced as `pk` or `id`. +!!! note + Model names should adhere to [PEP8](https://www.python.org/dev/peps/pep-0008/#class-names) standards and be CapWords (no underscores). Using underscores in model names will result in problems with permissions. + ## Enabling NetBox Features Plugin models can leverage certain NetBox features by inheriting from NetBox's `NetBoxModel` class. This class extends the plugin model to enable features unique to NetBox, including: From f69d99ea67c503d481ac8a3b6c1213be99db9990 Mon Sep 17 00:00:00 2001 From: Daniel Sheppard Date: Tue, 27 Jun 2023 08:48:27 -0500 Subject: [PATCH 097/170] Fixes #12760 - Adds Vary header to cause cache to be keyed based on URL and the HX-Request header (or lack thereof) --- netbox/netbox/middleware.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/netbox/netbox/middleware.py b/netbox/netbox/middleware.py index ba6967f1f..18f350fd7 100644 --- a/netbox/netbox/middleware.py +++ b/netbox/netbox/middleware.py @@ -49,6 +49,9 @@ class CoreMiddleware: # Attach the unique request ID as an HTTP header. response['X-Request-ID'] = request.id + # Enable the Vary header to help with caching of HTMX responses + response['Vary'] = 'HX-Request' + # If this is an API request, attach an HTTP header annotating the API version (e.g. '3.5'). if is_api_request(request): response['API-Version'] = settings.REST_FRAMEWORK_VERSION From 3307bd200c38d2c548022917292c5f8db37c5541 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Fri, 30 Jun 2023 01:32:39 +0530 Subject: [PATCH 098/170] Fixes syntax error on reports (#12997) * fixes syntax error on reports #12842 * remove the extra filter #12842 --- netbox/extras/models/reports.py | 7 +- netbox/templates/extras/report_list.html | 128 ++++++++++++----------- 2 files changed, 72 insertions(+), 63 deletions(-) diff --git a/netbox/extras/models/reports.py b/netbox/extras/models/reports.py index aaa785696..f1e336df5 100644 --- a/netbox/extras/models/reports.py +++ b/netbox/extras/models/reports.py @@ -1,7 +1,7 @@ import inspect +import logging from functools import cached_property -from django.contrib.contenttypes.fields import GenericRelation from django.db import models from django.urls import reverse @@ -12,6 +12,8 @@ from netbox.models.features import JobsMixin, WebhooksMixin from utilities.querysets import RestrictedQuerySet from .mixins import PythonModuleMixin +logger = logging.getLogger('netbox.reports') + __all__ = ( 'Report', 'ReportModule', @@ -56,7 +58,8 @@ class ReportModule(PythonModuleMixin, JobsMixin, ManagedFile): try: module = self.get_module() - except ImportError: + except (ImportError, SyntaxError) as e: + logger.error(f"Unable to load report module {self.name}, exception: {e}") return {} reports = {} ordered = getattr(module, 'report_order', []) diff --git a/netbox/templates/extras/report_list.html b/netbox/templates/extras/report_list.html index 0c27eefda..7867fdbbe 100644 --- a/netbox/templates/extras/report_list.html +++ b/netbox/templates/extras/report_list.html @@ -38,71 +38,77 @@
{% include 'inc/sync_warning.html' with object=module %} -
MAC Address{{ object.mac_address|placeholder }}{{ object.mac_address|placeholder }}
WWN{{ object.wwn|placeholder }}{{ object.wwn|placeholder }}
VRF
MAC Address{{ object.mac_address|placeholder }}{{ object.mac_address|placeholder }}
802.1Q ModeRack {{ terminations.0.device.rack|linkify|placeholder }}
Device{{ terminations.0.device|linkify }}
{{ terminations.0|meta:"verbose_name"|capfirst }} {% for term in terminations %} - {{ term|linkify }}{% if not forloop.last %},{% endif %} + {{term.device|linkify}} + + {{ term|linkify }} + {% if not forloop.last %}
{% endif %} {% endfor %}
- - - - - - - - - - - {% with jobs=module.get_latest_jobs %} - {% for report_name, report in module.reports.items %} - {% with last_job=jobs|get_key:report.name %} - - - - {% if last_job %} - - - {% else %} - - - {% endif %} - - - {% for method, stats in last_job.data.items %} + {% if module.reports %} +
NameDescriptionLast RunStatus
- {{ report.name }} - {{ report.description|markdown|placeholder }} - {{ last_job.created|annotated_date }} - - {% badge last_job.get_status_display last_job.get_status_color %} - Never{{ ''|placeholder }} - {% if perms.extras.run_report %} -
-
- {% csrf_token %} - -
-
- {% endif %} -
+ + + + + + + + + + + {% with jobs=module.get_latest_jobs %} + {% for report_name, report in module.reports.items %} + {% with last_job=jobs|get_key:report.name %} - - + {% if last_job %} + + + {% else %} + + + {% endif %} + - {% endfor %} - {% endwith %} - {% endfor %} - {% endwith %} - -
NameDescriptionLast RunStatus
- {{ method }} + + {{ report.name }} - {{ stats.success }} - {{ stats.info }} - {{ stats.warning }} - {{ stats.failure }} + {{ report.description|markdown|placeholder }} + {{ last_job.created|annotated_date }} + + {% badge last_job.get_status_display last_job.get_status_color %} + Never{{ ''|placeholder }} + {% if perms.extras.run_report %} +
+
+ {% csrf_token %} + +
+
+ {% endif %}
+ {% for method, stats in last_job.data.items %} + + + {{ method }} + + + {{ stats.success }} + {{ stats.info }} + {{ stats.warning }} + {{ stats.failure }} + + + {% endfor %} + {% endwith %} + {% endfor %} + {% endwith %} + + + {% else %} + + {% endif %} {% empty %} From 63ba9fb38cde90ec9183d1cb234a7de12450e4d2 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 5 Jul 2023 11:39:35 -0400 Subject: [PATCH 099/170] Fixes #11335: Default manager for ObjectChange should filter by installed apps (#11709) * Fixes #11335: Default manager for ObjectChange should filter by installed apps * Employ canonical model discovery mechanism * Move filtering logic to valid_models() queryset method * fixed import to avoid content type does not exist * Cleanup --------- Co-authored-by: Abhimanyu Saharan --- netbox/extras/api/views.py | 2 +- netbox/extras/models/change_logging.py | 4 ++-- netbox/extras/querysets.py | 13 +++++++++++++ netbox/extras/tests/test_api.py | 3 ++- netbox/extras/views.py | 8 ++++---- netbox/users/views.py | 4 +++- 6 files changed, 25 insertions(+), 9 deletions(-) diff --git a/netbox/extras/api/views.py b/netbox/extras/api/views.py index 3f796d7f8..f4b5a1433 100644 --- a/netbox/extras/api/views.py +++ b/netbox/extras/api/views.py @@ -368,7 +368,7 @@ class ObjectChangeViewSet(ReadOnlyModelViewSet): Retrieve a list of recent changes. """ metadata_class = ContentTypeMetadata - queryset = ObjectChange.objects.prefetch_related('user') + queryset = ObjectChange.objects.valid_models().prefetch_related('user') serializer_class = serializers.ObjectChangeSerializer filterset_class = filtersets.ObjectChangeFilterSet diff --git a/netbox/extras/models/change_logging.py b/netbox/extras/models/change_logging.py index e2b118b84..2cb53ed01 100644 --- a/netbox/extras/models/change_logging.py +++ b/netbox/extras/models/change_logging.py @@ -5,7 +5,7 @@ from django.db import models from django.urls import reverse from extras.choices import * -from utilities.querysets import RestrictedQuerySet +from ..querysets import ObjectChangeQuerySet __all__ = ( 'ObjectChange', @@ -82,7 +82,7 @@ class ObjectChange(models.Model): null=True ) - objects = RestrictedQuerySet.as_manager() + objects = ObjectChangeQuerySet.as_manager() class Meta: ordering = ['-time'] diff --git a/netbox/extras/querysets.py b/netbox/extras/querysets.py index 2b97af0fb..2e6f93b93 100644 --- a/netbox/extras/querysets.py +++ b/netbox/extras/querysets.py @@ -1,3 +1,5 @@ +from django.apps import apps +from django.contrib.contenttypes.models import ContentType from django.contrib.postgres.aggregates import JSONBAgg from django.db.models import OuterRef, Subquery, Q @@ -151,3 +153,14 @@ class ConfigContextModelQuerySet(RestrictedQuerySet): ) return base_query + + +class ObjectChangeQuerySet(RestrictedQuerySet): + + def valid_models(self): + # Exclude any change records which refer to an instance of a model that's no longer installed. This + # can happen when a plugin is removed but its data remains in the database, for example. + content_type_ids = set( + ct.pk for ct in ContentType.objects.get_for_models(*apps.get_models()).values() + ) + return self.filter(changed_object_type_id__in=content_type_ids) diff --git a/netbox/extras/tests/test_api.py b/netbox/extras/tests/test_api.py index b59481a36..086c8e246 100644 --- a/netbox/extras/tests/test_api.py +++ b/netbox/extras/tests/test_api.py @@ -8,7 +8,6 @@ from rest_framework import status from core.choices import ManagedFileRootPathChoices from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Rack, Location, RackRole, Site -from extras.api.views import ReportViewSet, ScriptViewSet from extras.models import * from extras.reports import Report from extras.scripts import BooleanVar, IntegerVar, Script, StringVar @@ -579,6 +578,7 @@ class ReportTest(APITestCase): super().setUp() # Monkey-patch the API viewset's _get_report() method to return our test Report above + from extras.api.views import ReportViewSet ReportViewSet._get_report = self.get_test_report def test_get_report(self): @@ -621,6 +621,7 @@ class ScriptTest(APITestCase): super().setUp() # Monkey-patch the API viewset's _get_script() method to return our test Script above + from extras.api.views import ScriptViewSet ScriptViewSet._get_script = self.get_test_script def test_get_script(self): diff --git a/netbox/extras/views.py b/netbox/extras/views.py index 6cbadf09d..6ba63ab58 100644 --- a/netbox/extras/views.py +++ b/netbox/extras/views.py @@ -511,7 +511,7 @@ class ConfigTemplateBulkSyncDataView(generic.BulkSyncDataView): # class ObjectChangeListView(generic.ObjectListView): - queryset = ObjectChange.objects.all() + queryset = ObjectChange.objects.valid_models() filterset = filtersets.ObjectChangeFilterSet filterset_form = forms.ObjectChangeFilterForm table = tables.ObjectChangeTable @@ -521,10 +521,10 @@ class ObjectChangeListView(generic.ObjectListView): @register_model_view(ObjectChange) class ObjectChangeView(generic.ObjectView): - queryset = ObjectChange.objects.all() + queryset = ObjectChange.objects.valid_models() def get_extra_context(self, request, instance): - related_changes = ObjectChange.objects.restrict(request.user, 'view').filter( + related_changes = ObjectChange.objects.valid_models().restrict(request.user, 'view').filter( request_id=instance.request_id ).exclude( pk=instance.pk @@ -534,7 +534,7 @@ class ObjectChangeView(generic.ObjectView): orderable=False ) - objectchanges = ObjectChange.objects.restrict(request.user, 'view').filter( + objectchanges = ObjectChange.objects.valid_models().restrict(request.user, 'view').filter( changed_object_type=instance.changed_object_type, changed_object_id=instance.changed_object_id, ) diff --git a/netbox/users/views.py b/netbox/users/views.py index a82620914..05648e2e3 100644 --- a/netbox/users/views.py +++ b/netbox/users/views.py @@ -159,7 +159,9 @@ class ProfileView(LoginRequiredMixin, View): def get(self, request): # Compile changelog table - changelog = ObjectChange.objects.restrict(request.user, 'view').filter(user=request.user).prefetch_related( + changelog = ObjectChange.objects.valid_models().restrict(request.user, 'view').filter( + user=request.user + ).prefetch_related( 'changed_object_type' )[:20] changelog_table = ObjectChangeTable(changelog) From 07ae7c8a6e04ecc6e641044330fd4d03e4997252 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 5 Jul 2023 11:43:53 -0400 Subject: [PATCH 100/170] Changelog for #11335, #12760, #12842, #12951, #12955 --- docs/release-notes/version-3.5.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/release-notes/version-3.5.md b/docs/release-notes/version-3.5.md index 7b82ed07c..beb0a1991 100644 --- a/docs/release-notes/version-3.5.md +++ b/docs/release-notes/version-3.5.md @@ -5,11 +5,16 @@ ### Enhancements * [#12945](https://github.com/netbox-community/netbox/issues/12945) - Add 100GE QSFP-DD interface type +* [#12955](https://github.com/netbox-community/netbox/issues/12955) - Include additional contact details on contact assignments table ### Bug Fixes +* [#11335](https://github.com/netbox-community/netbox/issues/11335) - Exclude stale content types when retrieving changelog records * [#12533](https://github.com/netbox-community/netbox/issues/12533) - Fix REST API validation of null values for several interface attributes +* [#12760](https://github.com/netbox-community/netbox/issues/12760) - Avoid rendering partial HTMX responses when restoring browser tabs +* [#12842](https://github.com/netbox-community/netbox/issues/12842) - Improve handling of exceptions when loading reports * [#12849](https://github.com/netbox-community/netbox/issues/12849) - Fix LDAP group permissions assignment for API clients +* [#12951](https://github.com/netbox-community/netbox/issues/12951) - Display consistent parent information for each termination under cable view * [#12953](https://github.com/netbox-community/netbox/issues/12953) - Fix designation of primary IP addresses during interface assignment * [#12960](https://github.com/netbox-community/netbox/issues/12960) - Fix OpenAPI schema for various choice fields * [#12961](https://github.com/netbox-community/netbox/issues/12961) - Set correct return URL for object contacts tabs From 4355ee6407946a0f858ec8bba1dc115c4f504d7e Mon Sep 17 00:00:00 2001 From: Arthur Date: Thu, 6 Jul 2023 12:45:11 +0700 Subject: [PATCH 101/170] 12092 allow setnull for bulk edit power port maximum and allocated draw --- netbox/dcim/forms/bulk_edit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/dcim/forms/bulk_edit.py b/netbox/dcim/forms/bulk_edit.py index bc9693afb..f1abdef1d 100644 --- a/netbox/dcim/forms/bulk_edit.py +++ b/netbox/dcim/forms/bulk_edit.py @@ -1106,7 +1106,7 @@ class PowerPortBulkEditForm( (None, ('module', 'type', 'label', 'description', 'mark_connected')), ('Power', ('maximum_draw', 'allocated_draw')), ) - nullable_fields = ('module', 'label', 'description') + nullable_fields = ('module', 'label', 'description', 'maximum_draw', 'allocated_draw') class PowerOutletBulkEditForm( From 5f0922713fa03135fb837c9ff2cb31db2691c461 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 29 Jun 2023 15:35:53 -0400 Subject: [PATCH 102/170] Fixes #13047: Add annotate_asn_count() to ASNRange manager --- netbox/ipam/models/asns.py | 3 +++ netbox/ipam/querysets.py | 26 +++++++++++++++++++++++++- netbox/ipam/tables/asn.py | 9 ++++----- netbox/ipam/views.py | 10 +++------- 4 files changed, 35 insertions(+), 13 deletions(-) diff --git a/netbox/ipam/models/asns.py b/netbox/ipam/models/asns.py index a07cbb789..6c0b5231b 100644 --- a/netbox/ipam/models/asns.py +++ b/netbox/ipam/models/asns.py @@ -4,6 +4,7 @@ from django.urls import reverse from django.utils.translation import gettext as _ from ipam.fields import ASNField +from ipam.querysets import ASNRangeQuerySet from netbox.models import OrganizationalModel, PrimaryModel __all__ = ( @@ -37,6 +38,8 @@ class ASNRange(OrganizationalModel): null=True ) + objects = ASNRangeQuerySet.as_manager() + class Meta: ordering = ('name',) verbose_name = 'ASN range' diff --git a/netbox/ipam/querysets.py b/netbox/ipam/querysets.py index 9f4463f61..d6b23b843 100644 --- a/netbox/ipam/querysets.py +++ b/netbox/ipam/querysets.py @@ -1,9 +1,33 @@ from django.contrib.contenttypes.models import ContentType -from django.db.models import Q +from django.db.models import Count, OuterRef, Q, Subquery, Value from django.db.models.expressions import RawSQL from utilities.querysets import RestrictedQuerySet +__all__ = ( + 'ASNRangeQuerySet', + 'PrefixQuerySet', + 'VLANQuerySet', +) + + +class ASNRangeQuerySet(RestrictedQuerySet): + + def annotate_asn_counts(self): + """ + Annotate the number of ASNs which appear within each range. + """ + from .models import ASN + + # Because ASN does not have a foreign key to ASNRange, we create a fake column "_" with a consistent value + # that we can use to count ASNs and return a single value per ASNRange. + asns = ASN.objects.filter( + asn__gte=OuterRef('start'), + asn__lte=OuterRef('end') + ).order_by().annotate(_=Value(1)).values('_').annotate(c=Count('*')).values('c') + + return self.annotate(asn_count=Subquery(asns)) + class PrefixQuerySet(RestrictedQuerySet): diff --git a/netbox/ipam/tables/asn.py b/netbox/ipam/tables/asn.py index 511e914ec..356f2fc17 100644 --- a/netbox/ipam/tables/asn.py +++ b/netbox/ipam/tables/asn.py @@ -21,10 +21,8 @@ class ASNRangeTable(TenancyColumnsMixin, NetBoxTable): tags = columns.TagColumn( url_name='ipam:asnrange_list' ) - asn_count = columns.LinkedCountColumn( - viewname='ipam:asn_list', - url_params={'asn_id': 'pk'}, - verbose_name=_('ASN Count') + asn_count = tables.Column( + verbose_name=_('ASNs') ) class Meta(NetBoxTable.Meta): @@ -59,7 +57,8 @@ class ASNTable(TenancyColumnsMixin, NetBoxTable): verbose_name=_('Provider Count') ) sites = columns.ManyToManyColumn( - linkify_item=True + linkify_item=True, + verbose_name=_('Sites') ) comments = columns.MarkdownColumn() tags = columns.TagColumn( diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index 6b73a061b..6efaef8ea 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -198,7 +198,7 @@ class RIRBulkDeleteView(generic.BulkDeleteView): # class ASNRangeListView(generic.ObjectListView): - queryset = ASNRange.objects.all() + queryset = ASNRange.objects.annotate_asn_counts() filterset = filtersets.ASNRangeFilterSet filterset_form = forms.ASNRangeFilterForm table = tables.ASNRangeTable @@ -247,18 +247,14 @@ class ASNRangeBulkImportView(generic.BulkImportView): class ASNRangeBulkEditView(generic.BulkEditView): - queryset = ASNRange.objects.annotate( - site_count=count_related(Site, 'asns') - ) + queryset = ASNRange.objects.annotate_asn_counts() filterset = filtersets.ASNRangeFilterSet table = tables.ASNRangeTable form = forms.ASNRangeBulkEditForm class ASNRangeBulkDeleteView(generic.BulkDeleteView): - queryset = ASNRange.objects.annotate( - site_count=count_related(Site, 'asns') - ) + queryset = ASNRange.objects.annotate_asn_counts() filterset = filtersets.ASNRangeFilterSet table = tables.ASNRangeTable From 860be780ad3b3887bda9e7606e869cdccba89b0d Mon Sep 17 00:00:00 2001 From: Anthony Brissonnet <137606620+netopsab@users.noreply.github.com> Date: Thu, 6 Jul 2023 15:28:45 +0200 Subject: [PATCH 103/170] Fix #12579 create cable and add another error (#13007) * fix create cable and add another error #12579 * fix return proper parent object field * improve code and wokflow --------- Co-authored-by: netopsab --- netbox/dcim/views.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index b52e0afa5..008db382a 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -3131,6 +3131,19 @@ class CableEditView(generic.ObjectEditView): return obj + def get_extra_addanother_params(self, request): + + params = { + 'a_terminations_type': request.GET.get('a_terminations_type'), + 'b_terminations_type': request.GET.get('b_terminations_type') + } + + for key in request.POST: + if 'device' in key or 'power_panel' in key or 'circuit' in key: + params.update({key: request.POST.get(key)}) + + return params + @register_model_view(Cable, 'delete') class CableDeleteView(generic.ObjectDeleteView): From 16ee42ac388466f7829b9fc70888554c5d1dc655 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Sat, 24 Jun 2023 00:29:37 +0530 Subject: [PATCH 104/170] fixes prechange snapshot #12617 --- netbox/ipam/forms/model_forms.py | 1 + 1 file changed, 1 insertion(+) diff --git a/netbox/ipam/forms/model_forms.py b/netbox/ipam/forms/model_forms.py index 6fa0f95ea..a3c218fc9 100644 --- a/netbox/ipam/forms/model_forms.py +++ b/netbox/ipam/forms/model_forms.py @@ -379,6 +379,7 @@ class IPAddressForm(TenancyForm, NetBoxModelForm): interface = self.instance.assigned_object if type(interface) in (Interface, VMInterface): parent = interface.parent_object + parent.snapshot() if self.cleaned_data['primary_for_parent']: if ipaddress.address.version == 4: parent.primary_ip4 = ipaddress From ffe4558ec5c94f7271fd348ec29e67e24d827065 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Thu, 6 Jul 2023 18:41:42 +0530 Subject: [PATCH 105/170] fixes search for vdc #13100 --- netbox/dcim/filtersets.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/netbox/dcim/filtersets.py b/netbox/dcim/filtersets.py index e784be8e8..5ddaf9a9a 100644 --- a/netbox/dcim/filtersets.py +++ b/netbox/dcim/filtersets.py @@ -1077,10 +1077,13 @@ class VirtualDeviceContextFilterSet(NetBoxModelFilterSet, TenancyFilterSet): def search(self, queryset, name, value): if not value.strip(): return queryset - return queryset.filter( - Q(name__icontains=value) | - Q(identifier=value.strip()) - ).distinct() + + qs_filter = Q(name__icontains=value) + try: + qs_filter |= Q(identifier=int(value)) + except ValueError: + pass + return queryset.filter(qs_filter).distinct() def _has_primary_ip(self, queryset, name, value): params = Q(primary_ip4__isnull=False) | Q(primary_ip6__isnull=False) From 8143c6e03b6455e4d36c8f6da5d85d764c5d1eb3 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Thu, 6 Jul 2023 18:51:51 +0530 Subject: [PATCH 106/170] adds object change for contact assignment #13065 --- netbox/tenancy/models/contacts.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/netbox/tenancy/models/contacts.py b/netbox/tenancy/models/contacts.py index 440541b5f..1df5e3305 100644 --- a/netbox/tenancy/models/contacts.py +++ b/netbox/tenancy/models/contacts.py @@ -136,3 +136,8 @@ class ContactAssignment(ChangeLoggedModel): def get_absolute_url(self): return reverse('tenancy:contact', args=[self.contact.pk]) + + def to_objectchange(self, action): + objectchange = super().to_objectchange(action) + objectchange.related_object = self.object + return objectchange From 62bdb90f61c2b83148b3c0008d64e5b2168db036 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Thu, 6 Jul 2023 23:49:55 +0530 Subject: [PATCH 107/170] Adds copy content button (#12584) * adds copy content button #12499 * adds newline * Omit hash mark from target string * Clean up HTML element IDs --------- Co-authored-by: Jeremy Stretch --- netbox/ipam/tables/ip.py | 31 ++++++++++++++++-- netbox/project-static/dist/netbox.js | Bin 530632 -> 530613 bytes netbox/project-static/dist/netbox.js.map | Bin 450874 -> 450868 bytes netbox/project-static/src/clipboard.ts | 2 +- netbox/templates/core/datafile.html | 8 ++--- netbox/templates/dcim/device.html | 6 ++-- .../templates/dcim/virtualdevicecontext.html | 14 ++++++-- netbox/templates/users/api_token.html | 6 ++-- .../virtualization/virtualmachine.html | 6 ++-- netbox/users/tables.py | 4 +-- .../templates/builtins/copy_content.html | 3 ++ .../utilities/templatetags/builtins/tags.py | 12 +++++++ 12 files changed, 69 insertions(+), 23 deletions(-) create mode 100644 netbox/utilities/templates/builtins/copy_content.html diff --git a/netbox/ipam/tables/ip.py b/netbox/ipam/tables/ip.py index 86d1a3775..aff090f3a 100644 --- a/netbox/ipam/tables/ip.py +++ b/netbox/ipam/tables/ip.py @@ -19,14 +19,22 @@ __all__ = ( AVAILABLE_LABEL = mark_safe('Available') +AGGREGATE_COPY_BUTTON = """ +{% copy_content record.pk prefix="aggregate_" %} +""" + PREFIX_LINK = """ {% if record.pk %} - {{ record.prefix }} + {{ record.prefix }} {% else %} {{ record.prefix }} {% endif %} """ +PREFIX_COPY_BUTTON = """ +{% copy_content record.pk prefix="prefix_" %} +""" + PREFIX_LINK_WITH_DEPTH = """ {% load helpers %} {% if record.depth %} @@ -40,7 +48,7 @@ PREFIX_LINK_WITH_DEPTH = """ IPADDRESS_LINK = """ {% if record.pk %} - {{ record.address }} + {{ record.address }} {% elif perms.ipam.add_ipaddress %} {% if record.0 <= 65536 %}{{ record.0 }}{% else %}Many{% endif %} IP{{ record.0|pluralize }} available {% else %} @@ -48,6 +56,10 @@ IPADDRESS_LINK = """ {% endif %} """ +IPADDRESS_COPY_BUTTON = """ +{% copy_content record.pk prefix="ipaddress_" %} +""" + IPADDRESS_ASSIGN_LINK = """ {{ record }} """ @@ -99,7 +111,11 @@ class RIRTable(NetBoxTable): class AggregateTable(TenancyColumnsMixin, NetBoxTable): prefix = tables.Column( linkify=True, - verbose_name='Aggregate' + verbose_name='Aggregate', + attrs={ + # Allow the aggregate to be copied to the clipboard + 'a': {'id': lambda record: f"aggregate_{record.pk}"} + } ) date_added = tables.DateColumn( format="Y-m-d", @@ -116,6 +132,9 @@ class AggregateTable(TenancyColumnsMixin, NetBoxTable): tags = columns.TagColumn( url_name='ipam:aggregate_list' ) + actions = columns.ActionsColumn( + extra_buttons=AGGREGATE_COPY_BUTTON + ) class Meta(NetBoxTable.Meta): model = Aggregate @@ -242,6 +261,9 @@ class PrefixTable(TenancyColumnsMixin, NetBoxTable): tags = columns.TagColumn( url_name='ipam:prefix_list' ) + actions = columns.ActionsColumn( + extra_buttons=PREFIX_COPY_BUTTON + ) class Meta(NetBoxTable.Meta): model = Prefix @@ -348,6 +370,9 @@ class IPAddressTable(TenancyColumnsMixin, NetBoxTable): tags = columns.TagColumn( url_name='ipam:ipaddress_list' ) + actions = columns.ActionsColumn( + extra_buttons=IPADDRESS_COPY_BUTTON + ) class Meta(NetBoxTable.Meta): model = IPAddress diff --git a/netbox/project-static/dist/netbox.js b/netbox/project-static/dist/netbox.js index 9642d15851c5238db66f5a46ac81880ce2f10e1f..b62436d757a2b299e5bf0f33dcc4fbd67ec21e8c 100644 GIT binary patch delta 40 wcmX@HQDN&wg@zW!7N!>F7M2#)7Pc+yaw2TW`FSO&dF|#R?Ay&nI3}_H03@djzW@LL delta 59 zcmdnGQQ^c!g@zW!7N!>F7M2#)7Pc+yaw3u?`Pr#?N;*nOr6nc#d3wqD1(mwRsmVpD P?J6Sd+f_t3Cb9qkYoQZ* diff --git a/netbox/project-static/dist/netbox.js.map b/netbox/project-static/dist/netbox.js.map index f86d50148d419322456611b15cb949883cc3d9c2..ed3833f982a5d0f0d81c6bcf7e90a86aeb99d789 100644 GIT binary patch delta 34 qcmdmWNP5d5>4p}@7N!>F7M2#)Eo^FBY}rnZ&Kb_zjkwr2t^)wp@(S<( delta 40 wcmdmTNP5>H>4p}@7N!>F7M2#)Eo^FBJgJV3=}tPuPL9qQF57ju*f_2O03L1(wEzGB diff --git a/netbox/project-static/src/clipboard.ts b/netbox/project-static/src/clipboard.ts index a04acba39..46ca5e36c 100644 --- a/netbox/project-static/src/clipboard.ts +++ b/netbox/project-static/src/clipboard.ts @@ -2,7 +2,7 @@ import Clipboard from 'clipboard'; import { getElements } from './util'; export function initClipboard(): void { - for (const element of getElements('a.copy-token', 'button.copy-secret')) { + for (const element of getElements('a.copy-content')) { new Clipboard(element); } } diff --git a/netbox/templates/core/datafile.html b/netbox/templates/core/datafile.html index 3d79d17e2..785617ae5 100644 --- a/netbox/templates/core/datafile.html +++ b/netbox/templates/core/datafile.html @@ -39,9 +39,7 @@ Path {{ object.path }} - - - + {% copy_content "datafile_path" %} @@ -56,9 +54,7 @@ SHA256 Hash {{ object.hash }} - - - + {% copy_content "datafile_hash" %} diff --git a/netbox/templates/dcim/device.html b/netbox/templates/dcim/device.html index b0e67269c..df5209add 100644 --- a/netbox/templates/dcim/device.html +++ b/netbox/templates/dcim/device.html @@ -194,12 +194,13 @@ Primary IPv4 {% if object.primary_ip4 %} - {{ object.primary_ip4.address.ip }} + {{ object.primary_ip4.address.ip }} {% if object.primary_ip4.nat_inside %} (NAT for {{ object.primary_ip4.nat_inside.address.ip }}) {% elif object.primary_ip4.nat_outside.exists %} (NAT: {% for nat in object.primary_ip4.nat_outside.all %}{{ nat.address.ip }}{% if not forloop.last %}, {% endif %}{% endfor %}) {% endif %} + {% copy_content "primary_ip4" %} {% else %} {{ ''|placeholder }} {% endif %} @@ -209,12 +210,13 @@ Primary IPv6 {% if object.primary_ip6 %} - {{ object.primary_ip6.address.ip }} + {{ object.primary_ip6.address.ip }} {% if object.primary_ip6.nat_inside %} (NAT for {{ object.primary_ip6.nat_inside.address.ip }}) {% elif object.primary_ip6.nat_outside.exists %} (NAT: {% for nat in object.primary_ip6.nat_outside.all %}{{ nat.address.ip }}{% if not forloop.last %}, {% endif %}{% endfor %}) {% endif %} + {% copy_content "primary_ip6" %} {% else %} {{ ''|placeholder }} {% endif %} diff --git a/netbox/templates/dcim/virtualdevicecontext.html b/netbox/templates/dcim/virtualdevicecontext.html index d6e3e0c63..1caf05bd2 100644 --- a/netbox/templates/dcim/virtualdevicecontext.html +++ b/netbox/templates/dcim/virtualdevicecontext.html @@ -31,13 +31,23 @@ Primary IPv4 - {{ object.primary_ip4|linkify|placeholder }} + {% if object.primary_ip4 %} + {{ object.primary_ip4 }} + {% copy_content "primary_ip4" %} + {% else %} + + {% endif %} Primary IPv6 - {{ object.primary_ip6|linkify|placeholder }} + {% if object.primary_ip6 %} + {{ object.primary_ip6 }} + {% copy_content "primary_ip6" %} + {% else %} + + {% endif %} diff --git a/netbox/templates/users/api_token.html b/netbox/templates/users/api_token.html index 1a9296704..7fd6f064d 100644 --- a/netbox/templates/users/api_token.html +++ b/netbox/templates/users/api_token.html @@ -8,7 +8,7 @@
{% if not settings.ALLOW_TOKEN_RETRIEVAL %} {% endif %}
@@ -19,9 +19,7 @@ Key
- - - + {% copy_content "token_id" %}
{{ key }}
diff --git a/netbox/templates/virtualization/virtualmachine.html b/netbox/templates/virtualization/virtualmachine.html index 51fd8aa80..3d3b498ad 100644 --- a/netbox/templates/virtualization/virtualmachine.html +++ b/netbox/templates/virtualization/virtualmachine.html @@ -46,12 +46,13 @@ Primary IPv4 {% if object.primary_ip4 %} - {{ object.primary_ip4.address.ip }} + {{ object.primary_ip4.address.ip }} {% if object.primary_ip4.nat_inside %} (NAT for {{ object.primary_ip4.nat_inside.address.ip }}) {% elif object.primary_ip4.nat_outside.exists %} (NAT: {% for nat in object.primary_ip4.nat_outside.all %}{{ nat.address.ip }}{% if not forloop.last %}, {% endif %}{% endfor %}) {% endif %} + {% copy_content "primary_ip4" %} {% else %} {{ ''|placeholder }} {% endif %} @@ -61,12 +62,13 @@ Primary IPv6 {% if object.primary_ip6 %} - {{ object.primary_ip6.address.ip }} + {{ object.primary_ip6.address.ip }} {% if object.primary_ip6.nat_inside %} (NAT for {{ object.primary_ip6.nat_inside.address.ip }}) {% elif object.primary_ip6.nat_outside.exists %} (NAT: {% for nat in object.primary_ip6.nat_outside.all %}{{ nat.address.ip }}{% if not forloop.last %}, {% endif %}{% endfor %}) {% endif %} + {% copy_content "primary_ip6" %} {% else %} {{ ''|placeholder }} {% endif %} diff --git a/netbox/users/tables.py b/netbox/users/tables.py index 0f1484887..cea50b10f 100644 --- a/netbox/users/tables.py +++ b/netbox/users/tables.py @@ -12,9 +12,7 @@ ALLOWED_IPS = """{{ value|join:", " }}""" COPY_BUTTON = """ {% if settings.ALLOW_TOKEN_RETRIEVAL %} - - - + {% copy_content record.pk prefix="token_" color="success" %} {% endif %} """ diff --git a/netbox/utilities/templates/builtins/copy_content.html b/netbox/utilities/templates/builtins/copy_content.html new file mode 100644 index 000000000..9025a71a1 --- /dev/null +++ b/netbox/utilities/templates/builtins/copy_content.html @@ -0,0 +1,3 @@ + + + diff --git a/netbox/utilities/templatetags/builtins/tags.py b/netbox/utilities/templatetags/builtins/tags.py index f9fe5f4e3..35aec1000 100644 --- a/netbox/utilities/templatetags/builtins/tags.py +++ b/netbox/utilities/templatetags/builtins/tags.py @@ -6,6 +6,7 @@ from utilities.utils import dict_to_querydict __all__ = ( 'badge', 'checkmark', + 'copy_content', 'customfield_value', 'tag', ) @@ -79,6 +80,17 @@ def checkmark(value, show_false=True, true='Yes', false='No'): } +@register.inclusion_tag('builtins/copy_content.html') +def copy_content(target, prefix=None, color='primary'): + """ + Display a copy button to copy the content of a field. + """ + return { + 'target': f'#{prefix or ""}{target}', + 'color': f'btn-{color}' + } + + @register.inclusion_tag('builtins/htmx_table.html', takes_context=True) def htmx_table(context, viewname, return_url=None, **kwargs): """ From 7419a8e11285934cd6b90817307d147ee4c26688 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 6 Jul 2023 14:51:28 -0400 Subject: [PATCH 108/170] Closes #11738: Annotate utilization on VLAN groups (#13108) * Update serializers.py * Update vlans.py * Update vlans.py * Update vlangroup.html * Update vlans.py * Update vlans.py * Update serializers.py * adds db annotation to calculate utilization * optimize queries * merge fix * adds round function for utilization to limit decimal * fixed object view annotation * consolidated queryset for utilization * lint fixes * Renamed manager method to annotate_utilization() for consistency with other managers --------- Co-authored-by: Abhimanyu Saharan --- netbox/ipam/api/serializers.py | 3 ++- netbox/ipam/api/views.py | 6 +++--- netbox/ipam/models/vlans.py | 4 +++- netbox/ipam/querysets.py | 15 ++++++++++++++- netbox/ipam/tables/vlans.py | 8 ++++++-- netbox/ipam/views.py | 17 ++++++----------- netbox/templates/ipam/vlangroup.html | 4 ++++ 7 files changed, 38 insertions(+), 19 deletions(-) diff --git a/netbox/ipam/api/serializers.py b/netbox/ipam/api/serializers.py index 064452667..1501f16dc 100644 --- a/netbox/ipam/api/serializers.py +++ b/netbox/ipam/api/serializers.py @@ -218,12 +218,13 @@ class VLANGroupSerializer(NetBoxModelSerializer): scope_id = serializers.IntegerField(allow_null=True, required=False, default=None) scope = serializers.SerializerMethodField(read_only=True) vlan_count = serializers.IntegerField(read_only=True) + utilization = serializers.CharField(read_only=True) class Meta: model = VLANGroup fields = [ 'id', 'url', 'display', 'name', 'slug', 'scope_type', 'scope_id', 'scope', 'min_vid', 'max_vid', - 'description', 'tags', 'custom_fields', 'created', 'last_updated', 'vlan_count', + 'description', 'tags', 'custom_fields', 'created', 'last_updated', 'vlan_count', 'utilization' ] validators = [] diff --git a/netbox/ipam/api/views.py b/netbox/ipam/api/views.py index f432e0e6b..99b4c023d 100644 --- a/netbox/ipam/api/views.py +++ b/netbox/ipam/api/views.py @@ -1,5 +1,7 @@ from django.core.exceptions import ObjectDoesNotExist, PermissionDenied from django.db import transaction +from django.db.models import F +from django.db.models.functions import Round from django.shortcuts import get_object_or_404 from django_pglocks import advisory_lock from drf_spectacular.utils import extend_schema @@ -145,9 +147,7 @@ class FHRPGroupAssignmentViewSet(NetBoxModelViewSet): class VLANGroupViewSet(NetBoxModelViewSet): - queryset = VLANGroup.objects.annotate( - vlan_count=count_related(VLAN, 'group') - ).prefetch_related('tags') + queryset = VLANGroup.objects.annotate_utilization().prefetch_related('tags') serializer_class = serializers.VLANGroupSerializer filterset_class = filtersets.VLANGroupFilterSet diff --git a/netbox/ipam/models/vlans.py b/netbox/ipam/models/vlans.py index 7d4777da9..da504ded2 100644 --- a/netbox/ipam/models/vlans.py +++ b/netbox/ipam/models/vlans.py @@ -9,7 +9,7 @@ from django.utils.translation import gettext as _ from dcim.models import Interface from ipam.choices import * from ipam.constants import * -from ipam.querysets import VLANQuerySet +from ipam.querysets import VLANQuerySet, VLANGroupQuerySet from netbox.models import OrganizationalModel, PrimaryModel from virtualization.models import VMInterface @@ -63,6 +63,8 @@ class VLANGroup(OrganizationalModel): help_text=_('Highest permissible ID of a child VLAN') ) + objects = VLANGroupQuerySet.as_manager() + class Meta: ordering = ('name', 'pk') # Name may be non-unique constraints = ( diff --git a/netbox/ipam/querysets.py b/netbox/ipam/querysets.py index d6b23b843..39da0c3a2 100644 --- a/netbox/ipam/querysets.py +++ b/netbox/ipam/querysets.py @@ -1,8 +1,10 @@ from django.contrib.contenttypes.models import ContentType -from django.db.models import Count, OuterRef, Q, Subquery, Value +from django.db.models import Count, F, OuterRef, Q, Subquery, Value from django.db.models.expressions import RawSQL +from django.db.models.functions import Round from utilities.querysets import RestrictedQuerySet +from utilities.utils import count_related __all__ = ( 'ASNRangeQuerySet', @@ -54,6 +56,17 @@ class PrefixQuerySet(RestrictedQuerySet): ) +class VLANGroupQuerySet(RestrictedQuerySet): + + def annotate_utilization(self): + from .models import VLAN + + return self.annotate( + vlan_count=count_related(VLAN, 'group'), + utilization=Round(F('vlan_count') / (F('max_vid') - F('min_vid') + 1.0) * 100, 2) + ) + + class VLANQuerySet(RestrictedQuerySet): def get_for_device(self, device): diff --git a/netbox/ipam/tables/vlans.py b/netbox/ipam/tables/vlans.py index 6fa2cd2da..5d9828531 100644 --- a/netbox/ipam/tables/vlans.py +++ b/netbox/ipam/tables/vlans.py @@ -70,6 +70,10 @@ class VLANGroupTable(NetBoxTable): url_params={'group_id': 'pk'}, verbose_name='VLANs' ) + utilization = columns.UtilizationColumn( + orderable=False, + verbose_name='Utilization' + ) tags = columns.TagColumn( url_name='ipam:vlangroup_list' ) @@ -81,9 +85,9 @@ class VLANGroupTable(NetBoxTable): model = VLANGroup fields = ( 'pk', 'id', 'name', 'scope_type', 'scope', 'min_vid', 'max_vid', 'vlan_count', 'slug', 'description', - 'tags', 'created', 'last_updated', 'actions', + 'tags', 'created', 'last_updated', 'actions', 'utilization', ) - default_columns = ('pk', 'name', 'scope_type', 'scope', 'vlan_count', 'description') + default_columns = ('pk', 'name', 'scope_type', 'scope', 'vlan_count', 'utilization', 'description') # diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index 6efaef8ea..32badd2d5 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -1,6 +1,7 @@ from django.contrib.contenttypes.models import ContentType -from django.db.models import Prefetch +from django.db.models import F, Prefetch from django.db.models.expressions import RawSQL +from django.db.models.functions import Round from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse from django.utils.translation import gettext as _ @@ -882,9 +883,7 @@ class IPAddressRelatedIPsView(generic.ObjectChildrenView): # class VLANGroupListView(generic.ObjectListView): - queryset = VLANGroup.objects.annotate( - vlan_count=count_related(VLAN, 'group') - ) + queryset = VLANGroup.objects.annotate_utilization().prefetch_related('tags') filterset = filtersets.VLANGroupFilterSet filterset_form = forms.VLANGroupFilterForm table = tables.VLANGroupTable @@ -892,7 +891,7 @@ class VLANGroupListView(generic.ObjectListView): @register_model_view(VLANGroup) class VLANGroupView(generic.ObjectView): - queryset = VLANGroup.objects.all() + queryset = VLANGroup.objects.annotate_utilization().prefetch_related('tags') def get_extra_context(self, request, instance): related_models = ( @@ -934,18 +933,14 @@ class VLANGroupBulkImportView(generic.BulkImportView): class VLANGroupBulkEditView(generic.BulkEditView): - queryset = VLANGroup.objects.annotate( - vlan_count=count_related(VLAN, 'group') - ) + queryset = VLANGroup.objects.annotate_utilization().prefetch_related('tags') filterset = filtersets.VLANGroupFilterSet table = tables.VLANGroupTable form = forms.VLANGroupBulkEditForm class VLANGroupBulkDeleteView(generic.BulkDeleteView): - queryset = VLANGroup.objects.annotate( - vlan_count=count_related(VLAN, 'group') - ) + queryset = VLANGroup.objects.annotate_utilization().prefetch_related('tags') filterset = filtersets.VLANGroupFilterSet table = tables.VLANGroupTable diff --git a/netbox/templates/ipam/vlangroup.html b/netbox/templates/ipam/vlangroup.html index 2917536be..e474cbd84 100644 --- a/netbox/templates/ipam/vlangroup.html +++ b/netbox/templates/ipam/vlangroup.html @@ -42,6 +42,10 @@ Permitted VIDs {{ object.min_vid }} - {{ object.max_vid }} + + Utilization + {% utilization_graph object.utilization %} +
From ecb4a084cc0415421d5030e66655aeb32ed6ea29 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 6 Jul 2023 14:54:37 -0400 Subject: [PATCH 109/170] Change log for #11738, #12499, #12579, #12617, #13047, #13065, #13092, #13100 --- docs/release-notes/version-3.5.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/release-notes/version-3.5.md b/docs/release-notes/version-3.5.md index beb0a1991..194d44922 100644 --- a/docs/release-notes/version-3.5.md +++ b/docs/release-notes/version-3.5.md @@ -4,13 +4,18 @@ ### Enhancements +* [#11738](https://github.com/netbox-community/netbox/issues/11738) - Annotate VLAN group utilization +* [#12499](https://github.com/netbox-community/netbox/issues/12499) - Add "copy to clipboard" buttons in UI for IP addresses * [#12945](https://github.com/netbox-community/netbox/issues/12945) - Add 100GE QSFP-DD interface type * [#12955](https://github.com/netbox-community/netbox/issues/12955) - Include additional contact details on contact assignments table +* [#13065](https://github.com/netbox-community/netbox/issues/13065) - Associate contact assignments with their objects in the change log ### Bug Fixes * [#11335](https://github.com/netbox-community/netbox/issues/11335) - Exclude stale content types when retrieving changelog records * [#12533](https://github.com/netbox-community/netbox/issues/12533) - Fix REST API validation of null values for several interface attributes +* [#12579](https://github.com/netbox-community/netbox/issues/12579) - Fix exception when clicking "create and add another" to add a cable +* [#12617](https://github.com/netbox-community/netbox/issues/12617) - Populate prechange snapshot on parent object when assigning/removing primary IP address * [#12760](https://github.com/netbox-community/netbox/issues/12760) - Avoid rendering partial HTMX responses when restoring browser tabs * [#12842](https://github.com/netbox-community/netbox/issues/12842) - Improve handling of exceptions when loading reports * [#12849](https://github.com/netbox-community/netbox/issues/12849) - Fix LDAP group permissions assignment for API clients @@ -24,6 +29,9 @@ * [#12983](https://github.com/netbox-community/netbox/issues/12983) - Avoid erroneously clearing many-to-many assignments during bulk edit * [#12989](https://github.com/netbox-community/netbox/issues/12989) - Fix bulk import of tags for device & module types * [#13011](https://github.com/netbox-community/netbox/issues/13011) - Do not escape commas when rendering custom links +* [#13047](https://github.com/netbox-community/netbox/issues/13047) - Correct ASN count under ASN ranges list +* [#13092](https://github.com/netbox-community/netbox/issues/13092) - Allow nullifying power port max & allocated draw values during bulk edit +* [#13100](https://github.com/netbox-community/netbox/issues/13100) - Fix ValueError exception when searching for virtual device context for non-numeric values --- From 74fb707ad3110a3da8cf943520b961118c03264d Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Fri, 7 Jul 2023 00:43:02 +0530 Subject: [PATCH 110/170] adds config_template to device serializer #13056 --- netbox/dcim/api/serializers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index 2f854d3e4..9cf30fdd4 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -698,7 +698,8 @@ class DeviceWithConfigContextSerializer(DeviceSerializer): 'id', 'url', 'display', 'name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'asset_tag', 'site', 'location', 'rack', 'position', 'face', 'parent_device', 'status', 'airflow', 'primary_ip', 'primary_ip4', 'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'description', - 'comments', 'local_context_data', 'tags', 'custom_fields', 'config_context', 'created', 'last_updated', + 'comments', 'local_context_data', 'tags', 'custom_fields', 'config_context', 'config_template', + 'created', 'last_updated', ] @extend_schema_field(serializers.JSONField(allow_null=True)) From 53a75a3dd78909c8586bea5baea74eb9f1214b76 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 6 Jul 2023 16:20:14 -0400 Subject: [PATCH 111/170] Release v3.5.5 --- .github/ISSUE_TEMPLATE/bug_report.yaml | 2 +- .github/ISSUE_TEMPLATE/feature_request.yaml | 2 +- docs/release-notes/version-3.5.md | 3 ++- netbox/netbox/settings.py | 2 +- requirements.txt | 16 ++++++++-------- 5 files changed, 13 insertions(+), 12 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index b3dd583ca..a43f754bf 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -14,7 +14,7 @@ body: attributes: label: NetBox version description: What version of NetBox are you currently running? - placeholder: v3.5.4 + placeholder: v3.5.5 validations: required: true - type: dropdown diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml index bd93001e7..c79b2fe9c 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yaml +++ b/.github/ISSUE_TEMPLATE/feature_request.yaml @@ -14,7 +14,7 @@ body: attributes: label: NetBox version description: What version of NetBox are you currently running? - placeholder: v3.5.4 + placeholder: v3.5.5 validations: required: true - type: dropdown diff --git a/docs/release-notes/version-3.5.md b/docs/release-notes/version-3.5.md index 194d44922..6087f895f 100644 --- a/docs/release-notes/version-3.5.md +++ b/docs/release-notes/version-3.5.md @@ -1,6 +1,6 @@ # NetBox v3.5 -## v3.5.5 (FUTURE) +## v3.5.5 (2023-07-06) ### Enhancements @@ -30,6 +30,7 @@ * [#12989](https://github.com/netbox-community/netbox/issues/12989) - Fix bulk import of tags for device & module types * [#13011](https://github.com/netbox-community/netbox/issues/13011) - Do not escape commas when rendering custom links * [#13047](https://github.com/netbox-community/netbox/issues/13047) - Correct ASN count under ASN ranges list +* [#13056](https://github.com/netbox-community/netbox/issues/13056) - Add `config_template` field to device API serializer * [#13092](https://github.com/netbox-community/netbox/issues/13092) - Allow nullifying power port max & allocated draw values during bulk edit * [#13100](https://github.com/netbox-community/netbox/issues/13100) - Fix ValueError exception when searching for virtual device context for non-numeric values diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 31363144f..8a7133c07 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -25,7 +25,7 @@ from netbox.constants import RQ_QUEUE_DEFAULT, RQ_QUEUE_HIGH, RQ_QUEUE_LOW # Environment setup # -VERSION = '3.5.5-dev' +VERSION = '3.5.5' # Hostname HOSTNAME = platform.node() diff --git a/requirements.txt b/requirements.txt index e6e56ce56..df729d30f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ bleach==6.0.0 -boto3==1.26.156 -Django==4.1.9 +boto3==1.27.1 +Django==4.1.10 django-cors-headers==4.1.0 django-debug-toolbar==4.1.0 django-filter==23.2 @@ -11,25 +11,25 @@ django-prometheus==2.3.1 django-redis==5.3.0 django-rich==1.6.0 django-rq==2.8.1 -django-tables2==2.5.3 +django-tables2==2.6.0 django-taggit==4.0.0 django-timezone-field==5.1 djangorestframework==3.14.0 -drf-spectacular==0.26.2 -drf-spectacular-sidecar==2023.6.1 +drf-spectacular==0.26.3 +drf-spectacular-sidecar==2023.7.1 dulwich==0.21.5 feedparser==6.0.10 graphene-django==3.0.0 gunicorn==20.1.0 Jinja2==3.1.2 Markdown==3.3.7 -mkdocs-material==9.1.16 +mkdocs-material==9.1.18 mkdocstrings[python-legacy]==0.22.0 netaddr==0.8.0 -Pillow==9.5.0 +Pillow==10.0.0 psycopg2-binary==2.9.6 PyYAML==6.0 -sentry-sdk==1.25.1 +sentry-sdk==1.27.1 social-auth-app-django==5.2.0 social-auth-core[openidconnect]==4.4.2 svgwrite==1.4.3 From 63c33ff4bedd29986e189fc35dd5cfd1018c0554 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 6 Jul 2023 16:40:11 -0400 Subject: [PATCH 112/170] PRVB --- docs/release-notes/version-3.5.md | 4 ++++ netbox/netbox/settings.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/version-3.5.md b/docs/release-notes/version-3.5.md index 6087f895f..678966bc5 100644 --- a/docs/release-notes/version-3.5.md +++ b/docs/release-notes/version-3.5.md @@ -1,5 +1,9 @@ # NetBox v3.5 +## v3.5.6 (FUTURE) + +--- + ## v3.5.5 (2023-07-06) ### Enhancements diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 8a7133c07..c260a3a56 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -25,7 +25,7 @@ from netbox.constants import RQ_QUEUE_DEFAULT, RQ_QUEUE_HIGH, RQ_QUEUE_LOW # Environment setup # -VERSION = '3.5.5' +VERSION = '3.5.6-dev' # Hostname HOSTNAME = platform.node() From bc7678c7160ee1746287b8d8ba21134e01c6c499 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Fri, 7 Jul 2023 12:16:19 +0530 Subject: [PATCH 113/170] fixes content type lookups when db is uninitialized #13116 --- netbox/extras/querysets.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/netbox/extras/querysets.py b/netbox/extras/querysets.py index 2e6f93b93..7b71fa656 100644 --- a/netbox/extras/querysets.py +++ b/netbox/extras/querysets.py @@ -2,6 +2,7 @@ from django.apps import apps from django.contrib.contenttypes.models import ContentType from django.contrib.postgres.aggregates import JSONBAgg from django.db.models import OuterRef, Subquery, Q +from django.db.utils import ProgrammingError from extras.models.tags import TaggedItem from utilities.query_functions import EmptyGroupByJSONBAgg @@ -160,7 +161,13 @@ class ObjectChangeQuerySet(RestrictedQuerySet): def valid_models(self): # Exclude any change records which refer to an instance of a model that's no longer installed. This # can happen when a plugin is removed but its data remains in the database, for example. + try: + content_types = ContentType.objects.get_for_models(*apps.get_models()).values() + except ProgrammingError: + # Handle the case where the database schema has not yet been initialized + content_types = ContentType.objects.none() + content_type_ids = set( - ct.pk for ct in ContentType.objects.get_for_models(*apps.get_models()).values() + ct.pk for ct in content_types ) return self.filter(changed_object_type_id__in=content_type_ids) From cab7b76220a2379450f20831e3093ed513fde9a8 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Mon, 10 Jul 2023 20:00:51 +0530 Subject: [PATCH 114/170] Fixes form rendering when scheduling_enabled is disabled (#13123) * fixes form rendering when scheduling_enabled is disabled #13096 * Remove requires_input property from BaseScript; render form consistently --------- Co-authored-by: Jeremy Stretch --- netbox/extras/forms/scripts.py | 7 ------- netbox/extras/scripts.py | 7 ++++++- netbox/templates/extras/script.html | 16 +++++----------- 3 files changed, 11 insertions(+), 19 deletions(-) diff --git a/netbox/extras/forms/scripts.py b/netbox/extras/forms/scripts.py index 05febaa6f..19a7878e1 100644 --- a/netbox/extras/forms/scripts.py +++ b/netbox/extras/forms/scripts.py @@ -56,10 +56,3 @@ class ScriptForm(BootstrapMixin, forms.Form): self.cleaned_data['_schedule_at'] = local_now() return self.cleaned_data - - @property - def requires_input(self): - """ - A boolean indicating whether the form requires user input (ignore the built-in fields). - """ - return bool(len(self.fields) > 3) diff --git a/netbox/extras/scripts.py b/netbox/extras/scripts.py index cebc57af4..9fa31db31 100644 --- a/netbox/extras/scripts.py +++ b/netbox/extras/scripts.py @@ -366,7 +366,7 @@ class BaseScript: if self.fieldsets: fieldsets.extend(self.fieldsets) else: - fields = (name for name, _ in self._get_vars().items()) + fields = list(name for name, _ in self._get_vars().items()) fieldsets.append(('Script Data', fields)) # Append the default fieldset if defined in the Meta class @@ -390,6 +390,11 @@ class BaseScript: # Set initial "commit" checkbox state based on the script's Meta parameter form.fields['_commit'].initial = self.commit_default + # Hide fields if scheduling has been disabled + if not self.scheduling_enabled: + form.fields['_schedule_at'].widget = forms.HiddenInput() + form.fields['_interval'].widget = forms.HiddenInput() + return form # Logging diff --git a/netbox/templates/extras/script.html b/netbox/templates/extras/script.html index b7ef2a908..b515e8a99 100644 --- a/netbox/templates/extras/script.html +++ b/netbox/templates/extras/script.html @@ -15,9 +15,9 @@
{% csrf_token %}
- {% if form.requires_input %} - {# Render grouped fields according to declared fieldsets #} - {% for group, fields in script.get_fieldsets %} + {# Render grouped fields according to declared fieldsets #} + {% for group, fields in script.get_fieldsets %} + {% if fields %}
{{ group }}
@@ -28,14 +28,8 @@ {% endwith %} {% endfor %}
- {% endfor %} - {% else %} -
- - This script does not require any input to run. -
- {% render_form form %} - {% endif %} + {% endif %} + {% endfor %}
Cancel From 73348ee43529031582a81d765a829bf0d4c69bd8 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 10 Jul 2023 13:53:31 -0400 Subject: [PATCH 115/170] Fixes #13105: Avoid exception when attempting to allocate next available IP address from prefix marked as utilized --- netbox/ipam/models/ip.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/ipam/models/ip.py b/netbox/ipam/models/ip.py index 015f9220c..00dcf8422 100644 --- a/netbox/ipam/models/ip.py +++ b/netbox/ipam/models/ip.py @@ -406,7 +406,7 @@ class Prefix(GetAvailablePrefixesMixin, PrimaryModel): Return all available IPs within this prefix as an IPSet. """ if self.mark_utilized: - return list() + return netaddr.IPSet() prefix = netaddr.IPSet(self.prefix) child_ips = netaddr.IPSet([ip.address.ip for ip in self.get_child_ips()]) From f6fcf776a43fac39811e401b23dc936856d69234 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 10 Jul 2023 14:13:45 -0400 Subject: [PATCH 116/170] Fixes #13061: Fix display of last result for scripts & reports with a custom name defined --- netbox/templates/extras/report_list.html | 2 +- netbox/templates/extras/script_list.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/netbox/templates/extras/report_list.html b/netbox/templates/extras/report_list.html index 7867fdbbe..e1efec755 100644 --- a/netbox/templates/extras/report_list.html +++ b/netbox/templates/extras/report_list.html @@ -52,7 +52,7 @@ {% with jobs=module.get_latest_jobs %} {% for report_name, report in module.reports.items %} - {% with last_job=jobs|get_key:report.name %} + {% with last_job=jobs|get_key:report.class_name %} {{ report.name }} diff --git a/netbox/templates/extras/script_list.html b/netbox/templates/extras/script_list.html index 9a67e2b10..0f32ba0b9 100644 --- a/netbox/templates/extras/script_list.html +++ b/netbox/templates/extras/script_list.html @@ -61,7 +61,7 @@ {{ script_class.Meta.description|markdown|placeholder }} - {% with last_result=jobs|get_key:script_class.name %} + {% with last_result=jobs|get_key:script_class.class_name %} {% if last_result %} {{ last_result.created|annotated_date }} From 252cc37f97c63a6ed0cc5693aa6f699c246d6d9d Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 10 Jul 2023 14:39:40 -0400 Subject: [PATCH 117/170] Changelog for #13061, #13096, #13105, #13116 --- docs/release-notes/version-3.5.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/release-notes/version-3.5.md b/docs/release-notes/version-3.5.md index 678966bc5..4d8632410 100644 --- a/docs/release-notes/version-3.5.md +++ b/docs/release-notes/version-3.5.md @@ -2,6 +2,13 @@ ## v3.5.6 (FUTURE) +### Bug Fixes + +* [#13061](https://github.com/netbox-community/netbox/issues/13061) - Fix display of last result for scripts & reports with a custom name defined +* [#13096](https://github.com/netbox-community/netbox/issues/13096) - Hide scheduling fields for all scripts with scheduling disabled +* [#13105](https://github.com/netbox-community/netbox/issues/13105) - Fix exception when attempting to allocate next available IP address from prefix marked as utilized +* [#13116](https://github.com/netbox-community/netbox/issues/13116) - Catch ProgrammingError exception when starting NetBox without pre-populated content types + --- ## v3.5.5 (2023-07-06) From 1c30a44b4e332e30006162e85a06ee2da5cccbd2 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 10 Jul 2023 16:35:53 -0400 Subject: [PATCH 118/170] Release v3.5.6 --- .github/ISSUE_TEMPLATE/bug_report.yaml | 2 +- .github/ISSUE_TEMPLATE/feature_request.yaml | 2 +- docs/release-notes/version-3.5.md | 2 +- netbox/netbox/settings.py | 2 +- requirements.txt | 8 ++++---- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index a43f754bf..8cb548de2 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -14,7 +14,7 @@ body: attributes: label: NetBox version description: What version of NetBox are you currently running? - placeholder: v3.5.5 + placeholder: v3.5.6 validations: required: true - type: dropdown diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml index c79b2fe9c..df931c77b 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yaml +++ b/.github/ISSUE_TEMPLATE/feature_request.yaml @@ -14,7 +14,7 @@ body: attributes: label: NetBox version description: What version of NetBox are you currently running? - placeholder: v3.5.5 + placeholder: v3.5.6 validations: required: true - type: dropdown diff --git a/docs/release-notes/version-3.5.md b/docs/release-notes/version-3.5.md index 4d8632410..e455914d5 100644 --- a/docs/release-notes/version-3.5.md +++ b/docs/release-notes/version-3.5.md @@ -1,6 +1,6 @@ # NetBox v3.5 -## v3.5.6 (FUTURE) +## v3.5.6 (2023-07-10) ### Bug Fixes diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index c260a3a56..0b158b2bf 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -25,7 +25,7 @@ from netbox.constants import RQ_QUEUE_DEFAULT, RQ_QUEUE_HIGH, RQ_QUEUE_LOW # Environment setup # -VERSION = '3.5.6-dev' +VERSION = '3.5.6' # Hostname HOSTNAME = platform.node() diff --git a/requirements.txt b/requirements.txt index df729d30f..3750e724b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ bleach==6.0.0 -boto3==1.27.1 +boto3==1.28.1 Django==4.1.10 -django-cors-headers==4.1.0 +django-cors-headers==4.2.0 django-debug-toolbar==4.1.0 django-filter==23.2 django-graphiql-debug-toolbar==0.2.0 @@ -9,7 +9,7 @@ django-mptt==0.14 django-pglocks==1.0.4 django-prometheus==2.3.1 django-redis==5.3.0 -django-rich==1.6.0 +django-rich==1.7.0 django-rq==2.8.1 django-tables2==2.6.0 django-taggit==4.0.0 @@ -29,7 +29,7 @@ netaddr==0.8.0 Pillow==10.0.0 psycopg2-binary==2.9.6 PyYAML==6.0 -sentry-sdk==1.27.1 +sentry-sdk==1.28.0 social-auth-app-django==5.2.0 social-auth-core[openidconnect]==4.4.2 svgwrite==1.4.3 From 0f0cf683c44f058a72a50fa90b889ab3fc6bea08 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 10 Jul 2023 16:55:17 -0400 Subject: [PATCH 119/170] PRVB --- docs/release-notes/version-3.5.md | 4 ++++ netbox/netbox/settings.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/version-3.5.md b/docs/release-notes/version-3.5.md index e455914d5..db301c55f 100644 --- a/docs/release-notes/version-3.5.md +++ b/docs/release-notes/version-3.5.md @@ -1,5 +1,9 @@ # NetBox v3.5 +## v3.5.7 (FUTURE) + +--- + ## v3.5.6 (2023-07-10) ### Bug Fixes diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 0b158b2bf..7d2da2996 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -25,7 +25,7 @@ from netbox.constants import RQ_QUEUE_DEFAULT, RQ_QUEUE_HIGH, RQ_QUEUE_LOW # Environment setup # -VERSION = '3.5.6' +VERSION = '3.5.7-dev' # Hostname HOSTNAME = platform.node() From 8b8adfbbbb4667dab4742c6ad66296670db42fe6 Mon Sep 17 00:00:00 2001 From: kkthxbye-code Date: Mon, 24 Jul 2023 12:09:44 +0200 Subject: [PATCH 120/170] Use class_name instead of name to get script results --- netbox/extras/api/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/netbox/extras/api/views.py b/netbox/extras/api/views.py index f4b5a1433..77930e2a4 100644 --- a/netbox/extras/api/views.py +++ b/netbox/extras/api/views.py @@ -303,7 +303,7 @@ class ScriptViewSet(ViewSet): # Attach Job objects to each script (if any) for script in script_list: - script.result = results.get(script.name, None) + script.result = results.get(script.class_name, None) serializer = serializers.ScriptSerializer(script_list, many=True, context={'request': request}) @@ -314,7 +314,7 @@ class ScriptViewSet(ViewSet): object_type = ContentType.objects.get(app_label='extras', model='scriptmodule') script.result = Job.objects.filter( object_type=object_type, - name=script.name, + name=script.class_name, status__in=JobStatusChoices.TERMINAL_STATE_CHOICES ).first() serializer = serializers.ScriptDetailSerializer(script, context={'request': request}) From d075e7a66a26a6ca9154fb145b224472aaa5cd86 Mon Sep 17 00:00:00 2001 From: Fabian Geisberger Date: Sun, 23 Jul 2023 17:32:41 -0400 Subject: [PATCH 121/170] Fixes #13237 - Allow unauthenticated api access to content-types. --- netbox/extras/api/views.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/netbox/extras/api/views.py b/netbox/extras/api/views.py index 77930e2a4..a7792803b 100644 --- a/netbox/extras/api/views.py +++ b/netbox/extras/api/views.py @@ -6,7 +6,6 @@ from rest_framework import status from rest_framework.decorators import action from rest_framework.exceptions import PermissionDenied from rest_framework.generics import RetrieveUpdateDestroyAPIView -from rest_framework.permissions import IsAuthenticated from rest_framework.renderers import JSONRenderer from rest_framework.response import Response from rest_framework.routers import APIRootView @@ -381,7 +380,7 @@ class ContentTypeViewSet(ReadOnlyModelViewSet): """ Read-only list of ContentTypes. Limit results to ContentTypes pertinent to NetBox objects. """ - permission_classes = (IsAuthenticated,) + permission_classes = [IsAuthenticatedOrLoginNotRequired] queryset = ContentType.objects.order_by('app_label', 'model') serializer_class = serializers.ContentTypeSerializer filterset_class = filtersets.ContentTypeFilterSet From 5a3d46ac8dea930edb5e01ef4684878ff46d8526 Mon Sep 17 00:00:00 2001 From: kkthxbye-code Date: Sun, 23 Jul 2023 11:06:02 +0200 Subject: [PATCH 122/170] Remove vlan_group from nullable fields in InterfaceBulkEditForm --- netbox/dcim/forms/bulk_edit.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/netbox/dcim/forms/bulk_edit.py b/netbox/dcim/forms/bulk_edit.py index f1abdef1d..a16de0b75 100644 --- a/netbox/dcim/forms/bulk_edit.py +++ b/netbox/dcim/forms/bulk_edit.py @@ -1259,8 +1259,8 @@ class InterfaceBulkEditForm( ) nullable_fields = ( 'module', 'label', 'parent', 'bridge', 'lag', 'speed', 'duplex', 'mac_address', 'wwn', 'vdcs', 'mtu', 'description', - 'poe_mode', 'poe_type', 'mode', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', - 'vlan_group', 'untagged_vlan', 'tagged_vlans', 'vrf', 'wireless_lans' + 'poe_mode', 'poe_type', 'mode', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'untagged_vlan', + 'tagged_vlans', 'vrf', 'wireless_lans' ) def __init__(self, *args, **kwargs): From 0ab3f979e0451444a4ce42330bf43c952d940402 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Fri, 28 Jul 2023 01:29:41 +0530 Subject: [PATCH 123/170] Adds faster polling for scripts and reports (#13202) * adds faster polling for scripts and reports #13097 * changes as per review --- netbox/templates/extras/report_result.html | 2 +- netbox/templates/extras/script_result.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/netbox/templates/extras/report_result.html b/netbox/templates/extras/report_result.html index 9358af364..f6a9a6398 100644 --- a/netbox/templates/extras/report_result.html +++ b/netbox/templates/extras/report_result.html @@ -4,7 +4,7 @@ {% block content-wrapper %}
-
+
{% include 'extras/htmx/report_result.html' %}
diff --git a/netbox/templates/extras/script_result.html b/netbox/templates/extras/script_result.html index 4dfd7482a..436ba7354 100644 --- a/netbox/templates/extras/script_result.html +++ b/netbox/templates/extras/script_result.html @@ -47,7 +47,7 @@
-
+
{% include 'extras/htmx/script_result.html' %}
From bba4fe437cc6b577d9d4f5973688b617b819b46d Mon Sep 17 00:00:00 2001 From: Alef Burzmali Date: Wed, 12 Jul 2023 23:05:35 +0200 Subject: [PATCH 124/170] Update the install doc for PostgreSQL 15 Fixes #12768 --- docs/installation/1-postgresql.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/installation/1-postgresql.md b/docs/installation/1-postgresql.md index 1fccd0270..f2e1ea356 100644 --- a/docs/installation/1-postgresql.md +++ b/docs/installation/1-postgresql.md @@ -55,6 +55,9 @@ Within the shell, enter the following commands to create the database and user ( CREATE DATABASE netbox; CREATE USER netbox WITH PASSWORD 'J5brHrAXFLQSif0K'; ALTER DATABASE netbox OWNER TO netbox; +-- the next two commands are needed on PostgreSQL 15 and later +\connect netbox; +GRANT CREATE ON SCHEMA public TO netbox; ``` !!! danger "Use a strong password" From 1d52627f71c230982b8b5a1c0806c7d050ec4955 Mon Sep 17 00:00:00 2001 From: Roger Miret Date: Wed, 12 Jul 2023 20:54:06 +0200 Subject: [PATCH 125/170] Update ipam.md 100.64.16.9/24 isn't a valid CIDR --- docs/features/ipam.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features/ipam.md b/docs/features/ipam.md index d67645b17..3cbe4319d 100644 --- a/docs/features/ipam.md +++ b/docs/features/ipam.md @@ -38,7 +38,7 @@ An example hierarchy might look like this: * 100.64.16.1/24 (address) * 100.64.16.2/24 (address) * 100.64.16.3/24 (address) - * 100.64.16.9/24 (prefix) + * 100.64.19.0/24 (prefix) * 100.64.32.0/20 (prefix) * 100.64.32.1/24 (address) * 100.64.32.10-99/24 (range) From 0276f29067c0ea4b9cb4c5712240d89ae0593690 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Tue, 18 Jul 2023 15:30:49 +0530 Subject: [PATCH 126/170] adds sensitive_parameters to DataBackend #12625 --- netbox/core/data_backends.py | 3 +++ netbox/templates/core/datasource.html | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/netbox/core/data_backends.py b/netbox/core/data_backends.py index 6cc534774..43e6f4e79 100644 --- a/netbox/core/data_backends.py +++ b/netbox/core/data_backends.py @@ -41,6 +41,7 @@ def register_backend(name): class DataBackend: parameters = {} + sensitive_parameters = [] def __init__(self, url, **kwargs): self.url = url @@ -86,6 +87,7 @@ class GitBackend(DataBackend): widget=forms.TextInput(attrs={'class': 'form-control'}) ) } + sensitive_parameters = ['password'] @contextmanager def fetch(self): @@ -135,6 +137,7 @@ class S3Backend(DataBackend): widget=forms.TextInput(attrs={'class': 'form-control'}) ), } + sensitive_parameters = ['aws_secret_access_key'] REGION_REGEX = r's3\.([a-z0-9-]+)\.amazonaws\.com' diff --git a/netbox/templates/core/datasource.html b/netbox/templates/core/datasource.html index c69569358..992edb2d1 100644 --- a/netbox/templates/core/datasource.html +++ b/netbox/templates/core/datasource.html @@ -88,7 +88,11 @@ {% for name, field in object.get_backend.parameters.items %} {{ field.label }} - {{ object.parameters|get_key:name|placeholder }} + {% if name in object.get_backend.sensitive_parameters and not perms.core.change_datasource %} + ******** + {% else %} + {{ object.parameters|get_key:name|placeholder }} + {% endif %} {% empty %} From 4b2922312a3aa58d9c63e5b95577e8fd841b4a45 Mon Sep 17 00:00:00 2001 From: kkthxbye-code Date: Sun, 23 Jul 2023 23:08:08 +0200 Subject: [PATCH 127/170] Allow the align property on th and td and add CSS rules for overriding text-alignment --- netbox/project-static/dist/netbox-dark.css | Bin 375390 -> 375591 bytes netbox/project-static/dist/netbox-light.css | Bin 232699 -> 232798 bytes netbox/project-static/dist/netbox-print.css | Bin 727424 -> 727883 bytes netbox/project-static/styles/netbox.scss | 12 ++++++++++++ netbox/utilities/utils.py | 2 ++ 5 files changed, 14 insertions(+) diff --git a/netbox/project-static/dist/netbox-dark.css b/netbox/project-static/dist/netbox-dark.css index 11110069e11515e95b985a5bb8cf2eb818eb395d..2aa24b72c1b05dc2fdcadd498f5c2a8817b85ee2 100644 GIT binary patch delta 111 zcmccjMQr&uv4$4L7N!>F7M3lng4_5@GNKc6GSl;Hb5hevVyma`o55lv2p3FF%_~VQ u0t(BEGMUK3g^M!NGk}UqQY%Vy!Md$LBGU^)nAN5yY-5$!uDy-bm<0e}V<~I^ delta 30 mcmZ4fP3+zmv4$4L7N!>F7M3lng4?F|Z)X+T{$U%dIST;J5DZHI diff --git a/netbox/project-static/dist/netbox-light.css b/netbox/project-static/dist/netbox-light.css index 8a3c83af9b64aa1f2bd4a5ffab0b40139bf9dc80..ffdd83285e235e4f723f8f46937a2c0ab5033570 100644 GIT binary patch delta 120 zcmex8lkeUvzJ?aY7N#xCOkGJO8PSP3ndy19IjLzSvDGE16(zc00V|L|EnGS|HLoPK Z2t@%z2%)4XGd%;<2(ZX@#xCYxOaNOOFVX-2 delta 26 icmcaNi|_YLzJ?aY7N#xCOkL9_bTLb9ukT{M&jbLLUJ7Xd diff --git a/netbox/project-static/dist/netbox-print.css b/netbox/project-static/dist/netbox-print.css index ef2682e0a33fc5e0ede356d8f29c499cb48e87b2..b492e4d1dbd0bfe6538e68d291f6d78972bb52ea 100644 GIT binary patch delta 217 zcmZqJtaEysPD2Z03sVbo3rh=Y3tJ0&3r7oQ3)dFzxBMI>8PSP3ndy1c@10}SMByn< z7x=)%!3UMM%}GrwiLIVot*y!p(P5jMnpZNt@E)@oFU$~#(8agR>VhajsYS8X(_h=N ws3I(wbAnY1g{K0xP7Y>WQD%Au(7uw?iV|J01Fb+J(?2d^6W{)xpL-%F0K48&(*OVf delta 37 tcmX@TO{ZbAPD2Z03sVbo3rh=Y3tJ0&3r7oQ3)dFzxBT082y##61ONit49)-m diff --git a/netbox/project-static/styles/netbox.scss b/netbox/project-static/styles/netbox.scss index b294d67bd..94fddc32c 100644 --- a/netbox/project-static/styles/netbox.scss +++ b/netbox/project-static/styles/netbox.scss @@ -1002,6 +1002,18 @@ div.card-overlay { padding: 8px; } +th[align="left"] { + text-align: left; +} + +th[align="center"] { + text-align: center; +} + +th[align="right"] { + text-align: right; +} + /* Markdown widget */ .markdown-widget { .nav-link { diff --git a/netbox/utilities/utils.py b/netbox/utilities/utils.py index 114397dae..9524e242c 100644 --- a/netbox/utilities/utils.py +++ b/netbox/utilities/utils.py @@ -519,6 +519,8 @@ def clean_html(html, schemes): "h1": ["id"], "h2": ["id"], "h3": ["id"], "h4": ["id"], "h5": ["id"], "h6": ["id"], "a": ["href", "title"], "img": ["src", "title", "alt"], + "th": ["align"], + "td": ["align"], } return bleach.clean( From 3e12fbe36791a67363652b4639861c1ffd7746bb Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 27 Jul 2023 16:42:03 -0400 Subject: [PATCH 128/170] Changelog for #12625, #13051, #13097, #13167, #13233, #13237 --- docs/release-notes/version-3.5.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/release-notes/version-3.5.md b/docs/release-notes/version-3.5.md index db301c55f..62e8741d6 100644 --- a/docs/release-notes/version-3.5.md +++ b/docs/release-notes/version-3.5.md @@ -2,6 +2,18 @@ ## v3.5.7 (FUTURE) +### Enhancements + +* [#12625](https://github.com/netbox-community/netbox/issues/12625) - Mask sensitive parameters when viewing a configured data source +* [#13097](https://github.com/netbox-community/netbox/issues/13097) - Implement a faster initial poll for report & script results + +### Bug Fixes + +* [#13051](https://github.com/netbox-community/netbox/issues/13051) - Fix Markdown support for table cell alignment +* [#13167](https://github.com/netbox-community/netbox/issues/13167) - Fix missing script results when fetched via REST API +* [#13233](https://github.com/netbox-community/netbox/issues/13233) - Remove extraneous VLAN group field from bulk edit form for interfaces +* [#13237](https://github.com/netbox-community/netbox/issues/13237) - Permit unauthenticated access to content types REST API endpoint when `LOGIN_REQUIRED` is false + --- ## v3.5.6 (2023-07-10) From eeb069048fa6742936c3b7268934cbe8ee2b8c35 Mon Sep 17 00:00:00 2001 From: "Daniel W. Anner" Date: Thu, 27 Jul 2023 19:02:08 -0400 Subject: [PATCH 129/170] Adding 100gbase-x-dsfp and 100gbase-x-sfpdd (#13236) * Adding 100gbase-x-dsfp * fixing missing comma * Adding interface `TYPE_100GE_SFP_DD`/`100gbase-x-sfpdd` * Update netbox/dcim/choices.py Co-authored-by: Jeremy Stretch --------- Co-authored-by: Jeremy Stretch --- netbox/dcim/choices.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/netbox/dcim/choices.py b/netbox/dcim/choices.py index f2f401718..1112b1344 100644 --- a/netbox/dcim/choices.py +++ b/netbox/dcim/choices.py @@ -809,6 +809,8 @@ class InterfaceTypeChoices(ChoiceSet): TYPE_100GE_CFP4 = '100gbase-x-cfp4' TYPE_100GE_CXP = '100gbase-x-cxp' TYPE_100GE_CPAK = '100gbase-x-cpak' + TYPE_100GE_DSFP = '100gbase-x-dsfp' + TYPE_100GE_SFP_DD = '100gbase-x-sfpdd' TYPE_100GE_QSFP28 = '100gbase-x-qsfp28' TYPE_100GE_QSFP_DD = '100gbase-x-qsfpdd' TYPE_200GE_CFP2 = '200gbase-x-cfp2' @@ -959,6 +961,8 @@ class InterfaceTypeChoices(ChoiceSet): (TYPE_100GE_CFP4, 'CFP4 (100GE)'), (TYPE_100GE_CXP, 'CXP (100GE)'), (TYPE_100GE_CPAK, 'Cisco CPAK (100GE)'), + (TYPE_100GE_DSFP, 'DSFP (100GE)'), + (TYPE_100GE_SFP_DD, 'SFP-DD (100GE)'), (TYPE_100GE_QSFP28, 'QSFP28 (100GE)'), (TYPE_100GE_QSFP_DD, 'QSFP-DD (100GE)'), (TYPE_200GE_QSFP56, 'QSFP56 (200GE)'), From c89193d3317a0f0655634fdba53bf30789c967da Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 28 Jul 2023 08:11:28 -0400 Subject: [PATCH 130/170] Closes #13080: Differentiate more clearly between old and new version placeholders in upgrade guide --- docs/installation/upgrading.md | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/docs/installation/upgrading.md b/docs/installation/upgrading.md index 27401c3cf..95304cd98 100644 --- a/docs/installation/upgrading.md +++ b/docs/installation/upgrading.md @@ -48,36 +48,40 @@ Download the [latest stable release](https://github.com/netbox-community/netbox/ Download and extract the latest version: ```no-highlight -wget https://github.com/netbox-community/netbox/archive/vX.Y.Z.tar.gz -sudo tar -xzf vX.Y.Z.tar.gz -C /opt -sudo ln -sfn /opt/netbox-X.Y.Z/ /opt/netbox +# Set $NEWVER to the NetBox version being installed +NEWVER=3.5.0 +wget https://github.com/netbox-community/netbox/archive/v$NEWVER.tar.gz +sudo tar -xzf v$NEWVER.tar.gz -C /opt +sudo ln -sfn /opt/netbox-$NEWVER/ /opt/netbox ``` Copy `local_requirements.txt`, `configuration.py`, and `ldap_config.py` (if present) from the current installation to the new version: ```no-highlight -sudo cp /opt/netbox-X.Y.Z/local_requirements.txt /opt/netbox/ -sudo cp /opt/netbox-X.Y.Z/netbox/netbox/configuration.py /opt/netbox/netbox/netbox/ -sudo cp /opt/netbox-X.Y.Z/netbox/netbox/ldap_config.py /opt/netbox/netbox/netbox/ +# Set $OLDVER to the NetBox version currently installed +NEWVER=3.4.9 +sudo cp /opt/netbox-$OLDVER/local_requirements.txt /opt/netbox/ +sudo cp /opt/netbox-$OLDVER/netbox/netbox/configuration.py /opt/netbox/netbox/netbox/ +sudo cp /opt/netbox-$OLDVER/netbox/netbox/ldap_config.py /opt/netbox/netbox/netbox/ ``` Be sure to replicate your uploaded media as well. (The exact action necessary will depend on where you choose to store your media, but in general moving or copying the media directory will suffice.) ```no-highlight -sudo cp -pr /opt/netbox-X.Y.Z/netbox/media/ /opt/netbox/netbox/ +sudo cp -pr /opt/netbox-$OLDVER/netbox/media/ /opt/netbox/netbox/ ``` Also make sure to copy or link any custom scripts and reports that you've made. Note that if these are stored outside the project root, you will not need to copy them. (Check the `SCRIPTS_ROOT` and `REPORTS_ROOT` parameters in the configuration file above if you're unsure.) ```no-highlight -sudo cp -r /opt/netbox-X.Y.Z/netbox/scripts /opt/netbox/netbox/ -sudo cp -r /opt/netbox-X.Y.Z/netbox/reports /opt/netbox/netbox/ +sudo cp -r /opt/netbox-$OLDVER/netbox/scripts /opt/netbox/netbox/ +sudo cp -r /opt/netbox-$OLDVER/netbox/reports /opt/netbox/netbox/ ``` If you followed the original installation guide to set up gunicorn, be sure to copy its configuration as well: ```no-highlight -sudo cp /opt/netbox-X.Y.Z/gunicorn.py /opt/netbox/ +sudo cp /opt/netbox-$OLDVER/gunicorn.py /opt/netbox/ ``` ### Option B: Clone the Git Repository From 7158360dfa296e1c3192bc3b9172ba1ae50db5f9 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Tue, 18 Jul 2023 14:58:04 +0530 Subject: [PATCH 131/170] moves non-racked devices to tab #11803 --- netbox/dcim/views.py | 28 +++++++--- netbox/templates/dcim/rack.html | 1 - .../dcim/rack/non_racked_devices.html | 51 +++++++++++++++++++ 3 files changed, 71 insertions(+), 9 deletions(-) create mode 100644 netbox/templates/dcim/rack/non_racked_devices.html diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 008db382a..5b93e5f0b 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -681,13 +681,6 @@ class RackView(generic.ObjectView): (PowerFeed.objects.restrict(request.user).filter(rack=instance), 'rack_id'), ) - # Get 0U devices located within the rack - nonracked_devices = Device.objects.filter( - rack=instance, - position__isnull=True, - parent_bay__isnull=True - ).prefetch_related('device_type__manufacturer', 'parent_bay', 'device_role') - peer_racks = Rack.objects.restrict(request.user, 'view').filter(site=instance.site) if instance.location: @@ -704,7 +697,6 @@ class RackView(generic.ObjectView): return { 'related_models': related_models, - 'nonracked_devices': nonracked_devices, 'next_rack': next_rack, 'prev_rack': prev_rack, 'svg_extra': svg_extra, @@ -731,6 +723,26 @@ class RackRackReservationsView(generic.ObjectChildrenView): return parent.reservations.restrict(request.user, 'view') +@register_model_view(Rack, 'nonracked_devices', 'nonracked-devices') +class RackNonRackedView(generic.ObjectChildrenView): + queryset = Rack.objects.all() + child_model = Device + table = tables.DeviceTable + filterset = filtersets.DeviceFilterSet + template_name = 'dcim/rack/non_racked_devices.html' + tab = ViewTab( + label=_('Non-Racked Devices'), + badge=lambda obj: obj.devices.filter(rack=obj, position__isnull=True, parent_bay__isnull=True).count(), + weight=500, + permission='dcim.view_device', + ) + + def get_children(self, request, parent): + return parent.devices.restrict(request.user, 'view').filter( + rack=parent, position__isnull=True, parent_bay__isnull=True + ) + + @register_model_view(Rack, 'edit') class RackEditView(generic.ObjectEditView): queryset = Rack.objects.all() diff --git a/netbox/templates/dcim/rack.html b/netbox/templates/dcim/rack.html index 52b5d4bfe..e513b178d 100644 --- a/netbox/templates/dcim/rack.html +++ b/netbox/templates/dcim/rack.html @@ -190,7 +190,6 @@
{% include 'inc/panels/related_objects.html' %} - {% include 'dcim/inc/nonracked_devices.html' %} {% plugin_right_page object %}
diff --git a/netbox/templates/dcim/rack/non_racked_devices.html b/netbox/templates/dcim/rack/non_racked_devices.html new file mode 100644 index 000000000..700c66369 --- /dev/null +++ b/netbox/templates/dcim/rack/non_racked_devices.html @@ -0,0 +1,51 @@ +{% extends 'dcim/rack/base.html' %} +{% load helpers %} + +{% block extra_controls %} + {% if perms.dcim.add_device %} + + {% endif %} +{% endblock %} + +{% block content %} + {% include 'inc/table_controls_htmx.html' with table_modal="DeviceTable_config" %} + + + {% csrf_token %} + +
+
+ {% include 'htmx/table.html' %} +
+
+ +
+
+ {% if 'bulk_edit' in actions %} + + {% endif %} + {% if 'bulk_delete' in actions %} + + {% endif %} +
+
+ +{% endblock content %} + +{% block modals %} + {{ block.super }} + {% table_config_form table %} +{% endblock modals %} From 2aa51d0d94322bd2c38c7eafd0f1913e708b5d15 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Fri, 28 Jul 2023 18:53:22 +0530 Subject: [PATCH 132/170] Adds contact assignment bulk import (#13109) * adds contact assignment bulk import #11307 * Remove unsupported tags field added by NetBoxModelImportForm --------- Co-authored-by: Jeremy Stretch --- netbox/netbox/navigation/menu.py | 2 +- netbox/tenancy/forms/bulk_import.py | 28 +++++++++++++++++++++++++++- netbox/tenancy/urls.py | 1 + netbox/tenancy/views.py | 5 +++++ 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/netbox/netbox/navigation/menu.py b/netbox/netbox/navigation/menu.py index e009f62f1..1379beba5 100644 --- a/netbox/netbox/navigation/menu.py +++ b/netbox/netbox/navigation/menu.py @@ -46,7 +46,7 @@ ORGANIZATION_MENU = Menu( get_model_item('tenancy', 'contact', _('Contacts')), get_model_item('tenancy', 'contactgroup', _('Contact Groups')), get_model_item('tenancy', 'contactrole', _('Contact Roles')), - get_model_item('tenancy', 'contactassignment', _('Contact Assignments'), actions=[]), + get_model_item('tenancy', 'contactassignment', _('Contact Assignments'), actions=['import']), ), ), ), diff --git a/netbox/tenancy/forms/bulk_import.py b/netbox/tenancy/forms/bulk_import.py index f9b8accd9..0aec0e28f 100644 --- a/netbox/tenancy/forms/bulk_import.py +++ b/netbox/tenancy/forms/bulk_import.py @@ -1,9 +1,11 @@ +from django.contrib.contenttypes.models import ContentType from django.utils.translation import gettext as _ from netbox.forms import NetBoxModelImportForm from tenancy.models import * -from utilities.forms.fields import CSVModelChoiceField, SlugField +from utilities.forms.fields import CSVContentTypeField, CSVModelChoiceField, SlugField __all__ = ( + 'ContactAssignmentImportForm', 'ContactImportForm', 'ContactGroupImportForm', 'ContactRoleImportForm', @@ -81,3 +83,27 @@ class ContactImportForm(NetBoxModelImportForm): class Meta: model = Contact fields = ('name', 'title', 'phone', 'email', 'address', 'link', 'group', 'description', 'comments', 'tags') + + +class ContactAssignmentImportForm(NetBoxModelImportForm): + content_type = CSVContentTypeField( + queryset=ContentType.objects.all(), + help_text=_("One or more assigned object types") + ) + contact = CSVModelChoiceField( + queryset=Contact.objects.all(), + to_field_name='name', + help_text=_('Assigned contact') + ) + role = CSVModelChoiceField( + queryset=ContactRole.objects.all(), + to_field_name='name', + help_text=_('Assigned role') + ) + + # Remove the tags field added by NetBoxModelImportForm (unsupported by ContactAssignment) + tags = None + + class Meta: + model = ContactAssignment + fields = ('content_type', 'object_id', 'contact', 'priority', 'role') diff --git a/netbox/tenancy/urls.py b/netbox/tenancy/urls.py index 87491ea0e..ad9908c62 100644 --- a/netbox/tenancy/urls.py +++ b/netbox/tenancy/urls.py @@ -49,6 +49,7 @@ urlpatterns = [ # Contact assignments path('contact-assignments/', views.ContactAssignmentListView.as_view(), name='contactassignment_list'), path('contact-assignments/add/', views.ContactAssignmentEditView.as_view(), name='contactassignment_add'), + path('contact-assignments/import/', views.ContactAssignmentBulkImportView.as_view(), name='contactassignment_import'), path('contact-assignments/edit/', views.ContactAssignmentBulkEditView.as_view(), name='contactassignment_bulk_edit'), path('contact-assignments/delete/', views.ContactAssignmentBulkDeleteView.as_view(), name='contactassignment_bulk_delete'), path('contact-assignments//', include(get_model_urls('tenancy', 'contactassignment'))), diff --git a/netbox/tenancy/views.py b/netbox/tenancy/views.py index bbe901bde..23020e794 100644 --- a/netbox/tenancy/views.py +++ b/netbox/tenancy/views.py @@ -420,6 +420,11 @@ class ContactAssignmentBulkEditView(generic.BulkEditView): form = forms.ContactAssignmentBulkEditForm +class ContactAssignmentBulkImportView(generic.BulkImportView): + queryset = ContactAssignment.objects.all() + model_form = forms.ContactAssignmentImportForm + + class ContactAssignmentBulkDeleteView(generic.BulkDeleteView): queryset = ContactAssignment.objects.all() filterset = filtersets.ContactAssignmentFilterSet From 9d0457fe1aa4f0c081d9bc7be0842155a65f1cfc Mon Sep 17 00:00:00 2001 From: Bruno Blanes Date: Fri, 28 Jul 2023 10:26:46 -0300 Subject: [PATCH 133/170] Add Brazilian power outlet standard to choices.py (#13012) * Add Brazilian power outlet standard to choices.py * Eliminate possible name conflict * Rename group and add IEC 60906-1 plug type * Update choices.py Add Brazilian power port standard --- netbox/dcim/choices.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/netbox/dcim/choices.py b/netbox/dcim/choices.py index 1112b1344..e850a8c51 100644 --- a/netbox/dcim/choices.py +++ b/netbox/dcim/choices.py @@ -318,6 +318,10 @@ class PowerPortTypeChoices(ChoiceSet): TYPE_IEC_3PNE4H = 'iec-60309-3p-n-e-4h' TYPE_IEC_3PNE6H = 'iec-60309-3p-n-e-6h' TYPE_IEC_3PNE9H = 'iec-60309-3p-n-e-9h' + # IEC 60906-1 + TYPE_IEC_60906_1 = 'iec-60906-1' + TYPE_NBR_14136_10A = 'nbr-14136-10a' + TYPE_NBR_14136_20A = 'nbr-14136-20a' # NEMA non-locking TYPE_NEMA_115P = 'nema-1-15p' TYPE_NEMA_515P = 'nema-5-15p' @@ -429,6 +433,11 @@ class PowerPortTypeChoices(ChoiceSet): (TYPE_IEC_3PNE6H, '3P+N+E 6H'), (TYPE_IEC_3PNE9H, '3P+N+E 9H'), )), + ('IEC 60906-1', ( + (TYPE_IEC_60906_1, 'IEC 60906-1'), + (TYPE_NBR_14136_10A, '2P+T 10A (NBR 14136)'), + (TYPE_NBR_14136_20A, '2P+T 20A (NBR 14136)'), + )), ('NEMA (Non-locking)', ( (TYPE_NEMA_115P, 'NEMA 1-15P'), (TYPE_NEMA_515P, 'NEMA 5-15P'), @@ -553,6 +562,10 @@ class PowerOutletTypeChoices(ChoiceSet): TYPE_IEC_3PNE4H = 'iec-60309-3p-n-e-4h' TYPE_IEC_3PNE6H = 'iec-60309-3p-n-e-6h' TYPE_IEC_3PNE9H = 'iec-60309-3p-n-e-9h' + # IEC 60906-1 + TYPE_IEC_60906_1 = 'iec-60906-1' + TYPE_NBR_14136_10A = 'nbr-14136-10a' + TYPE_NBR_14136_20A = 'nbr-14136-20a' # NEMA non-locking TYPE_NEMA_115R = 'nema-1-15r' TYPE_NEMA_515R = 'nema-5-15r' @@ -657,6 +670,11 @@ class PowerOutletTypeChoices(ChoiceSet): (TYPE_IEC_3PNE6H, '3P+N+E 6H'), (TYPE_IEC_3PNE9H, '3P+N+E 9H'), )), + ('IEC 60906-1', ( + (TYPE_IEC_60906_1, 'IEC 60906-1'), + (TYPE_NBR_14136_10A, '2P+T 10A (NBR 14136)'), + (TYPE_NBR_14136_20A, '2P+T 20A (NBR 14136)'), + )), ('NEMA (Non-locking)', ( (TYPE_NEMA_115R, 'NEMA 1-15R'), (TYPE_NEMA_515R, 'NEMA 5-15R'), From 90146941b5b1301258babfa91de96267badf4657 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 28 Jul 2023 09:49:09 -0400 Subject: [PATCH 134/170] Fixes #13285: Cast default u_height value to a decimal for validation --- netbox/dcim/models/devices.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/dcim/models/devices.py b/netbox/dcim/models/devices.py index 4cf330ffd..fbc92e1fe 100644 --- a/netbox/dcim/models/devices.py +++ b/netbox/dcim/models/devices.py @@ -232,7 +232,7 @@ class DeviceType(PrimaryModel, WeightMixin): super().clean() # U height must be divisible by 0.5 - if self.u_height % decimal.Decimal(0.5): + if decimal.Decimal(self.u_height) % decimal.Decimal(0.5): raise ValidationError({ 'u_height': "U height must be in increments of 0.5 rack units." }) From d9dc6cec3a07bf555d8613a77f7af1ce1954db6f Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 28 Jul 2023 10:02:42 -0400 Subject: [PATCH 135/170] Changelog for #11803, #13009, #13234, #13285 --- docs/release-notes/version-3.5.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/release-notes/version-3.5.md b/docs/release-notes/version-3.5.md index 62e8741d6..ddbc47154 100644 --- a/docs/release-notes/version-3.5.md +++ b/docs/release-notes/version-3.5.md @@ -4,8 +4,11 @@ ### Enhancements +* [#11803](https://github.com/netbox-community/netbox/issues/11803) - Move non-rack devices list to a separate tab under the rack view * [#12625](https://github.com/netbox-community/netbox/issues/12625) - Mask sensitive parameters when viewing a configured data source +* [#13009](https://github.com/netbox-community/netbox/issues/13009) - Add IEC 10609-1 and NBR 14136 power port & outlet types * [#13097](https://github.com/netbox-community/netbox/issues/13097) - Implement a faster initial poll for report & script results +* [#13234](https://github.com/netbox-community/netbox/issues/13234) - Add 100GBASE-X-DSFP and 100GBASE-X-SFPDD interface types ### Bug Fixes @@ -13,6 +16,7 @@ * [#13167](https://github.com/netbox-community/netbox/issues/13167) - Fix missing script results when fetched via REST API * [#13233](https://github.com/netbox-community/netbox/issues/13233) - Remove extraneous VLAN group field from bulk edit form for interfaces * [#13237](https://github.com/netbox-community/netbox/issues/13237) - Permit unauthenticated access to content types REST API endpoint when `LOGIN_REQUIRED` is false +* [#13285](https://github.com/netbox-community/netbox/issues/13285) - Fix exception when importing device type missing rack unit height value --- From 4f984c0831de10994888063a5836a090c5c6201e Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 28 Jul 2023 10:11:16 -0400 Subject: [PATCH 136/170] Release v3.5.7 --- .github/ISSUE_TEMPLATE/bug_report.yaml | 2 +- .github/ISSUE_TEMPLATE/feature_request.yaml | 2 +- docs/release-notes/version-3.5.md | 2 +- netbox/netbox/settings.py | 2 +- requirements.txt | 12 ++++++------ 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index 8cb548de2..42a716ae7 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -14,7 +14,7 @@ body: attributes: label: NetBox version description: What version of NetBox are you currently running? - placeholder: v3.5.6 + placeholder: v3.5.7 validations: required: true - type: dropdown diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml index df931c77b..b04fda1b6 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yaml +++ b/.github/ISSUE_TEMPLATE/feature_request.yaml @@ -14,7 +14,7 @@ body: attributes: label: NetBox version description: What version of NetBox are you currently running? - placeholder: v3.5.6 + placeholder: v3.5.7 validations: required: true - type: dropdown diff --git a/docs/release-notes/version-3.5.md b/docs/release-notes/version-3.5.md index ddbc47154..5fc6961fc 100644 --- a/docs/release-notes/version-3.5.md +++ b/docs/release-notes/version-3.5.md @@ -1,6 +1,6 @@ # NetBox v3.5 -## v3.5.7 (FUTURE) +## v3.5.7 (2023-07-28) ### Enhancements diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 7d2da2996..aec4f76f6 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -25,7 +25,7 @@ from netbox.constants import RQ_QUEUE_DEFAULT, RQ_QUEUE_HIGH, RQ_QUEUE_LOW # Environment setup # -VERSION = '3.5.7-dev' +VERSION = '3.5.7' # Hostname HOSTNAME = platform.node() diff --git a/requirements.txt b/requirements.txt index 3750e724b..f1235fa2c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ bleach==6.0.0 -boto3==1.28.1 +boto3==1.28.14 Django==4.1.10 django-cors-headers==4.2.0 django-debug-toolbar==4.1.0 @@ -15,21 +15,21 @@ django-tables2==2.6.0 django-taggit==4.0.0 django-timezone-field==5.1 djangorestframework==3.14.0 -drf-spectacular==0.26.3 +drf-spectacular==0.26.4 drf-spectacular-sidecar==2023.7.1 dulwich==0.21.5 feedparser==6.0.10 graphene-django==3.0.0 -gunicorn==20.1.0 +gunicorn==21.2.0 Jinja2==3.1.2 Markdown==3.3.7 -mkdocs-material==9.1.18 +mkdocs-material==9.1.21 mkdocstrings[python-legacy]==0.22.0 netaddr==0.8.0 Pillow==10.0.0 psycopg2-binary==2.9.6 -PyYAML==6.0 -sentry-sdk==1.28.0 +PyYAML==6.0.1 +sentry-sdk==1.28.1 social-auth-app-django==5.2.0 social-auth-core[openidconnect]==4.4.2 svgwrite==1.4.3 From 006c353d46d9aced93e39ac9efaa4a71aaa54e06 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 28 Jul 2023 10:31:54 -0400 Subject: [PATCH 137/170] PRVB --- docs/release-notes/version-3.5.md | 4 ++++ netbox/netbox/settings.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/version-3.5.md b/docs/release-notes/version-3.5.md index 5fc6961fc..66c2cabce 100644 --- a/docs/release-notes/version-3.5.md +++ b/docs/release-notes/version-3.5.md @@ -1,5 +1,9 @@ # NetBox v3.5 +## v3.5.8 (FUTURE) + +--- + ## v3.5.7 (2023-07-28) ### Enhancements diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index aec4f76f6..58256d079 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -25,7 +25,7 @@ from netbox.constants import RQ_QUEUE_DEFAULT, RQ_QUEUE_HIGH, RQ_QUEUE_LOW # Environment setup # -VERSION = '3.5.7' +VERSION = '3.5.8-dev' # Hostname HOSTNAME = platform.node() From 0b10131564dc16138b9b7c7cd869d705771c229e Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Sun, 30 Jul 2023 13:34:08 -0400 Subject: [PATCH 138/170] Satisfy PEP8 E721 linter complaints --- netbox/dcim/graphql/gfk_mixins.py | 64 ++++++++++++------------ netbox/ipam/graphql/gfk_mixins.py | 30 +++++------ netbox/ipam/utils.py | 2 +- netbox/utilities/forms/bulk_import.py | 4 +- netbox/utilities/templatetags/helpers.py | 2 +- 5 files changed, 51 insertions(+), 51 deletions(-) diff --git a/netbox/dcim/graphql/gfk_mixins.py b/netbox/dcim/graphql/gfk_mixins.py index f3b8b696b..c97aa4c2b 100644 --- a/netbox/dcim/graphql/gfk_mixins.py +++ b/netbox/dcim/graphql/gfk_mixins.py @@ -53,23 +53,23 @@ class LinkPeerType(graphene.Union): @classmethod def resolve_type(cls, instance, info): - if type(instance) == CircuitTermination: + if type(instance) is CircuitTermination: return CircuitTerminationType - if type(instance) == ConsolePortType: + if type(instance) is ConsolePortType: return ConsolePortType - if type(instance) == ConsoleServerPort: + if type(instance) is ConsoleServerPort: return ConsoleServerPortType - if type(instance) == FrontPort: + if type(instance) is FrontPort: return FrontPortType - if type(instance) == Interface: + if type(instance) is Interface: return InterfaceType - if type(instance) == PowerFeed: + if type(instance) is PowerFeed: return PowerFeedType - if type(instance) == PowerOutlet: + if type(instance) is PowerOutlet: return PowerOutletType - if type(instance) == PowerPort: + if type(instance) is PowerPort: return PowerPortType - if type(instance) == RearPort: + if type(instance) is RearPort: return RearPortType @@ -89,23 +89,23 @@ class CableTerminationTerminationType(graphene.Union): @classmethod def resolve_type(cls, instance, info): - if type(instance) == CircuitTermination: + if type(instance) is CircuitTermination: return CircuitTerminationType - if type(instance) == ConsolePortType: + if type(instance) is ConsolePortType: return ConsolePortType - if type(instance) == ConsoleServerPort: + if type(instance) is ConsoleServerPort: return ConsoleServerPortType - if type(instance) == FrontPort: + if type(instance) is FrontPort: return FrontPortType - if type(instance) == Interface: + if type(instance) is Interface: return InterfaceType - if type(instance) == PowerFeed: + if type(instance) is PowerFeed: return PowerFeedType - if type(instance) == PowerOutlet: + if type(instance) is PowerOutlet: return PowerOutletType - if type(instance) == PowerPort: + if type(instance) is PowerPort: return PowerPortType - if type(instance) == RearPort: + if type(instance) is RearPort: return RearPortType @@ -123,19 +123,19 @@ class InventoryItemTemplateComponentType(graphene.Union): @classmethod def resolve_type(cls, instance, info): - if type(instance) == ConsolePortTemplate: + if type(instance) is ConsolePortTemplate: return ConsolePortTemplateType - if type(instance) == ConsoleServerPortTemplate: + if type(instance) is ConsoleServerPortTemplate: return ConsoleServerPortTemplateType - if type(instance) == FrontPortTemplate: + if type(instance) is FrontPortTemplate: return FrontPortTemplateType - if type(instance) == InterfaceTemplate: + if type(instance) is InterfaceTemplate: return InterfaceTemplateType - if type(instance) == PowerOutletTemplate: + if type(instance) is PowerOutletTemplate: return PowerOutletTemplateType - if type(instance) == PowerPortTemplate: + if type(instance) is PowerPortTemplate: return PowerPortTemplateType - if type(instance) == RearPortTemplate: + if type(instance) is RearPortTemplate: return RearPortTemplateType @@ -153,17 +153,17 @@ class InventoryItemComponentType(graphene.Union): @classmethod def resolve_type(cls, instance, info): - if type(instance) == ConsolePort: + if type(instance) is ConsolePort: return ConsolePortType - if type(instance) == ConsoleServerPort: + if type(instance) is ConsoleServerPort: return ConsoleServerPortType - if type(instance) == FrontPort: + if type(instance) is FrontPort: return FrontPortType - if type(instance) == Interface: + if type(instance) is Interface: return InterfaceType - if type(instance) == PowerOutlet: + if type(instance) is PowerOutlet: return PowerOutletType - if type(instance) == PowerPort: + if type(instance) is PowerPort: return PowerPortType - if type(instance) == RearPort: + if type(instance) is RearPort: return RearPortType diff --git a/netbox/ipam/graphql/gfk_mixins.py b/netbox/ipam/graphql/gfk_mixins.py index 31742c4a4..01c79690a 100644 --- a/netbox/ipam/graphql/gfk_mixins.py +++ b/netbox/ipam/graphql/gfk_mixins.py @@ -24,11 +24,11 @@ class IPAddressAssignmentType(graphene.Union): @classmethod def resolve_type(cls, instance, info): - if type(instance) == Interface: + if type(instance) is Interface: return InterfaceType - if type(instance) == FHRPGroup: + if type(instance) is FHRPGroup: return FHRPGroupType - if type(instance) == VMInterface: + if type(instance) is VMInterface: return VMInterfaceType @@ -42,11 +42,11 @@ class L2VPNAssignmentType(graphene.Union): @classmethod def resolve_type(cls, instance, info): - if type(instance) == Interface: + if type(instance) is Interface: return InterfaceType - if type(instance) == VLAN: + if type(instance) is VLAN: return VLANType - if type(instance) == VMInterface: + if type(instance) is VMInterface: return VMInterfaceType @@ -59,9 +59,9 @@ class FHRPGroupInterfaceType(graphene.Union): @classmethod def resolve_type(cls, instance, info): - if type(instance) == Interface: + if type(instance) is Interface: return InterfaceType - if type(instance) == VMInterface: + if type(instance) is VMInterface: return VMInterfaceType @@ -79,17 +79,17 @@ class VLANGroupScopeType(graphene.Union): @classmethod def resolve_type(cls, instance, info): - if type(instance) == Cluster: + if type(instance) is Cluster: return ClusterType - if type(instance) == ClusterGroup: + if type(instance) is ClusterGroup: return ClusterGroupType - if type(instance) == Location: + if type(instance) is Location: return LocationType - if type(instance) == Rack: + if type(instance) is Rack: return RackType - if type(instance) == Region: + if type(instance) is Region: return RegionType - if type(instance) == Site: + if type(instance) is Site: return SiteType - if type(instance) == SiteGroup: + if type(instance) is SiteGroup: return SiteGroupType diff --git a/netbox/ipam/utils.py b/netbox/ipam/utils.py index 93a40e5a0..262fd8d46 100644 --- a/netbox/ipam/utils.py +++ b/netbox/ipam/utils.py @@ -121,7 +121,7 @@ def add_available_vlans(vlans, vlan_group=None): }) vlans = list(vlans) + new_vlans - vlans.sort(key=lambda v: v.vid if type(v) == VLAN else v['vid']) + vlans.sort(key=lambda v: v.vid if type(v) is VLAN else v['vid']) return vlans diff --git a/netbox/utilities/forms/bulk_import.py b/netbox/utilities/forms/bulk_import.py index b7f432e63..6bdfd5662 100644 --- a/netbox/utilities/forms/bulk_import.py +++ b/netbox/utilities/forms/bulk_import.py @@ -123,9 +123,9 @@ class BulkImportForm(BootstrapMixin, SyncedDataMixin, forms.Form): records = [] try: for data in yaml.load_all(data, Loader=yaml.SafeLoader): - if type(data) == list: + if type(data) is list: records.extend(data) - elif type(data) == dict: + elif type(data) is dict: records.append(data) else: raise forms.ValidationError({ diff --git a/netbox/utilities/templatetags/helpers.py b/netbox/utilities/templatetags/helpers.py index 09a083112..aaee9679c 100644 --- a/netbox/utilities/templatetags/helpers.py +++ b/netbox/utilities/templatetags/helpers.py @@ -114,7 +114,7 @@ def annotated_date(date_value): if not date_value: return '' - if type(date_value) == datetime.date: + if type(date_value) is datetime.date: long_ts = date(date_value, 'DATE_FORMAT') short_ts = date(date_value, 'SHORT_DATE_FORMAT') else: From a4c9cbc6ddf23e65558aa15780885189bb4ae34d Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 2 Aug 2023 08:55:38 -0400 Subject: [PATCH 139/170] Remove hard-coded test runner --- netbox/netbox/settings.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 58256d079..2744ba701 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -461,8 +461,6 @@ LOGIN_REDIRECT_URL = f'/{BASE_PATH}' DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' -TEST_RUNNER = "django_rich.test.RichRunner" - # Exclude potentially sensitive models from wildcard view exemption. These may still be exempted # by specifying the model individually in the EXEMPT_VIEW_PERMISSIONS configuration parameter. EXEMPT_EXCLUDE_MODELS = ( From a68831d3a1a438881e9ba254918d6b391a5303af Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Wed, 2 Aug 2023 17:25:54 +0530 Subject: [PATCH 140/170] fixes provider_network_id for related circuits #13343 --- netbox/circuits/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/circuits/views.py b/netbox/circuits/views.py index f1cfdd1d5..64dd82682 100644 --- a/netbox/circuits/views.py +++ b/netbox/circuits/views.py @@ -163,7 +163,7 @@ class ProviderNetworkView(generic.ObjectView): related_models = ( ( Circuit.objects.restrict(request.user, 'view').filter(terminations__provider_network=instance), - 'providernetwork_id', + 'provider_network_id', ), ) From ab916a18191849d9e91c8344eef18d4a55e85555 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Tue, 1 Aug 2023 20:35:16 +0530 Subject: [PATCH 141/170] fixes dummy payload URL for webhook test --- netbox/extras/tests/test_webhooks.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/netbox/extras/tests/test_webhooks.py b/netbox/extras/tests/test_webhooks.py index 19264dabb..ef7637765 100644 --- a/netbox/extras/tests/test_webhooks.py +++ b/netbox/extras/tests/test_webhooks.py @@ -31,8 +31,8 @@ class WebhookTest(APITestCase): def setUpTestData(cls): site_ct = ContentType.objects.get_for_model(Site) - DUMMY_URL = "http://localhost/" - DUMMY_SECRET = "LOOKATMEIMASECRETSTRING" + DUMMY_URL = 'http://localhost:9000/' + DUMMY_SECRET = 'LOOKATMEIMASECRETSTRING' webhooks = Webhook.objects.bulk_create(( Webhook(name='Webhook 1', type_create=True, payload_url=DUMMY_URL, secret=DUMMY_SECRET, additional_headers='X-Foo: Bar'), @@ -259,7 +259,7 @@ class WebhookTest(APITestCase): name='Conditional Webhook', type_create=True, type_update=True, - payload_url='http://localhost/', + payload_url='http://localhost:9000/', conditions={ 'and': [ { From 57860f26b778311e66115fe94cf6b95337f54593 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Thu, 3 Aug 2023 01:15:09 +0530 Subject: [PATCH 142/170] Adds assigned bool for IP address API (#13301) * adds assigned bool for ip address API #13151 * Add filterset test --------- Co-authored-by: Jeremy Stretch --- netbox/ipam/filtersets.py | 16 ++++++++++++++++ netbox/ipam/tests/test_filtersets.py | 6 ++++++ 2 files changed, 22 insertions(+) diff --git a/netbox/ipam/filtersets.py b/netbox/ipam/filtersets.py index d011472d9..9b57cb273 100644 --- a/netbox/ipam/filtersets.py +++ b/netbox/ipam/filtersets.py @@ -591,6 +591,10 @@ class IPAddressFilterSet(NetBoxModelFilterSet, TenancyFilterSet): method='_assigned_to_interface', label=_('Is assigned to an interface'), ) + assigned = django_filters.BooleanFilter( + method='_assigned', + label=_('Is assigned'), + ) status = django_filters.MultipleChoiceFilter( choices=IPAddressStatusChoices, null_value=None @@ -706,6 +710,18 @@ class IPAddressFilterSet(NetBoxModelFilterSet, TenancyFilterSet): assigned_object_id__isnull=False ) + def _assigned(self, queryset, name, value): + if value: + return queryset.exclude( + assigned_object_type__isnull=True, + assigned_object_id__isnull=True + ) + else: + return queryset.filter( + assigned_object_type__isnull=True, + assigned_object_id__isnull=True + ) + class FHRPGroupFilterSet(NetBoxModelFilterSet): protocol = django_filters.MultipleChoiceFilter( diff --git a/netbox/ipam/tests/test_filtersets.py b/netbox/ipam/tests/test_filtersets.py index 3d9a66567..0ae7544ab 100644 --- a/netbox/ipam/tests/test_filtersets.py +++ b/netbox/ipam/tests/test_filtersets.py @@ -992,6 +992,12 @@ class IPAddressTestCase(TestCase, ChangeLoggedFilterSetTests): params = {'fhrpgroup_id': [fhrp_groups[0].pk, fhrp_groups[1].pk]} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + def test_assigned(self): + params = {'assigned': 'true'} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 8) + params = {'assigned': 'false'} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) + def test_assigned_to_interface(self): params = {'assigned_to_interface': 'true'} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6) From a807cca29e094a2aa1872411a63f2a0bac98acb8 Mon Sep 17 00:00:00 2001 From: Matej Vadnjal Date: Wed, 2 Aug 2023 22:08:14 +0200 Subject: [PATCH 143/170] Fixes #13033: add formatted speed column to Interfaces (#13275) * Fixes #13033: add formatted speed column to Interfaces * use TemplateColumn instead of own class --- netbox/dcim/tables/devices.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/netbox/dcim/tables/devices.py b/netbox/dcim/tables/devices.py index db2655d27..42b34e999 100644 --- a/netbox/dcim/tables/devices.py +++ b/netbox/dcim/tables/devices.py @@ -545,6 +545,11 @@ class InterfaceTable(ModularDeviceComponentTable, BaseInterfaceTable, PathEndpoi } ) mgmt_only = columns.BooleanColumn() + speed_formatted = columns.TemplateColumn( + template_code='{% load helpers %}{{ value|humanize_speed }}', + accessor=Accessor('speed'), + verbose_name='Speed' + ) wireless_link = tables.Column( linkify=True ) @@ -568,7 +573,7 @@ class InterfaceTable(ModularDeviceComponentTable, BaseInterfaceTable, PathEndpoi model = models.Interface fields = ( 'pk', 'id', 'name', 'device', 'module_bay', 'module', 'label', 'enabled', 'type', 'mgmt_only', 'mtu', - 'speed', 'duplex', 'mode', 'mac_address', 'wwn', 'poe_mode', 'poe_type', 'rf_role', 'rf_channel', + 'speed', 'speed_formatted', 'duplex', 'mode', 'mac_address', 'wwn', 'poe_mode', 'poe_type', 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'description', 'mark_connected', 'cable', 'cable_color', 'wireless_link', 'wireless_lans', 'link_peer', 'connection', 'tags', 'vdcs', 'vrf', 'l2vpn', 'ip_addresses', 'fhrp_groups', 'untagged_vlan', 'tagged_vlans', 'created', 'last_updated', From 9cc295827b262693d43eb6f03dc0a3722fd3a88e Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 3 Aug 2023 14:53:58 -0400 Subject: [PATCH 144/170] Fixes #13369: Fix job termination status for failed reports --- netbox/extras/reports.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/netbox/extras/reports.py b/netbox/extras/reports.py index 8f3af2a09..6af81a9d9 100644 --- a/netbox/extras/reports.py +++ b/netbox/extras/reports.py @@ -214,20 +214,18 @@ class Report(object): self.active_test = method_name test_method = getattr(self, method_name) test_method() + job.data = self._results if self.failed: self.logger.warning("Report failed") - job.status = JobStatusChoices.STATUS_FAILED + job.terminate(status=JobStatusChoices.STATUS_FAILED) else: self.logger.info("Report completed successfully") - job.status = JobStatusChoices.STATUS_COMPLETED + job.terminate() except Exception as e: stacktrace = traceback.format_exc() self.log_failure(None, f"An exception occurred: {type(e).__name__}: {e}
{stacktrace}
") logger.error(f"Exception raised during report execution: {e}") job.terminate(status=JobStatusChoices.STATUS_ERRORED) - finally: - job.data = self._results - job.terminate() # Perform any post-run tasks self.post_run() From 93a862cded3b15d293c0680f58c45c2da6d2e397 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 4 Aug 2023 08:55:43 -0400 Subject: [PATCH 145/170] Add stadium analogy and behavior anti-patterns --- CONTRIBUTING.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6b71fb515..301fac079 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,12 +14,25 @@

-Some general tips for engaging here on GitHub: +## :information_source: Welcome to the Stadium! + +In her book [Working in Public](https://www.amazon.com/Working-Public-Making-Maintenance-Software/dp/0578675862), Nadia Eghbal defines four production models for open source projects, categorized by contributor and user growth: federations, clubs, toys, and stadiums. The NetBox project fits her definition of a stadium very well: + +> Stadiums are projects with low contributor growth and high user growth. While they may receive casual contributions, their regular contributor base does not grow proportionately to their users. As a result, they tend to be powered by one or a few developers. + +The bulk of NetBox's development is carried out by a handful of core maintainers, with occasional contributions from collaborators in the community. We find the stadium analogy very useful in conveying the roles and obligations of both contributors and users. + +If you're a contributor, actively working on the center stage, you have an obligation to produce quality content that will benefit the project as a whole. Conversely, if you're in the audience consuming the work being produced, you have the option of making requests and suggestions, but must also recognize that contributors are under no obligation to act on them. + +NetBox users are welcome to participate in either role, on stage or in the crowd. We ask only that you acknowledge the role you've chosen and respect the roles of others. + +### General Tips for Working on GitHub * Register for a free [GitHub account](https://github.com/signup) if you haven't already. * You can use [GitHub Markdown](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax) for formatting text and adding images. * To help mitigate notification spam, please avoid "bumping" issues with no activity. (To vote an issue up or down, use a :thumbsup: or :thumbsdown: reaction.) * Please avoid pinging members with `@` unless they've previously expressed interest or involvement with that particular issue. +* Familiarize yourself with [this list of discussion anti-patterns](https://github.com/bradfitz/issue-tracker-behaviors) and make every effort to avoid them. ## :bug: Reporting Bugs From 7f22c6bf12e5e4e18691fe65973f6a33dab8d066 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 4 Aug 2023 10:12:15 -0400 Subject: [PATCH 146/170] Include notes re: demo data and netbox-docker --- docs/development/release-checklist.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/development/release-checklist.md b/docs/development/release-checklist.md index efb0f44b9..000948ee7 100644 --- a/docs/development/release-checklist.md +++ b/docs/development/release-checklist.md @@ -43,10 +43,22 @@ Follow these instructions to perform a new installation of NetBox in a temporary Submit a pull request to merge the `feature` branch into the `develop` branch in preparation for its release. Once it has been merged, continue with the section for patch releases below. +### Rebuild Demo Data (After Release) + +After the release of a new minor version, generate a new demo data snapshot compatible with the new release. See the [`netbox-demo-data`](https://github.com/netbox-community/netbox-demo-data) repository for instructions. + --- ## Patch Releases +### Notify netbox-docker Project of Any Relevant Changes + +Notify the [`netbox-docker`](https://github.com/netbox-community/netbox-docker) maintainers (in **#netbox-docker**) of any changes that may be relevant to their build process, including: + +* Significant changes to `upgrade.sh` +* Increases in minimum versions for service dependencies (PostgreSQL, Redis, etc.) +* Any changes to the reference installation + ### Update Requirements Before each release, update each of NetBox's Python dependencies to its most recent stable version. These are defined in `requirements.txt`, which is updated from `base_requirements.txt` using `pip`. To do this: From 43ce453938c9acdcd1bfdeebda906800e37ac86d Mon Sep 17 00:00:00 2001 From: Henrik Strand Date: Fri, 4 Aug 2023 17:32:52 +0200 Subject: [PATCH 147/170] Adding interface TYPE_400GE_CFP2/400gbase-x-cfp2 (#13338) * Added 400G CFP2 to InterfaceTypeChoices * Added new type to choises --- netbox/dcim/choices.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/netbox/dcim/choices.py b/netbox/dcim/choices.py index e850a8c51..21bd3ed7e 100644 --- a/netbox/dcim/choices.py +++ b/netbox/dcim/choices.py @@ -834,6 +834,7 @@ class InterfaceTypeChoices(ChoiceSet): TYPE_200GE_CFP2 = '200gbase-x-cfp2' TYPE_200GE_QSFP56 = '200gbase-x-qsfp56' TYPE_200GE_QSFP_DD = '200gbase-x-qsfpdd' + TYPE_400GE_CFP2 = '400gbase-x-cfp2' TYPE_400GE_QSFP_DD = '400gbase-x-qsfpdd' TYPE_400GE_OSFP = '400gbase-x-osfp' TYPE_400GE_CDFP = '400gbase-x-cdfp' @@ -976,6 +977,7 @@ class InterfaceTypeChoices(ChoiceSet): (TYPE_100GE_CFP, 'CFP (100GE)'), (TYPE_100GE_CFP2, 'CFP2 (100GE)'), (TYPE_200GE_CFP2, 'CFP2 (200GE)'), + (TYPE_400GE_CFP2, 'CFP2 (400GE)'), (TYPE_100GE_CFP4, 'CFP4 (100GE)'), (TYPE_100GE_CXP, 'CXP (100GE)'), (TYPE_100GE_CPAK, 'Cisco CPAK (100GE)'), From 01bb09db674d99316cc8347c26cb430268e9e8fb Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Tue, 1 Aug 2023 18:55:42 +0530 Subject: [PATCH 148/170] adds delete for SyncedDataMixin when related AutoSyncRecord is available #12750 --- netbox/netbox/models/features.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/netbox/netbox/models/features.py b/netbox/netbox/models/features.py index 8bacba534..1e55ec2a3 100644 --- a/netbox/netbox/models/features.py +++ b/netbox/netbox/models/features.py @@ -442,6 +442,19 @@ class SyncedDataMixin(models.Model): return ret + def delete(self, *args, **kwargs): + from core.models import AutoSyncRecord + + # Delete AutoSyncRecord + content_type = ContentType.objects.get_for_model(self) + AutoSyncRecord.objects.filter( + datafile=self.data_file, + object_type=content_type, + object_id=self.pk + ).delete() + + return super().delete(*args, **kwargs) + def resolve_data_file(self): """ Determine the designated DataFile object identified by its parent DataSource and its path. Returns None if From 88562d7dcfb642532d24134abf26c7bea4dc7180 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 4 Aug 2023 13:36:33 -0400 Subject: [PATCH 149/170] Changelog for #12750, #12889, #13033, #13151, #13343, #13369 --- docs/release-notes/version-3.5.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/release-notes/version-3.5.md b/docs/release-notes/version-3.5.md index 66c2cabce..4347d9837 100644 --- a/docs/release-notes/version-3.5.md +++ b/docs/release-notes/version-3.5.md @@ -2,6 +2,18 @@ ## v3.5.8 (FUTURE) +### Enhancements + +* [#12889](https://github.com/netbox-community/netbox/issues/12889) - Add 400GE CFP2 interface type +* [#13033](https://github.com/netbox-community/netbox/issues/13033) - Add human-friendly speed column to interfaces table +* [#13151](https://github.com/netbox-community/netbox/issues/13151) - Add "assigned" filter for IP addresses + +### Bug Fixes + +* [#12750](https://github.com/netbox-community/netbox/issues/12750) - Automatically delete an AutoSyncRecord when its object is deleted +* [#13343](https://github.com/netbox-community/netbox/issues/13343) - Fix filtering of circuits under provider network view +* [#13369](https://github.com/netbox-community/netbox/issues/13369) - Fix job termination status for failed reports + --- ## v3.5.7 (2023-07-28) From 0dd319d0c817ad71edf6542c4ffdac366e862822 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 4 Aug 2023 14:05:41 -0400 Subject: [PATCH 150/170] Closes #11675: Add support for specifying import/export route targets during VRF bulk import --- netbox/ipam/forms/bulk_import.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/netbox/ipam/forms/bulk_import.py b/netbox/ipam/forms/bulk_import.py index 683d40f49..3bce26249 100644 --- a/netbox/ipam/forms/bulk_import.py +++ b/netbox/ipam/forms/bulk_import.py @@ -1,7 +1,6 @@ from django import forms from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ValidationError -from django.db.models import Q from django.utils.translation import gettext as _ from dcim.models import Device, Interface, Site @@ -10,7 +9,9 @@ from ipam.constants import * from ipam.models import * from netbox.forms import NetBoxModelImportForm from tenancy.models import Tenant -from utilities.forms.fields import CSVChoiceField, CSVContentTypeField, CSVModelChoiceField, SlugField +from utilities.forms.fields import ( + CSVChoiceField, CSVContentTypeField, CSVModelChoiceField, CSVModelMultipleChoiceField, SlugField +) from virtualization.models import VirtualMachine, VMInterface __all__ = ( @@ -41,10 +42,25 @@ class VRFImportForm(NetBoxModelImportForm): to_field_name='name', help_text=_('Assigned tenant') ) + import_targets = CSVModelMultipleChoiceField( + queryset=RouteTarget.objects.all(), + required=False, + to_field_name='name', + help_text=_('Import route targets') + ) + export_targets = CSVModelMultipleChoiceField( + queryset=RouteTarget.objects.all(), + required=False, + to_field_name='name', + help_text=_('Export route targets') + ) class Meta: model = VRF - fields = ('name', 'rd', 'tenant', 'enforce_unique', 'description', 'comments', 'tags') + fields = ( + 'name', 'rd', 'tenant', 'enforce_unique', 'description', 'import_targets', 'export_targets', 'comments', + 'tags', + ) class RouteTargetImportForm(NetBoxModelImportForm): From 2236b86c35c1f6bfe34aa426cf24eaf0be404237 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 4 Aug 2023 14:37:40 -0400 Subject: [PATCH 151/170] Closes #11922: Populate assigned VDCs when adding a child interface --- netbox/dcim/forms/model_forms.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/netbox/dcim/forms/model_forms.py b/netbox/dcim/forms/model_forms.py index 219216045..3c02e6e4e 100644 --- a/netbox/dcim/forms/model_forms.py +++ b/netbox/dcim/forms/model_forms.py @@ -1042,6 +1042,9 @@ class InterfaceForm(InterfaceCommonForm, ModularDeviceComponentForm): queryset=VirtualDeviceContext.objects.all(), required=False, label='Virtual Device Contexts', + initial_params={ + 'interfaces': '$parent', + }, query_params={ 'device_id': '$device', } From f9648d854416590d98eb7b5d7cf6be18bd75b8cf Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 7 Aug 2023 10:48:41 -0400 Subject: [PATCH 152/170] Closes #13400: Add 'name' property to BaseTable class --- netbox/netbox/tables/tables.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/netbox/netbox/tables/tables.py b/netbox/netbox/tables/tables.py index 20eab822d..975311e4a 100644 --- a/netbox/netbox/tables/tables.py +++ b/netbox/netbox/tables/tables.py @@ -54,7 +54,7 @@ class BaseTable(tables.Table): # 3. Meta.fields selected_columns = None if user is not None and not isinstance(user, AnonymousUser): - selected_columns = user.config.get(f"tables.{self.__class__.__name__}.columns") + selected_columns = user.config.get(f"tables.{self.name}.columns") if not selected_columns: selected_columns = getattr(self.Meta, 'default_columns', self.Meta.fields) @@ -113,6 +113,10 @@ class BaseTable(tables.Table): columns.append((name, column.verbose_name)) return columns + @property + def name(self): + return self.__class__.__name__ + @property def available_columns(self): return self._get_columns(visible=False) @@ -138,17 +142,16 @@ class BaseTable(tables.Table): """ # Save ordering preference if request.user.is_authenticated: - table_name = self.__class__.__name__ if self.prefixed_order_by_field in request.GET: if request.GET[self.prefixed_order_by_field]: # If an ordering has been specified as a query parameter, save it as the # user's preferred ordering for this table. ordering = request.GET.getlist(self.prefixed_order_by_field) - request.user.config.set(f'tables.{table_name}.ordering', ordering, commit=True) + request.user.config.set(f'tables.{self.name}.ordering', ordering, commit=True) else: # If the ordering has been set to none (empty), clear any existing preference. - request.user.config.clear(f'tables.{table_name}.ordering', commit=True) - elif ordering := request.user.config.get(f'tables.{table_name}.ordering'): + request.user.config.clear(f'tables.{self.name}.ordering', commit=True) + elif ordering := request.user.config.get(f'tables.{self.name}.ordering'): # If no ordering has been specified, set the preferred ordering (if any). self.order_by = ordering From f5a1f83f9fa9d98c945d21eb0f7ccb8cd37fbf59 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 7 Aug 2023 15:29:20 -0400 Subject: [PATCH 153/170] Closes #13368: Report installed plugins during server error (#13387) * Introduce get_installed_plugins() utility * Extend 500 error template to list installed plugins * Move get_plugin_config() to extras.plugins.utils --- netbox/extras/plugins/__init__.py | 21 ---------------- netbox/extras/plugins/utils.py | 37 +++++++++++++++++++++++++++++ netbox/extras/tests/test_plugins.py | 3 ++- netbox/netbox/api/views.py | 11 ++------- netbox/netbox/views/errors.py | 3 +++ netbox/templates/500.html | 5 +++- 6 files changed, 48 insertions(+), 32 deletions(-) create mode 100644 netbox/extras/plugins/utils.py diff --git a/netbox/extras/plugins/__init__.py b/netbox/extras/plugins/__init__.py index 83c7a7bb0..8736a3197 100644 --- a/netbox/extras/plugins/__init__.py +++ b/netbox/extras/plugins/__init__.py @@ -2,7 +2,6 @@ import collections from importlib import import_module from django.apps import AppConfig -from django.conf import settings from django.core.exceptions import ImproperlyConfigured from django.utils.module_loading import import_string from packaging import version @@ -146,23 +145,3 @@ class PluginConfig(AppConfig): for setting, value in cls.default_settings.items(): if setting not in user_config: user_config[setting] = value - - -# -# Utilities -# - -def get_plugin_config(plugin_name, parameter, default=None): - """ - Return the value of the specified plugin configuration parameter. - - Args: - plugin_name: The name of the plugin - parameter: The name of the configuration parameter - default: The value to return if the parameter is not defined (default: None) - """ - try: - plugin_config = settings.PLUGINS_CONFIG[plugin_name] - return plugin_config.get(parameter, default) - except KeyError: - raise ImproperlyConfigured(f"Plugin {plugin_name} is not registered.") diff --git a/netbox/extras/plugins/utils.py b/netbox/extras/plugins/utils.py new file mode 100644 index 000000000..c260f156d --- /dev/null +++ b/netbox/extras/plugins/utils.py @@ -0,0 +1,37 @@ +from django.apps import apps +from django.conf import settings +from django.core.exceptions import ImproperlyConfigured + +__all__ = ( + 'get_installed_plugins', + 'get_plugin_config', +) + + +def get_installed_plugins(): + """ + Return a dictionary mapping the names of installed plugins to their versions. + """ + plugins = {} + for plugin_name in settings.PLUGINS: + plugin_name = plugin_name.rsplit('.', 1)[-1] + plugin_config = apps.get_app_config(plugin_name) + plugins[plugin_name] = getattr(plugin_config, 'version', None) + + return dict(sorted(plugins.items())) + + +def get_plugin_config(plugin_name, parameter, default=None): + """ + Return the value of the specified plugin configuration parameter. + + Args: + plugin_name: The name of the plugin + parameter: The name of the configuration parameter + default: The value to return if the parameter is not defined (default: None) + """ + try: + plugin_config = settings.PLUGINS_CONFIG[plugin_name] + return plugin_config.get(parameter, default) + except KeyError: + raise ImproperlyConfigured(f"Plugin {plugin_name} is not registered.") diff --git a/netbox/extras/tests/test_plugins.py b/netbox/extras/tests/test_plugins.py index cb7629ad2..42dde43fd 100644 --- a/netbox/extras/tests/test_plugins.py +++ b/netbox/extras/tests/test_plugins.py @@ -5,8 +5,9 @@ from django.core.exceptions import ImproperlyConfigured from django.test import Client, TestCase, override_settings from django.urls import reverse -from extras.plugins import PluginMenu, get_plugin_config +from extras.plugins import PluginMenu from extras.tests.dummy_plugin import config as dummy_config +from extras.plugins.utils import get_plugin_config from netbox.graphql.schema import Query from netbox.registry import registry diff --git a/netbox/netbox/api/views.py b/netbox/netbox/api/views.py index 5c55697ff..97f690762 100644 --- a/netbox/netbox/api/views.py +++ b/netbox/netbox/api/views.py @@ -11,6 +11,7 @@ from rest_framework.reverse import reverse from rest_framework.views import APIView from rq.worker import Worker +from extras.plugins.utils import get_installed_plugins from netbox.api.authentication import IsAuthenticatedOrLoginNotRequired @@ -61,19 +62,11 @@ class StatusView(APIView): installed_apps[app_config.name] = version installed_apps = {k: v for k, v in sorted(installed_apps.items())} - # Gather installed plugins - plugins = {} - for plugin_name in settings.PLUGINS: - plugin_name = plugin_name.rsplit('.', 1)[-1] - plugin_config = apps.get_app_config(plugin_name) - plugins[plugin_name] = getattr(plugin_config, 'version', None) - plugins = {k: v for k, v in sorted(plugins.items())} - return Response({ 'django-version': DJANGO_VERSION, 'installed-apps': installed_apps, 'netbox-version': settings.VERSION, - 'plugins': plugins, + 'plugins': get_installed_plugins(), 'python-version': platform.python_version(), 'rq-workers-running': Worker.count(get_connection('default')), }) diff --git a/netbox/netbox/views/errors.py b/netbox/netbox/views/errors.py index c74c67cef..a81d45cb5 100644 --- a/netbox/netbox/views/errors.py +++ b/netbox/netbox/views/errors.py @@ -11,6 +11,8 @@ from django.views.defaults import ERROR_500_TEMPLATE_NAME, page_not_found from django.views.generic import View from sentry_sdk import capture_message +from extras.plugins.utils import get_installed_plugins + __all__ = ( 'handler_404', 'handler_500', @@ -53,4 +55,5 @@ def handler_500(request, template_name=ERROR_500_TEMPLATE_NAME): 'exception': str(type_), 'netbox_version': settings.VERSION, 'python_version': platform.python_version(), + 'plugins': get_installed_plugins(), })) diff --git a/netbox/templates/500.html b/netbox/templates/500.html index 6cface941..0257e7c43 100644 --- a/netbox/templates/500.html +++ b/netbox/templates/500.html @@ -30,7 +30,10 @@ {{ error }} Python version: {{ python_version }} -NetBox version: {{ netbox_version }} +NetBox version: {{ netbox_version }} +Plugins: {% for plugin, version in plugins.items %} + {{ plugin }}: {{ version }}{% empty %}None installed{% endfor %} +

If further assistance is required, please post to the NetBox discussion forum on GitHub.

From 545769ad884e473651673168d74b80ba2412a62b Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Wed, 9 Aug 2023 23:46:03 +0530 Subject: [PATCH 154/170] Adds generic object children template (#13388) * adds generic tab view template #12110 * Rename view_tab.html and move to generic/ * Fix console ports template * Move bulk operations view resolution to template * Avoid setting default template_name on ObjectChildrenView * Move base_template and table_config context vars to base context * removed bulk_delete_control from templates * refactored bulk_controls view * fixed table_config * renamed object_tab.html to objectchildren_list.html * removed unused import * Refactor template blocks for bulk operation buttons * Rename object children generic template * Move disconnect bulk action into a separate template for device components * Fix cluster devices & VM interfaces views * minor button label change --------- Co-authored-by: Jeremy Stretch --- netbox/dcim/views.py | 13 +++ netbox/ipam/views.py | 10 +-- netbox/netbox/views/generic/object_views.py | 3 + .../dcim/device/components_base.html | 15 ++++ .../templates/dcim/device/consoleports.html | 74 +++++------------ .../dcim/device/consoleserverports.html | 74 +++++------------ netbox/templates/dcim/device/devicebays.html | 57 +++---------- netbox/templates/dcim/device/frontports.html | 74 +++++------------ netbox/templates/dcim/device/interfaces.html | 83 +++++-------------- netbox/templates/dcim/device/inventory.html | 57 +++---------- netbox/templates/dcim/device/modulebays.html | 53 +++--------- .../templates/dcim/device/poweroutlets.html | 74 +++++------------ netbox/templates/dcim/device/powerports.html | 74 +++++------------ netbox/templates/dcim/device/rearports.html | 74 +++++------------ .../dcim/rack/non_racked_devices.html | 43 +--------- netbox/templates/dcim/rack/reservations.html | 49 ++--------- netbox/templates/generic/object_children.html | 57 +++++++++++++ netbox/templates/ipam/aggregate/prefixes.html | 39 +-------- netbox/templates/ipam/asnrange/asns.html | 36 -------- .../ipam/ipaddress/ip_addresses.html | 19 ----- .../templates/ipam/iprange/ip_addresses.html | 41 +-------- .../templates/ipam/prefix/ip_addresses.html | 39 +-------- netbox/templates/ipam/prefix/ip_ranges.html | 39 +-------- netbox/templates/ipam/prefix/prefixes.html | 40 +-------- netbox/templates/ipam/vlan/interfaces.html | 20 ----- netbox/templates/ipam/vlan/vminterfaces.html | 20 ----- netbox/templates/tenancy/object_contacts.html | 19 +---- .../virtualization/cluster/devices.html | 37 +++------ .../cluster/virtual_machines.html | 35 -------- .../virtualmachine/interfaces.html | 54 +++--------- netbox/tenancy/views.py | 5 -- netbox/virtualization/views.py | 19 ++++- 32 files changed, 337 insertions(+), 1009 deletions(-) create mode 100644 netbox/templates/dcim/device/components_base.html create mode 100644 netbox/templates/generic/object_children.html delete mode 100644 netbox/templates/ipam/asnrange/asns.html delete mode 100644 netbox/templates/ipam/ipaddress/ip_addresses.html delete mode 100644 netbox/templates/ipam/vlan/interfaces.html delete mode 100644 netbox/templates/ipam/vlan/vminterfaces.html delete mode 100644 netbox/templates/virtualization/cluster/virtual_machines.html diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 5b93e5f0b..fca222f47 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -1,4 +1,5 @@ import traceback +from collections import defaultdict from django.contrib import messages from django.contrib.contenttypes.models import ContentType @@ -45,6 +46,15 @@ CABLE_TERMINATION_TYPES = { class DeviceComponentsView(generic.ObjectChildrenView): + actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete', 'bulk_rename', 'bulk_disconnect') + action_perms = defaultdict(set, **{ + 'add': {'add'}, + 'import': {'add'}, + 'bulk_edit': {'change'}, + 'bulk_delete': {'delete'}, + 'bulk_rename': {'change'}, + 'bulk_disconnect': {'change'}, + }) queryset = Device.objects.all() def get_children(self, request, parent): @@ -1997,6 +2007,7 @@ class DeviceModuleBaysView(DeviceComponentsView): table = tables.DeviceModuleBayTable filterset = filtersets.ModuleBayFilterSet template_name = 'dcim/device/modulebays.html' + actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete', 'bulk_rename') tab = ViewTab( label=_('Module Bays'), badge=lambda obj: obj.modulebays.count(), @@ -2012,6 +2023,7 @@ class DeviceDeviceBaysView(DeviceComponentsView): table = tables.DeviceDeviceBayTable filterset = filtersets.DeviceBayFilterSet template_name = 'dcim/device/devicebays.html' + actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete', 'bulk_rename') tab = ViewTab( label=_('Device Bays'), badge=lambda obj: obj.devicebays.count(), @@ -2023,6 +2035,7 @@ class DeviceDeviceBaysView(DeviceComponentsView): @register_model_view(Device, 'inventory') class DeviceInventoryView(DeviceComponentsView): + actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete', 'bulk_rename') child_model = InventoryItem table = tables.DeviceInventoryItemTable filterset = filtersets.InventoryItemFilterSet diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index 32badd2d5..d8e4d8b47 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -216,7 +216,7 @@ class ASNRangeASNsView(generic.ObjectChildrenView): child_model = ASN table = tables.ASNTable filterset = filtersets.ASNFilterSet - template_name = 'ipam/asnrange/asns.html' + template_name = 'generic/object_children.html' tab = ViewTab( label=_('ASNs'), badge=lambda x: x.get_child_asns().count(), @@ -816,7 +816,6 @@ class IPAddressAssignView(generic.ObjectView): table = None if form.is_valid(): - addresses = self.queryset.prefetch_related('vrf', 'tenant') # Limit to 100 results addresses = filtersets.IPAddressFilterSet(request.POST, addresses).qs[:100] @@ -866,7 +865,7 @@ class IPAddressRelatedIPsView(generic.ObjectChildrenView): child_model = IPAddress table = tables.IPAddressTable filterset = filtersets.IPAddressFilterSet - template_name = 'ipam/ipaddress/ip_addresses.html' + template_name = 'generic/object_children.html' tab = ViewTab( label=_('Related IPs'), badge=lambda x: x.get_related_ips().count(), @@ -963,7 +962,6 @@ class FHRPGroupView(generic.ObjectView): queryset = FHRPGroup.objects.all() def get_extra_context(self, request, instance): - # Get assigned interfaces members_table = tables.FHRPGroupAssignmentTable( data=FHRPGroupAssignment.objects.restrict(request.user, 'view').filter(group=instance), @@ -1077,7 +1075,7 @@ class VLANInterfacesView(generic.ObjectChildrenView): child_model = Interface table = tables.VLANDevicesTable filterset = InterfaceFilterSet - template_name = 'ipam/vlan/interfaces.html' + template_name = 'generic/object_children.html' tab = ViewTab( label=_('Device Interfaces'), badge=lambda x: x.get_interfaces().count(), @@ -1095,7 +1093,7 @@ class VLANVMInterfacesView(generic.ObjectChildrenView): child_model = VMInterface table = tables.VLANVirtualMachinesTable filterset = VMInterfaceFilterSet - template_name = 'ipam/vlan/vminterfaces.html' + template_name = 'generic/object_children.html' tab = ViewTab( label=_('VM Interfaces'), badge=lambda x: x.get_vminterfaces().count(), diff --git a/netbox/netbox/views/generic/object_views.py b/netbox/netbox/views/generic/object_views.py index 1ba789cf1..99d8ff540 100644 --- a/netbox/netbox/views/generic/object_views.py +++ b/netbox/netbox/views/generic/object_views.py @@ -143,9 +143,12 @@ class ObjectChildrenView(ObjectView, ActionsMixin, TableMixin): return render(request, self.get_template_name(), { 'object': instance, 'child_model': self.child_model, + 'base_template': f'{instance._meta.app_label}/{instance._meta.model_name}.html', 'table': table, + 'table_config': f'{table.name}_config', 'actions': actions, 'tab': self.tab, + 'return_url': request.get_full_path(), **self.get_extra_context(request, instance), }) diff --git a/netbox/templates/dcim/device/components_base.html b/netbox/templates/dcim/device/components_base.html new file mode 100644 index 000000000..1e3d8a39d --- /dev/null +++ b/netbox/templates/dcim/device/components_base.html @@ -0,0 +1,15 @@ +{% extends 'generic/object_children.html' %} +{% load helpers %} + +{% block bulk_edit_controls %} + {{ block.super }} + {% with bulk_rename_view=child_model|validated_viewname:"bulk_rename" %} + {% if 'bulk_rename' in actions and bulk_rename_view %} + + {% endif %} + {% endwith %} +{% endblock bulk_edit_controls %} diff --git a/netbox/templates/dcim/device/consoleports.html b/netbox/templates/dcim/device/consoleports.html index ccd12f61c..6e1c1b699 100644 --- a/netbox/templates/dcim/device/consoleports.html +++ b/netbox/templates/dcim/device/consoleports.html @@ -1,57 +1,27 @@ -{% extends 'dcim/device/base.html' %} -{% load render_table from django_tables2 %} +{% extends 'dcim/device/components_base.html' %} {% load helpers %} -{% load static %} -{% block content %} - {% include 'inc/table_controls_htmx.html' with table_modal="DeviceConsolePortTable_config" %} - -
- {% csrf_token %} - -
-
- {% include 'htmx/table.html' %} -
-
- -
-
- {% if 'bulk_edit' in actions %} -
- - -
{% endif %} -
- {% if 'bulk_delete' in actions %} - - {% endif %} - {% if 'bulk_edit' in actions %} - - {% endif %} -
-
- {% if perms.dcim.add_consoleport %} - - {% endif %} -
-
-{% endblock %} + {% endwith %} +{% endblock bulk_delete_controls %} -{% block modals %} - {{ block.super }} - {% table_config_form table %} -{% endblock modals %} +{% block bulk_extra_controls %} + {{ block.super }} + {% if perms.dcim.add_consoleport %} + + {% endif %} +{% endblock bulk_extra_controls %} diff --git a/netbox/templates/dcim/device/consoleserverports.html b/netbox/templates/dcim/device/consoleserverports.html index 43396651d..637f06118 100644 --- a/netbox/templates/dcim/device/consoleserverports.html +++ b/netbox/templates/dcim/device/consoleserverports.html @@ -1,57 +1,27 @@ -{% extends 'dcim/device/base.html' %} -{% load render_table from django_tables2 %} +{% extends 'dcim/device/components_base.html' %} {% load helpers %} -{% load static %} -{% block content %} - {% include 'inc/table_controls_htmx.html' with table_modal="DeviceConsoleServerPortTable_config" %} - -
- {% csrf_token %} - -
-
- {% include 'htmx/table.html' %} -
-
- -
-
- {% if 'bulk_edit' in actions %} -
- - -
{% endif %} -
- {% if 'bulk_delete' in actions %} - - {% endif %} - {% if 'bulk_edit' in actions %} - - {% endif %} -
-
- {% if perms.dcim.add_consoleserverport %} - - {% endif %} -
-
-{% endblock %} + {% endwith %} +{% endblock bulk_delete_controls %} -{% block modals %} - {{ block.super }} - {% table_config_form table %} -{% endblock modals %} +{% block bulk_extra_controls %} + {{ block.super }} + {% if perms.dcim.add_consoleserverport %} + + {% endif %} +{% endblock bulk_extra_controls %} \ No newline at end of file diff --git a/netbox/templates/dcim/device/devicebays.html b/netbox/templates/dcim/device/devicebays.html index 9453b9a59..0a7bbba7f 100644 --- a/netbox/templates/dcim/device/devicebays.html +++ b/netbox/templates/dcim/device/devicebays.html @@ -1,50 +1,13 @@ -{% extends 'dcim/device/base.html' %} -{% load render_table from django_tables2 %} -{% load helpers %} -{% load static %} +{% extends 'dcim/device/components_base.html' %} -{% block content %} - {% include 'inc/table_controls_htmx.html' with table_modal="DeviceDeviceBayTable_config" %} - -
- {% csrf_token %} - -
-
- {% include 'htmx/table.html' %} -
-
- -
-
- {% if 'bulk_edit' in actions %} -
- - -
- {% endif %} - {% if 'bulk_delete' in actions %} - - {% endif %} -
- {% if perms.dcim.add_devicebay %} +{% block bulk_extra_controls %} + {{ block.super }} + {% if perms.dcim.add_devicebay %} - {% endif %} -
-
-{% endblock %} - -{% block modals %} - {{ block.super }} - {% table_config_form table %} -{% endblock modals %} + {% endif %} +{% endblock bulk_extra_controls %} diff --git a/netbox/templates/dcim/device/frontports.html b/netbox/templates/dcim/device/frontports.html index dd0767d95..453064611 100644 --- a/netbox/templates/dcim/device/frontports.html +++ b/netbox/templates/dcim/device/frontports.html @@ -1,57 +1,27 @@ -{% extends 'dcim/device/base.html' %} -{% load render_table from django_tables2 %} +{% extends 'dcim/device/components_base.html' %} {% load helpers %} -{% load static %} -{% block content %} - {% include 'inc/table_controls_htmx.html' with table_modal="DeviceFrontPortTable_config" %} - -
- {% csrf_token %} - -
-
- {% include 'htmx/table.html' %} -
-
- -
-
- {% if 'bulk_edit' in actions %} -
- - -
{% endif %} -
- {% if 'bulk_delete' in actions %} - - {% endif %} - {% if 'bulk_edit' in actions %} - - {% endif %} -
-
- {% if perms.dcim.add_frontport %} - - {% endif %} -
-
-{% endblock %} + {% endwith %} +{% endblock bulk_delete_controls %} -{% block modals %} - {{ block.super }} - {% table_config_form table %} -{% endblock modals %} +{% block bulk_extra_controls %} + {{ block.super }} + {% if perms.dcim.add_frontport %} + + {% endif %} +{% endblock bulk_extra_controls %} \ No newline at end of file diff --git a/netbox/templates/dcim/device/interfaces.html b/netbox/templates/dcim/device/interfaces.html index c0e9a38b6..778101265 100644 --- a/netbox/templates/dcim/device/interfaces.html +++ b/netbox/templates/dcim/device/interfaces.html @@ -1,66 +1,27 @@ -{% extends 'dcim/device/base.html' %} -{% load render_table from django_tables2 %} +{% extends 'dcim/device/components_base.html' %} {% load helpers %} -{% load static %} -{% block content %} - {% include 'dcim/device/inc/interface_table_controls.html' with table_modal="DeviceInterfaceTable_config" %} - -
- {% csrf_token %} - -
-
- {% include 'htmx/table.html' %} -
-
- -
-
- {% if 'bulk_edit' in actions %} -
- - -
- {% endif %} -
- {% if 'bulk_delete' in actions %} - +{% block bulk_delete_controls %} + {{ block.super }} + {% with bulk_disconnect_view=child_model|validated_viewname:"bulk_disconnect" %} + {% if 'bulk_disconnect' in actions and bulk_disconnect_view %} + {% endif %} - {% if 'bulk_edit' in actions %} - - {% endif %} -
-
+ {% endwith %} +{% endblock bulk_delete_controls %} + +{% block bulk_extra_controls %} + {{ block.super }} {% if perms.dcim.add_interface %} - + {% endif %} -
-
-{% endblock %} - -{% block modals %} - {{ block.super }} - {% table_config_form table %} -{% endblock modals %} +{% endblock bulk_extra_controls %} diff --git a/netbox/templates/dcim/device/inventory.html b/netbox/templates/dcim/device/inventory.html index 9e11031ec..d4c9a9b68 100644 --- a/netbox/templates/dcim/device/inventory.html +++ b/netbox/templates/dcim/device/inventory.html @@ -1,50 +1,13 @@ -{% extends 'dcim/device/base.html' %} -{% load render_table from django_tables2 %} -{% load helpers %} -{% load static %} +{% extends 'dcim/device/components_base.html' %} -{% block content %} - {% include 'inc/table_controls_htmx.html' with table_modal="DeviceInventoryItemTable_config" %} - -
- {% csrf_token %} - -
-
- {% include 'htmx/table.html' %} -
-
- -
-
- {% if 'bulk_edit' in actions %} -
- - -
- {% endif %} - {% if 'bulk_delete' in actions %} - - {% endif %} -
- {% if perms.dcim.add_inventoryitem %} +{% block bulk_extra_controls %} + {{ block.super }} + {% if perms.dcim.add_inventoryitem %} - {% endif %} -
-
-{% endblock %} - -{% block modals %} - {{ block.super }} - {% table_config_form table %} -{% endblock modals %} + {% endif %} +{% endblock bulk_extra_controls %} diff --git a/netbox/templates/dcim/device/modulebays.html b/netbox/templates/dcim/device/modulebays.html index 7f0aacf1f..fc616f828 100644 --- a/netbox/templates/dcim/device/modulebays.html +++ b/netbox/templates/dcim/device/modulebays.html @@ -1,46 +1,13 @@ -{% extends 'dcim/device/base.html' %} -{% load render_table from django_tables2 %} -{% load helpers %} -{% load static %} +{% extends 'dcim/device/components_base.html' %} -{% block content %} - {% include 'inc/table_controls_htmx.html' with table_modal="DeviceModuleBayTable_config" %} - -
- {% csrf_token %} - -
-
- {% include 'htmx/table.html' %} -
-
- -
-
- {% if 'bulk_edit' in actions %} -
- - -
- {% endif %} - {% if 'bulk_delete' in actions %} - - {% endif %} -
- {% if perms.dcim.add_modulebay %} +{% block bulk_extra_controls %} + {{ block.super }} + {% if perms.dcim.add_modulebay %} - {% endif %} -
-
- {% table_config_form table %} -{% endblock %} + {% endif %} +{% endblock bulk_extra_controls %} \ No newline at end of file diff --git a/netbox/templates/dcim/device/poweroutlets.html b/netbox/templates/dcim/device/poweroutlets.html index 66b21b7af..f31067453 100644 --- a/netbox/templates/dcim/device/poweroutlets.html +++ b/netbox/templates/dcim/device/poweroutlets.html @@ -1,57 +1,27 @@ -{% extends 'dcim/device/base.html' %} -{% load render_table from django_tables2 %} +{% extends 'dcim/device/components_base.html' %} {% load helpers %} -{% load static %} -{% block content %} - {% include 'inc/table_controls_htmx.html' with table_modal="DevicePowerOutletTable_config" %} - -
- {% csrf_token %} - -
-
- {% include 'htmx/table.html' %} -
-
- -
-
- {% if 'bulk_edit' in actions %} -
- - -
{% endif %} -
- {% if 'bulk_delete' in actions %} - - {% endif %} - {% if 'bulk_edit' in actions %} - - {% endif %} -
-
- {% if perms.dcim.add_poweroutlet %} - - {% endif %} -
-
-{% endblock %} + {% endwith %} +{% endblock bulk_delete_controls %} -{% block modals %} - {{ block.super }} - {% table_config_form table %} -{% endblock modals %} +{% block bulk_extra_controls %} + {{ block.super }} + {% if perms.dcim.add_poweroutlet %} + + {% endif %} +{% endblock bulk_extra_controls %} diff --git a/netbox/templates/dcim/device/powerports.html b/netbox/templates/dcim/device/powerports.html index d9e1e121a..ad1dbacd8 100644 --- a/netbox/templates/dcim/device/powerports.html +++ b/netbox/templates/dcim/device/powerports.html @@ -1,57 +1,27 @@ -{% extends 'dcim/device/base.html' %} -{% load render_table from django_tables2 %} +{% extends 'dcim/device/components_base.html' %} {% load helpers %} -{% load static %} -{% block content %} - {% include 'inc/table_controls_htmx.html' with table_modal="DevicePowerPortTable_config" %} - -
- {% csrf_token %} - -
-
- {% include 'htmx/table.html' %} -
-
- -
-
- {% if 'bulk_edit' in actions %} -
- - -
{% endif %} -
- {% if 'bulk_delete' in actions %} - - {% endif %} - {% if 'bulk_edit' in actions %} - - {% endif %} -
-
- {% if perms.dcim.add_powerport %} - - {% endif %} -
-
-{% endblock %} + {% endwith %} +{% endblock bulk_delete_controls %} -{% block modals %} - {{ block.super }} - {% table_config_form table %} -{% endblock modals %} +{% block bulk_extra_controls %} + {{ block.super }} + {% if perms.dcim.add_powerport %} + + {% endif %} +{% endblock bulk_extra_controls %} diff --git a/netbox/templates/dcim/device/rearports.html b/netbox/templates/dcim/device/rearports.html index ce194cc78..dfa406386 100644 --- a/netbox/templates/dcim/device/rearports.html +++ b/netbox/templates/dcim/device/rearports.html @@ -1,57 +1,27 @@ -{% extends 'dcim/device/base.html' %} -{% load render_table from django_tables2 %} -{% load static %} +{% extends 'dcim/device/components_base.html' %} {% load helpers %} -{% block content %} - {% include 'inc/table_controls_htmx.html' with table_modal="DeviceRearPortTable_config" %} - -
- {% csrf_token %} - -
-
- {% include 'htmx/table.html' %} -
-
- -
-
- {% if 'bulk_edit' in actions %} -
- - -
{% endif %} -
- {% if 'bulk_delete' in actions %} - - {% endif %} - {% if 'bulk_edit' in actions %} - - {% endif %} -
-
- {% if perms.dcim.add_rearport %} - - {% endif %} -
-
-{% endblock %} + {% endwith %} +{% endblock bulk_delete_controls %} -{% block modals %} - {{ block.super }} - {% table_config_form table %} -{% endblock modals %} +{% block bulk_extra_controls %} + {{ block.super }} + {% if perms.dcim.add_rearport %} + + {% endif %} +{% endblock bulk_extra_controls %} \ No newline at end of file diff --git a/netbox/templates/dcim/rack/non_racked_devices.html b/netbox/templates/dcim/rack/non_racked_devices.html index 700c66369..e52b8647f 100644 --- a/netbox/templates/dcim/rack/non_racked_devices.html +++ b/netbox/templates/dcim/rack/non_racked_devices.html @@ -1,5 +1,4 @@ -{% extends 'dcim/rack/base.html' %} -{% load helpers %} +{% extends 'generic/object_children.html' %} {% block extra_controls %} {% if perms.dcim.add_device %} @@ -10,42 +9,4 @@
{% endif %} -{% endblock %} - -{% block content %} - {% include 'inc/table_controls_htmx.html' with table_modal="DeviceTable_config" %} - -
- {% csrf_token %} - -
-
- {% include 'htmx/table.html' %} -
-
- -
-
- {% if 'bulk_edit' in actions %} - - {% endif %} - {% if 'bulk_delete' in actions %} - - {% endif %} -
-
-
-{% endblock content %} - -{% block modals %} - {{ block.super }} - {% table_config_form table %} -{% endblock modals %} +{% endblock extra_controls %} diff --git a/netbox/templates/dcim/rack/reservations.html b/netbox/templates/dcim/rack/reservations.html index fb357e592..a01cf3b7e 100644 --- a/netbox/templates/dcim/rack/reservations.html +++ b/netbox/templates/dcim/rack/reservations.html @@ -1,43 +1,12 @@ -{% extends 'dcim/rack/base.html' %} -{% load helpers %} +{% extends 'generic/object_children.html' %} -{% block content %} - {% include 'inc/table_controls_htmx.html' with table_modal="RackReservationTable_config" %} - -
- {% csrf_token %} - -
-
- {% include 'htmx/table.html' %} -
-
- -
-
- {% if 'bulk_edit' in actions %} - - {% endif %} - {% if 'bulk_delete' in actions %} - - {% endif %} -
- {% if perms.dcim.add_rackreservation %} +{% block extra_controls %} + {% if perms.dcim.add_rackreservation %} - {% endif %} -
-
-{% endblock %} - -{% block modals %} - {{ block.super }} - {% table_config_form table %} -{% endblock modals %} + {% endif %} +{% endblock extra_controls %} diff --git a/netbox/templates/generic/object_children.html b/netbox/templates/generic/object_children.html new file mode 100644 index 000000000..eb5c65827 --- /dev/null +++ b/netbox/templates/generic/object_children.html @@ -0,0 +1,57 @@ +{% extends base_template %} +{% load helpers %} + +{% block content %} + {% include 'inc/table_controls_htmx.html' with table_modal=table_config %} +
+ {% csrf_token %} +
+
+ {% include 'htmx/table.html' %} +
+
+
+ {% block bulk_controls %} +
+
+ {# Bulk edit buttons #} + {% block bulk_edit_controls %} + {% with bulk_edit_view=child_model|validated_viewname:"bulk_edit" %} + {% if 'bulk_edit' in actions and bulk_edit_view %} + + {% endif %} + {% endwith %} + {% endblock bulk_edit_controls %} +
+
+ {# Bulk delete buttons #} + {% block bulk_delete_controls %} + {% with bulk_delete_view=child_model|validated_viewname:"bulk_delete" %} + {% if 'bulk_delete' in actions and bulk_delete_view %} + + {% endif %} + {% endwith %} + {% endblock bulk_delete_controls %} +
+
+
+ {# Other bulk action buttons #} + {% block bulk_extra_controls %}{% endblock %} +
+ {% endblock bulk_controls %} +
+
+{% endblock content %} + +{% block modals %} + {{ block.super }} + {% table_config_form table %} +{% endblock modals %} diff --git a/netbox/templates/ipam/aggregate/prefixes.html b/netbox/templates/ipam/aggregate/prefixes.html index a1d3bd276..7820e121e 100644 --- a/netbox/templates/ipam/aggregate/prefixes.html +++ b/netbox/templates/ipam/aggregate/prefixes.html @@ -1,5 +1,4 @@ -{% extends 'ipam/aggregate/base.html' %} -{% load helpers %} +{% extends 'generic/object_children.html' %} {% block extra_controls %} {% include 'ipam/inc/toggle_available.html' %} @@ -9,38 +8,4 @@ {% endif %} {{ block.super }} -{% endblock %} - -{% block content %} - {% include 'inc/table_controls_htmx.html' with table_modal="PrefixTable_config" %} - -
- {% csrf_token %} - -
-
- {% include 'htmx/table.html' %} -
-
- -
-
- {% if 'bulk_edit' in actions %} - - {% endif %} - {% if 'bulk_delete' in actions %} - - {% endif %} -
-
-
-{% endblock %} - -{% block modals %} - {{ block.super }} - {% table_config_form table %} -{% endblock modals %} +{% endblock extra_controls %} diff --git a/netbox/templates/ipam/asnrange/asns.html b/netbox/templates/ipam/asnrange/asns.html deleted file mode 100644 index 69d4e8abb..000000000 --- a/netbox/templates/ipam/asnrange/asns.html +++ /dev/null @@ -1,36 +0,0 @@ -{% extends 'ipam/asnrange/base.html' %} -{% load helpers %} - -{% block content %} - {% include 'inc/table_controls_htmx.html' with table_modal="ASNTable_config" %} - -
- {% csrf_token %} - -
-
- {% include 'htmx/table.html' %} -
-
- -
-
- {% if 'bulk_edit' in actions %} - - {% endif %} - {% if 'bulk_delete' in actions %} - - {% endif %} -
-
-
-{% endblock %} - -{% block modals %} - {{ block.super }} - {% table_config_form table %} -{% endblock modals %} diff --git a/netbox/templates/ipam/ipaddress/ip_addresses.html b/netbox/templates/ipam/ipaddress/ip_addresses.html deleted file mode 100644 index b82ec2375..000000000 --- a/netbox/templates/ipam/ipaddress/ip_addresses.html +++ /dev/null @@ -1,19 +0,0 @@ -{% extends 'ipam/ipaddress/base.html' %} -{% load helpers %} - -{% block content %} - {% include 'inc/table_controls_htmx.html' with table_modal="IPAddressTable_config" %} -
- {% csrf_token %} -
-
- {% include 'htmx/table.html' %} -
-
-
-{% endblock %} - -{% block modals %} - {{ block.super }} - {% table_config_form table %} -{% endblock modals %} diff --git a/netbox/templates/ipam/iprange/ip_addresses.html b/netbox/templates/ipam/iprange/ip_addresses.html index 9f77f6c78..869fd0fa1 100644 --- a/netbox/templates/ipam/iprange/ip_addresses.html +++ b/netbox/templates/ipam/iprange/ip_addresses.html @@ -1,44 +1,9 @@ -{% extends 'ipam/iprange/base.html' %} -{% load helpers %} +{% extends 'generic/object_children.html' %} {% block extra_controls %} - {% if perms.ipam.add_ipaddress and active_tab == 'ip-addresses' and object.first_available_ip %} + {% if perms.ipam.add_ipaddress and object.first_available_ip %} Add IP Address {% endif %} -{% endblock %} - -{% block content %} - {% include 'inc/table_controls_htmx.html' with table_modal="IPAddressTable_config" %} - -
- {% csrf_token %} - -
-
- {% include 'htmx/table.html' %} -
-
- -
-
- {% if 'bulk_edit' in actions %} - - {% endif %} - {% if 'bulk_delete' in actions %} - - {% endif %} -
-
-
-{% endblock %} - -{% block modals %} - {{ block.super }} - {% table_config_form table %} -{% endblock modals %} +{% endblock extra_controls %} diff --git a/netbox/templates/ipam/prefix/ip_addresses.html b/netbox/templates/ipam/prefix/ip_addresses.html index fe68039f8..f9d5febbe 100644 --- a/netbox/templates/ipam/prefix/ip_addresses.html +++ b/netbox/templates/ipam/prefix/ip_addresses.html @@ -1,5 +1,4 @@ -{% extends 'ipam/prefix/base.html' %} -{% load helpers %} +{% extends 'generic/object_children.html' %} {% block extra_controls %} {% if perms.ipam.add_ipaddress and first_available_ip %} @@ -7,38 +6,4 @@ Add IP Address {% endif %} -{% endblock %} - -{% block content %} - {% include 'inc/table_controls_htmx.html' with table_modal="IPAddressTable_config" %} - -
- {% csrf_token %} - -
-
- {% include 'htmx/table.html' %} -
-
- -
-
- {% if 'bulk_edit' in actions %} - - {% endif %} - {% if 'bulk_delete' in actions %} - - {% endif %} -
-
-
-{% endblock %} - -{% block modals %} - {{ block.super }} - {% table_config_form table %} -{% endblock modals %} +{% endblock extra_controls %} diff --git a/netbox/templates/ipam/prefix/ip_ranges.html b/netbox/templates/ipam/prefix/ip_ranges.html index 4452fd5a7..8371de81d 100644 --- a/netbox/templates/ipam/prefix/ip_ranges.html +++ b/netbox/templates/ipam/prefix/ip_ranges.html @@ -1,5 +1,4 @@ -{% extends 'ipam/prefix/base.html' %} -{% load helpers %} +{% extends 'generic/object_children.html' %} {% block extra_controls %} {% if perms.ipam.add_iprange and first_available_ip %} @@ -7,38 +6,4 @@ Add IP Range {% endif %} -{% endblock %} - -{% block content %} - {% include 'inc/table_controls_htmx.html' with table_modal="IPRangeTable_config" %} - -
- {% csrf_token %} - -
-
- {% include 'htmx/table.html' %} -
-
- -
-
- {% if 'bulk_edit' in actions %} - - {% endif %} - {% if 'bulk_delete' in actions %} - - {% endif %} -
-
-
-{% endblock %} - -{% block modals %} - {{ block.super }} - {% table_config_form table %} -{% endblock modals %} +{% endblock extra_controls %} diff --git a/netbox/templates/ipam/prefix/prefixes.html b/netbox/templates/ipam/prefix/prefixes.html index 5fc931f74..41407e870 100644 --- a/netbox/templates/ipam/prefix/prefixes.html +++ b/netbox/templates/ipam/prefix/prefixes.html @@ -1,5 +1,4 @@ -{% extends 'ipam/prefix/base.html' %} -{% load helpers %} +{% extends 'generic/object_children.html' %} {% block extra_controls %} {% include 'ipam/inc/toggle_available.html' %} @@ -8,39 +7,4 @@ Add Prefix {% endif %} - {{ block.super }} -{% endblock %} - -{% block content %} - {% include 'inc/table_controls_htmx.html' with table_modal="PrefixTable_config" %} - -
- {% csrf_token %} - -
-
- {% include 'htmx/table.html' %} -
-
- -
-
- {% if 'bulk_edit' in actions %} - - {% endif %} - {% if 'bulk_delete' in actions %} - - {% endif %} -
-
-
-{% endblock %} - -{% block modals %} - {{ block.super }} - {% table_config_form table %} -{% endblock modals %} +{% endblock extra_controls %} diff --git a/netbox/templates/ipam/vlan/interfaces.html b/netbox/templates/ipam/vlan/interfaces.html deleted file mode 100644 index f7bcc8563..000000000 --- a/netbox/templates/ipam/vlan/interfaces.html +++ /dev/null @@ -1,20 +0,0 @@ -{% extends 'ipam/vlan/base.html' %} -{% load helpers %} - -{% block content %} - {% include 'inc/table_controls_htmx.html' with table_modal="VLANDevicesTable_config" %} - -
- {% csrf_token %} -
-
- {% include 'htmx/table.html' %} -
-
-
-{% endblock content %} - -{% block modals %} - {{ block.super }} - {% table_config_form table %} -{% endblock modals %} diff --git a/netbox/templates/ipam/vlan/vminterfaces.html b/netbox/templates/ipam/vlan/vminterfaces.html deleted file mode 100644 index a485b33eb..000000000 --- a/netbox/templates/ipam/vlan/vminterfaces.html +++ /dev/null @@ -1,20 +0,0 @@ -{% extends 'ipam/vlan/base.html' %} -{% load helpers %} - -{% block content %} - {% include 'inc/table_controls_htmx.html' with table_modal="VLANVirtualMachinesTable_config" %} - -
- {% csrf_token %} -
-
- {% include 'htmx/table.html' %} -
-
-
-{% endblock content %} - -{% block modals %} - {{ block.super }} - {% table_config_form table %} -{% endblock modals %} diff --git a/netbox/templates/tenancy/object_contacts.html b/netbox/templates/tenancy/object_contacts.html index e13fedc43..95727604c 100644 --- a/netbox/templates/tenancy/object_contacts.html +++ b/netbox/templates/tenancy/object_contacts.html @@ -1,4 +1,4 @@ -{% extends base_template %} +{% extends 'generic/object_children.html' %} {% load helpers %} {% block extra_controls %} @@ -10,20 +10,3 @@ {% endwith %} {% endif %} {% endblock %} - -{% block content %} - {% include 'inc/table_controls_htmx.html' with table_modal="ContactAssignmentTable_config" %} -
- {% csrf_token %} -
-
- {% include 'htmx/table.html' %} -
-
-
-{% endblock content %} - -{% block modals %} - {{ block.super }} - {% table_config_form table %} -{% endblock modals %} diff --git a/netbox/templates/virtualization/cluster/devices.html b/netbox/templates/virtualization/cluster/devices.html index 083798233..271240ed1 100644 --- a/netbox/templates/virtualization/cluster/devices.html +++ b/netbox/templates/virtualization/cluster/devices.html @@ -1,30 +1,13 @@ -{% extends 'virtualization/cluster/base.html' %} +{% extends 'generic/object_children.html' %} {% load helpers %} -{% load render_table from django_tables2 %} -{% block content %} - {% include 'inc/table_controls_htmx.html' with table_modal="DeviceTable_config" %} - -
- {% csrf_token %} -
-
- {% include 'htmx/table.html' %} -
-
-
-
- {% if perms.virtualization.change_cluster %} - - {% endif %} -
-
-
-{% endblock content %} - -{% block modals %} - {{ block.super }} - {% table_config_form table %} -{% endblock modals %} + + {% endif %} +{% endblock bulk_delete_controls %} diff --git a/netbox/templates/virtualization/cluster/virtual_machines.html b/netbox/templates/virtualization/cluster/virtual_machines.html deleted file mode 100644 index 79c489d6b..000000000 --- a/netbox/templates/virtualization/cluster/virtual_machines.html +++ /dev/null @@ -1,35 +0,0 @@ -{% extends 'virtualization/cluster/base.html' %} -{% load helpers %} -{% load render_table from django_tables2 %} - -{% block content %} - {% include 'inc/table_controls_htmx.html' with table_modal="VirtualMachineTable_config" %} - -
- {% csrf_token %} -
-
- {% include 'htmx/table.html' %} -
-
-
-
- {% if 'bulk_edit' in actions %} - - {% endif %} - {% if 'bulk_delete' in actions %} - - {% endif %} -
-
-
-{% endblock content %} - -{% block modals %} - {{ block.super }} - {% table_config_form table %} -{% endblock modals %} diff --git a/netbox/templates/virtualization/virtualmachine/interfaces.html b/netbox/templates/virtualization/virtualmachine/interfaces.html index 71456d104..ee4e76926 100644 --- a/netbox/templates/virtualization/virtualmachine/interfaces.html +++ b/netbox/templates/virtualization/virtualmachine/interfaces.html @@ -1,47 +1,13 @@ -{% extends 'virtualization/virtualmachine/base.html' %} -{% load render_table from django_tables2 %} +{% extends 'generic/object_children.html' %} {% load helpers %} -{% block content %} - {% include 'inc/table_controls_htmx.html' with table_modal="VirtualMachineVMInterfaceTable_config" %} - -
- {% csrf_token %} - -
-
- {% include 'htmx/table.html' %} -
-
- -
- {% if perms.virtualization.change_vminterface %} -
- - -
- {% endif %} - {% if perms.virtualization.delete_vminterface %} - - {% endif %} - {% if perms.virtualization.add_vminterface %} - - {% endif %} -
-
-{% endblock content %} - -{% block modals %} - {{ block.super }} - {% table_config_form table %} -{% endblock modals %} + {% endif %} +{% endblock bulk_edit_controls %} diff --git a/netbox/tenancy/views.py b/netbox/tenancy/views.py index 23020e794..3025e7e04 100644 --- a/netbox/tenancy/views.py +++ b/netbox/tenancy/views.py @@ -41,11 +41,6 @@ class ObjectContactsView(generic.ObjectChildrenView): return table - def get_extra_context(self, request, instance): - return { - 'base_template': f'{instance._meta.app_label}/{instance._meta.model_name}.html', - } - # # Tenant groups # diff --git a/netbox/virtualization/views.py b/netbox/virtualization/views.py index 75e83f9e1..92a91f47e 100644 --- a/netbox/virtualization/views.py +++ b/netbox/virtualization/views.py @@ -1,3 +1,5 @@ +from collections import defaultdict + from django.contrib import messages from django.db import transaction from django.db.models import Prefetch, Sum @@ -175,7 +177,7 @@ class ClusterVirtualMachinesView(generic.ObjectChildrenView): child_model = VirtualMachine table = tables.VirtualMachineTable filterset = filtersets.VirtualMachineFilterSet - template_name = 'virtualization/cluster/virtual_machines.html' + template_name = 'generic/object_children.html' tab = ViewTab( label=_('Virtual Machines'), badge=lambda obj: obj.virtual_machines.count(), @@ -194,6 +196,13 @@ class ClusterDevicesView(generic.ObjectChildrenView): table = DeviceTable filterset = DeviceFilterSet template_name = 'virtualization/cluster/devices.html' + actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_remove_devices') + action_perms = defaultdict(set, **{ + 'add': {'add'}, + 'import': {'add'}, + 'bulk_edit': {'change'}, + 'bulk_remove_devices': {'change'}, + }) tab = ViewTab( label=_('Devices'), badge=lambda obj: obj.devices.count(), @@ -353,6 +362,14 @@ class VirtualMachineInterfacesView(generic.ObjectChildrenView): permission='virtualization.view_vminterface', weight=500 ) + actions = ('add', 'import', 'export', 'bulk_edit', 'bulk_delete', 'bulk_rename') + action_perms = defaultdict(set, **{ + 'add': {'add'}, + 'import': {'add'}, + 'bulk_edit': {'change'}, + 'bulk_delete': {'delete'}, + 'bulk_rename': {'change'}, + }) def get_children(self, request, parent): return parent.interfaces.restrict(request.user, 'view').prefetch_related( From 9b1406a1a7ca566d084fae6951ac9126bd57dc59 Mon Sep 17 00:00:00 2001 From: kkthxbye-code Date: Wed, 9 Aug 2023 11:56:30 +0200 Subject: [PATCH 155/170] Don't hide HIDDEN_IFUNSET custom fields from bulk import fields --- netbox/netbox/forms/base.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/netbox/netbox/forms/base.py b/netbox/netbox/forms/base.py index 83c238e0f..b406ab04e 100644 --- a/netbox/netbox/forms/base.py +++ b/netbox/netbox/forms/base.py @@ -78,7 +78,10 @@ class NetBoxModelImportForm(CSVModelForm, NetBoxModelForm): def _get_custom_fields(self, content_type): return CustomField.objects.filter(content_types=content_type).filter( - ui_visibility=CustomFieldVisibilityChoices.VISIBILITY_READ_WRITE + ui_visibility__in=[ + CustomFieldVisibilityChoices.VISIBILITY_READ_WRITE, + CustomFieldVisibilityChoices.VISIBILITY_HIDDEN_IFUNSET, + ] ) def _get_form_field(self, customfield): From dcdb4d27eced5671bd081ef905efff710ee68820 Mon Sep 17 00:00:00 2001 From: Arthur Date: Wed, 9 Aug 2023 12:28:35 +0700 Subject: [PATCH 156/170] 12665 add semicolon to link sanitation safe string --- netbox/extras/models/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/extras/models/models.py b/netbox/extras/models/models.py index fcf5c26a2..c76a5a76f 100644 --- a/netbox/extras/models/models.py +++ b/netbox/extras/models/models.py @@ -285,7 +285,7 @@ class CustomLink(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel): text = clean_html(text, allowed_schemes) # Sanitize link - link = urllib.parse.quote(link, safe='/:?&=%+[]@#,') + link = urllib.parse.quote(link, safe='/:?&=%+[]@#,;') # Verify link scheme is allowed result = urllib.parse.urlparse(link) From 72e1e8fab1ad0379fb0b126f66f6aaad805ce843 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 9 Aug 2023 15:02:49 -0400 Subject: [PATCH 157/170] Changelog for #11675, #11922, #12665, #13368, #13414 --- docs/release-notes/version-3.5.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/release-notes/version-3.5.md b/docs/release-notes/version-3.5.md index 4347d9837..cf47a3b23 100644 --- a/docs/release-notes/version-3.5.md +++ b/docs/release-notes/version-3.5.md @@ -4,15 +4,20 @@ ### Enhancements +* [#11675](https://github.com/netbox-community/netbox/issues/11675) - Add support for specifying import/export route targets during VRF bulk import +* [#11922](https://github.com/netbox-community/netbox/issues/11922) - Automatically populate any VDC assignments from the parent when adding a child interface via the UI * [#12889](https://github.com/netbox-community/netbox/issues/12889) - Add 400GE CFP2 interface type * [#13033](https://github.com/netbox-community/netbox/issues/13033) - Add human-friendly speed column to interfaces table * [#13151](https://github.com/netbox-community/netbox/issues/13151) - Add "assigned" filter for IP addresses +* [#13368](https://github.com/netbox-community/netbox/issues/13368) - List installed plugins on the server error report page ### Bug Fixes +* [#12665](https://github.com/netbox-community/netbox/issues/12665) - Avoid escaping semicolons when rendering custom links * [#12750](https://github.com/netbox-community/netbox/issues/12750) - Automatically delete an AutoSyncRecord when its object is deleted * [#13343](https://github.com/netbox-community/netbox/issues/13343) - Fix filtering of circuits under provider network view * [#13369](https://github.com/netbox-community/netbox/issues/13369) - Fix job termination status for failed reports +* [#13414](https://github.com/netbox-community/netbox/issues/13414) - Fix support for "hide-if-unset" custom fields on bulk import forms --- From dc7411e4c5fe72c2dfa79e925fb014bcc7553189 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 11 Aug 2023 08:56:58 -0400 Subject: [PATCH 158/170] Fixes #13446: Don't disable bulk edit/delete buttons after deselecting "select all" checkbox --- docs/release-notes/version-3.5.md | 1 + netbox/project-static/dist/netbox.js | Bin 530613 -> 530368 bytes netbox/project-static/dist/netbox.js.map | Bin 450868 -> 450659 bytes .../project-static/src/buttons/selectAll.ts | 30 +----------------- 4 files changed, 2 insertions(+), 29 deletions(-) diff --git a/docs/release-notes/version-3.5.md b/docs/release-notes/version-3.5.md index cf47a3b23..fe0832c3b 100644 --- a/docs/release-notes/version-3.5.md +++ b/docs/release-notes/version-3.5.md @@ -18,6 +18,7 @@ * [#13343](https://github.com/netbox-community/netbox/issues/13343) - Fix filtering of circuits under provider network view * [#13369](https://github.com/netbox-community/netbox/issues/13369) - Fix job termination status for failed reports * [#13414](https://github.com/netbox-community/netbox/issues/13414) - Fix support for "hide-if-unset" custom fields on bulk import forms +* [#13446](https://github.com/netbox-community/netbox/issues/13446) - Don't disable bulk edit/delete buttons after deselecting "select all" checkbox --- diff --git a/netbox/project-static/dist/netbox.js b/netbox/project-static/dist/netbox.js index b62436d757a2b299e5bf0f33dcc4fbd67ec21e8c..84bfecae34920e732ecf52670208390ce6712040 100644 GIT binary patch delta 9766 zcmZ{Kd3+RA^7!XqGPya# zpT{Z-*uqm(5EW3-1vg+To`B-9B7*L&x*m8SE24nAE{Y=G>X{*+`}zGrs$ac&_3G8D zs#jIR-lyVU-W9(t4#`i&FGNUI>d}ux7LENfOx#iKRXl!8z-yfLlSJ3V#<>klbqgv&^kG+fv6!q~YRHnr2Tx84=1#gMb z(ChLVOniwbcuG<#`a7E?H}5VH#FESsF}*}gN-9wXciylfOXT}y)6h_Yv7&Qm$k*TH zkXqxj#D;#kQ4kDXpX~Is8BC_6B$!W1>gzO?1Quk5NJ;{f>7kJuwheBTVweqVO?+@- zc-WMoX(P`aIB0zbUlKZ^lJ-P1awvB{v1+U@kGBS}*1*|H&X1wHqB%~3JiaWzudFl0(bpx(KCdu%S?+@1&9*5&J)Mip zidmh{Sw+6e%_k}k?ugb`xxtJ~nO5aKwFp^9{-usVj8#;&?ub(K&k7N>Ucmy2@%luc z18yXzq@h-+eb&I#dTAQcMJorNnG%s#0xbAiFW)Si;NLJGSRbuidY0&Gy{62585SLX zHZcYUA!Yi#yrYeG+Ie3aU#m>r7mKXQ{C!4!Sx{KN;@EcsDj0cS-wZ}?l>&-yRt`SD zEVe(77uzlyXnl#0=Qf)7YPVn=S@ePvk?cI)-9|r~gQAp|UKCN&NWx3e4C+%B?7sIr&N{sBOWk1fe2j@u9|9#d&%Qed3;uAchi$IvXx84oU%i~K&7gTGf;1Bm)W69$!qu4rNBY{YT0g;|N%%j+xJbqwwzTGbL zd0@T(=5TzwW zbvgM(MS`K!kWt$Jz6-&m!=Jd2-zF0Z@!N<|sa9E2<4V`jhf^fsoP_NxsJo55~ z911U%LK3(80~YO6-v8%^$g7Ne^w)67!nchI>9d0F)_yD?Q91nab;vyO!^i0?ax1By z?a{k}me8V{{%j^hhe@ArhUl>C^B7b(^4jO&h_qVx?$A-b|DpxXSN&yqj6)Kvc9SW+ z+%F`g2E&=nFF2H!zBDd&NDXFtV@iwY4n%?kLFku3P8*!f29X)0q5aKY&F5U2X}PR)zn?@X>%LltI+cWz z8&KPbda?`=zlARd5gWdq7h9&ehQ%rrIsum31()XBHGaXRc)m6x+sN*(-$2Nw419A4 z{+drm!{6G|!{Ig&G%c9rq@-%Uu|(!=%2%h~K+VeTZ>PluOgrB$Wch(r8L-misrL&7 zBPYH+hOXriZv&#u?qc~B)QgrWASn`4NpNc>FK-3n31t(O(>?aBt%c!t!h9Nj-nX+0-|H-SBk&O3^PqLDr4k`Tn9e}3nnAG5!Y6J(D4MzmDnSM6a|B&a zaJPl83t-2ML-SAp%^ZiSNxsPE2DCXtnR8$qJva{4p?o@hJW9(fmjq{VN|!A8B<&^V z3+zI?XN<=j)@*p*7mIXoZOHO-yYNoqCy;f zRBij;t-Y>{-(W*qOTR;FTZ_NLX&n2R0-tNly&7`(<>+f*d*6n~(MH5=bhC5-4PIpKdw`2v!H2il6^!aG7r2BvJ_W5p;RQK->F9!T zmtduv=b*(*eUAFc9P|-FKKfi5+5iSnHWxjKob=LMlm%v}?I|KQ{-2=QQ`?3rz<$_hcVG*iA;r0Pu9K~ms3x&5nARzStfqAcvu}E~;DJDsS zT2_xN^l8f61|ahu=1BnG-ot1}{k=>gBS}j5UnBK7k|0S*NfIr%mzjZbsPkUt874=f zT?@Gwn!bsN0m1+?+PR5|!#R>MM_spx!63G}VGEOqf~fZ&WS&KUy6Yik0t+X4a2pc~ z9=mTFa~B+}csoNdQ$L_qKF&lS5Fu0gdI-wHa*8lw2vL zO?KL|9IaxH8&ct*GiKzfPc39WMkqj4hD7zV3^oJuZFRc9>RIHVNlVz`36Ab74&RltRLWgfH<3W!j@zOFt-PbEveOja28` z*<&122)9$mM$?rNJ4L<0&8D%JQ7WL$GnYpi!8Ph*GOI$~MnxaH0Kg_6dmE}%XZEm| z1!(MJzhO|J8ac>b%ivlmqm~N8?Ay@+ky=8cNbNz<3TVP|b_)Hbg)LB5FJ~V@Oo2pS z%4M&mMJw4bFzkkvum@1x1c%(Qk|hCxi3P0jGQo*eU>OA}5G>%rd%6WlyDodTAZoBx z?YNzt!y-G~bSHZP_0W}T0JPH9Ke3m>+q;7@l(hu!vP<=^W#jayoo=LT1?r-wC@Z5* zwecQy5kj4G)4gnSa^UvcJc6ek*tfwp8uWJu73=E>a;k9(S|DwsrcLa0l%?hZ7=!$( z<6*Y-X4FjIx(-i);B?|Td<)8^ZX+H*z4W9JJ2Jc?-z9Z*IplOBge0>!G|F{&9J1(> zg6RCq=#th}cvHOr+6sEB39m*TI&K!u1m=aauyvwG6byc+qgSgl4ZN8?FbmtGyrR$m zNu=ay5q-v758vPv8%^~2S(sNXvvD2+g5Hsg6EO(7I}IzyO0(u-1L~)3bMaOPIb-JG zG}NzV%)`*JkZ8jn@L^O$P4n>)2&q5K$Cbdqc0C?O$WPz80k4OZOBcX_`)Th2TnCd! z7vKvB71OuUu`t!;)hbeWdOKy)pzQGV$e#FOkA}0-#0-2LyyHa~*a(i%lY!@PEm|wZ zG)Rxm!BNqWzPmMp2F&p2Q`$p6%)on53thJe$HJ9Az6fuC&6Wtb1r@1}2!KQ`^~J@Q z(*nw&Oblgowz@nYTN${81BLiL)I^sS;a5;Oomq_S$VP7~#?xW6y%_I+1KVu436Oub z;Z#6=YQtZ`=#>(DH|nN!rTBdSbIb5E0KO^1#ay3sMPjwG9JeFv5i|Sf%N2MA>Z6}l z;N76Nb(O%zN_(n6saCq73U5btbx}2zKvC)wHTX^hSG%wdn~<5d)Zwx}fFeR&lzcfs zFnD@8n;o(tGha^c5smpWpX-6Ph((568*&`8H?yZF-Aw;ghwlPs7_7%;*!qQfJQLV` zR*#=RX7x`ExCBAq`{YL4jUt_*DR>zTje)oisZx{LvJ}Ju?TlT9E09xNx(q*vCN)V$ zm(vuiRIDDL3Twu<>wwWc?HH0!JB{tYPE?_;?!aGZhv{(RMGP3s4iENB4_sd}xQnx4 zz}d+61-^3OtHLR`=v)aLq=h~!;o0#$5NV~%D(y6eenV!91euxB;qg^UT|+~u=`9p@ z;k?)$iTAk$mvgqm>~ZXL7J&dY5{v8zY3u)%Rp@Jg_;F4>us2`u}|RHH`Ar;(q)p z3w9qdjQ6rL^0nee>yWke2@SmxUm@|?65kAmv$YHP^sALPVeAz&4PS+$fX8*K@N+s; zseW-Qei}hI+jKjI_+O%y-+|8{$l5JyAmWtLo7UhC$jrZ8gV~9YnV-8K??R}OZru!a z-a%j93`89px8Q%l+}B%hBm*}0>sCCSVayWUdH_e!1>5m>y7&>idWKmmAUt-rBg^US zbb7r;Eu5Mqu(Do{(M&&m1P?QTbjwNr5?ju_J}@(l%jjp;e+@J>7tp#gQx6Zk2uuE^XC zq06G)yBi;6aGp2aqIyOkDB^Yzi0|Hm_Yf#T^7mmk(hYdET$e5(B!+Hoq#68jLTu% z?zB5XLtAiYcU+$D1_U@~iz`5oIB2Z^CW)YHjfnyLhfuv|1EG>+$r*j>hZ%k0WpbQ z+T|kCXxtIJ3YE~UM{sFin*MME-v(@!zk^?7Iz@UejZ9VRj^agVa;0R1CTIrKtnr<) zuq<(~jqW&xkD)|$;9c!j+SF}-!>1YA{2C;&+uz31L*n@F9R1l`XgpE*Lv?Kd@l?= z7x3`7;Hi_~VwL*+RI+94xMs*Rf|uJ*?@uC0 z@Sq+{B6%!oR>#jG6EV2_v=m}QKp50`mMqZYZ{kR`We!=uKsIrI8kvJIO!IX8r{L8W z=8}!TW&J$zDS||#dp=nJ5$N&xq?`dWxOf9;#U0)h@5TirMqR#ucrlwTX40*x;B(^y zGIw;O)(E7Bg)Hmv5|TU*_zw+r$|+6llHVa$Xq|F+Px?jE;O~(AVn*=!8>U+ zrYt4X;5jc`N)pXYnx{8q1w1?8$^k?lAhe5391ILALPPNMz_=2|Ay03jyESfo^zc&h zUnbWZ49;hp$O_b_b}b_o7Uo7;NG9onOYWp)b`nox>|{Qmi|r(jgRrOlC_$Imp%D3t zogB~zVccye(R8hYRB22D=lR(|{xLaA3V4wzl%`}{p8TnmOoqB~d>i=!l8&=&k@??ds0%5{Yv+UcR`oQMdM`>o#qmGL#rfb zL;u6Tx7ba?kbbwc2XaG~m=c;1TI|Ex``lyaU22<5x;5kfz)P-&5@(VRJP0g#VGoG| zAE@ggW&ro~5T)8D#XCLm9ij<3-20hdJZj<1T-UG1hPh=;6vbAIz^kmMjCCh=vzm6#C$E^>+mc;7~ni2(kS1|Xe!FIfn~ zs(VQj(pL-!evh%Tl^*w!D0*}gi3qRPS{y~K!R~WcxlpJ6u!(%a#0I+3)^g}r!F;|H zoG;SmPd`A$Fwph1 zD1{kPSI@GRz>yGIlfLJf+i~!shbOM0KFX-k1`1pb@31Sca zS!dG22~$|E9noCEO`sDZxiPenaC4~T3>QUH#_RO-F~W_9$o>q&#ZgHQMCLPGbYKcf zq--6x5=xdsI_^ySgRUFT73nlOzMSqH&$V*hHe+uu-%K+v z>Y`}!1TNE>i;J&7-g~+U+2PSbPv~LoZz``2(@+v%yX1vOT(WY=t50bZsb2A{G-x|)XpV$%5 zd+23|uTai}M^j-kHv#4gCv(#QI04MlwhHcQ%CHAJkm(_}xoYaF^CjA7!hdzs>E9=F zbD)K*i{Mft25jJ$p#%2^4xFgwM{o~9ex{y^0%`wG}ToXR~n zCcMff%u7wZ5)-Pal+Gmp>H2hTKWe2F8C(&xKOW5BhM9Uvja$T}GEfbbF6OpQWU6cw zZG(8s*|=UHT4Cd6;uh%9)1yAXcHYd5qp#XH18PzKW#hIXrW4Z9AufV$xswyrPfNL; ziKs`dTE-pM1H9hJ@w1s0iOwkGqSb-B!8^Dvi0h{GPC9cVXGC3U(MIkW!t_XV!d7mY zYPg@flYw6SuFY^~KKk}%t|qokG7a`RuS$GmM<*m{#x}Zm3l|GiwOhEt|38)b{1(oP z;C#P6$WaY$e281gXa}FNjk`nB^CORf^H!?TW88iMPj=caE{`db=*v5~NOkQl?miY4 z%^uhMz6^f?vAab5VLx{#iZ1asbZb9UhnnQ@KWj7uJ$Dk)zsC=9#wZvUUqz4^ z7zgJs9OP<|L#=p)dmP&5>aVYH?=d=uXe^d_x^x2x~ehEROnl3y6TCS#pC%DBEswJcL-*>eCN|Ii!9y-CT&=%yJ zp*}fQpReU(=f~-n!iwtg`X``7VJGN6*0%p_qW&E0^6n&kH1x>SQ{j4OmNcnbBlOGR R=VTT5U$4QMX#G;={{R{n`nLc8 delta 9842 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 diff --git a/netbox/project-static/dist/netbox.js.map b/netbox/project-static/dist/netbox.js.map index ed3833f982a5d0f0d81c6bcf7e90a86aeb99d789..7f2400ed2610973ed909703e534ec9b73c19c8ae 100644 GIT binary patch delta 44 zcmV+{0Mq}p!W-kj8-RoXgaU*Egaot&HT?rsH$s;W>ID^-;r#_p1Vd~^cDHc;1;pZx C4H8xW delta 254 zcmYL@%?bfw6o&INYb&MP!YM_R<$I1XO&XJiG)qlHgzVU`n0uI|o0zhdTnRh(Fy%XD zVRL#;y?^g}J$=+KaUm|orMR40?d%-wg+k!SG& r%d8<_AO4F%b#|B{*bT`;&4S3Z%JLU7(OR8kvdj+Y_D{EKW<6hDCyPpW diff --git a/netbox/project-static/src/buttons/selectAll.ts b/netbox/project-static/src/buttons/selectAll.ts index 64b98d390..f40520e26 100644 --- a/netbox/project-static/src/buttons/selectAll.ts +++ b/netbox/project-static/src/buttons/selectAll.ts @@ -1,4 +1,4 @@ -import { getElement, getElements, findFirstAdjacent } from '../util'; +import { getElements, findFirstAdjacent } from '../util'; /** * If any PK checkbox is checked, uncheck the select all table checkbox and the select all @@ -63,29 +63,6 @@ function handleSelectAllToggle(event: Event): void { } } -/** - * Synchronize the select all confirmation checkbox state with the select all confirmation button - * disabled state. If the select all confirmation checkbox is checked, the buttons should be - * enabled. If not, the buttons should be disabled. - * - * @param event Change Event - */ -function handleSelectAll(event: Event): void { - const target = event.currentTarget as HTMLInputElement; - const selectAllBox = getElement('select-all-box'); - if (selectAllBox !== null) { - for (const button of selectAllBox.querySelectorAll( - 'button[type="submit"]', - )) { - if (target.checked) { - button.disabled = false; - } else { - button.disabled = true; - } - } - } -} - /** * Initialize table select all elements. */ @@ -98,9 +75,4 @@ export function initSelectAll(): void { for (const element of getElements('input[type="checkbox"][name="pk"]')) { element.addEventListener('change', handlePkCheck); } - const selectAll = getElement('select-all'); - - if (selectAll !== null) { - selectAll.addEventListener('change', handleSelectAll); - } } From 9fd07b594c13266736cd67f1005334b916062828 Mon Sep 17 00:00:00 2001 From: Arthur Hanson Date: Fri, 11 Aug 2023 20:49:03 +0700 Subject: [PATCH 159/170] 11578 mark swagger available- apis to accept lists in post (#13445) * 11578 change swagger for available-ips to accept lists * 11578 change swagger for available-xxx to accept lists --- netbox/ipam/api/views.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/netbox/ipam/api/views.py b/netbox/ipam/api/views.py index 99b4c023d..feffc3ff2 100644 --- a/netbox/ipam/api/views.py +++ b/netbox/ipam/api/views.py @@ -224,7 +224,10 @@ class AvailableASNsView(ObjectValidationMixin, APIView): return Response(serializer.data) - @extend_schema(methods=["post"], responses={201: serializers.ASNSerializer(many=True)}) + @extend_schema(methods=["post"], + responses={201: serializers.ASNSerializer(many=True)}, + request=serializers.ASNSerializer(many=True), + ) @advisory_lock(ADVISORY_LOCK_KEYS['available-asns']) def post(self, request, pk): self.queryset = self.queryset.restrict(request.user, 'add') @@ -293,7 +296,10 @@ class AvailablePrefixesView(ObjectValidationMixin, APIView): return Response(serializer.data) - @extend_schema(methods=["post"], responses={201: serializers.PrefixSerializer(many=True)}) + @extend_schema(methods=["post"], + responses={201: serializers.PrefixSerializer(many=True)}, + request=serializers.PrefixSerializer(many=True), + ) @advisory_lock(ADVISORY_LOCK_KEYS['available-prefixes']) def post(self, request, pk): self.queryset = self.queryset.restrict(request.user, 'add') @@ -388,7 +394,10 @@ class AvailableIPAddressesView(ObjectValidationMixin, APIView): return Response(serializer.data) - @extend_schema(methods=["post"], responses={201: serializers.IPAddressSerializer(many=True)}) + @extend_schema(methods=["post"], + responses={201: serializers.IPAddressSerializer(many=True)}, + request=serializers.IPAddressSerializer(many=True), + ) @advisory_lock(ADVISORY_LOCK_KEYS['available-ips']) def post(self, request, pk): self.queryset = self.queryset.restrict(request.user, 'add') @@ -468,7 +477,10 @@ class AvailableVLANsView(ObjectValidationMixin, APIView): return Response(serializer.data) - @extend_schema(methods=["post"], responses={201: serializers.VLANSerializer(many=True)}) + @extend_schema(methods=["post"], + responses={201: serializers.VLANSerializer(many=True)}, + request=serializers.VLANSerializer(many=True), + ) @advisory_lock(ADVISORY_LOCK_KEYS['available-vlans']) def post(self, request, pk): self.queryset = self.queryset.restrict(request.user, 'add') From 40afe6cf36be56c2aa3856a34254aea8c8934e14 Mon Sep 17 00:00:00 2001 From: "Daniel W. Anner" Date: Fri, 11 Aug 2023 11:00:26 -0400 Subject: [PATCH 160/170] Feature - Schema Generation (#13353) * Schema generation is working * Added option to either dump to a file or the console * Moving schema file and utilizing settings definition for file paths * Cleaning up the imports and fixing a few pythonic issues * Tweak command flags * Clean up choices mapping * Misc cleanup * Rename & move template file * Move management command from extras to dcim * Update release checklist --------- Co-authored-by: Jeremy Stretch --- contrib/generated_schema.json | 561 ++++++++++++++++++ docs/development/release-checklist.md | 10 + .../dcim/management/commands/buildschema.py | 62 ++ .../extras/schema/devicetype_schema.jinja2 | 93 +++ 4 files changed, 726 insertions(+) create mode 100644 contrib/generated_schema.json create mode 100644 netbox/dcim/management/commands/buildschema.py create mode 100644 netbox/templates/extras/schema/devicetype_schema.jinja2 diff --git a/contrib/generated_schema.json b/contrib/generated_schema.json new file mode 100644 index 000000000..8dbcb2847 --- /dev/null +++ b/contrib/generated_schema.json @@ -0,0 +1,561 @@ +{ + "type": "object", + "additionalProperties": false, + "definitions": { + "airflow": { + "type": "string", + "enum": [ + "front-to-rear", + "rear-to-front", + "left-to-right", + "right-to-left", + "side-to-rear", + "passive", + "mixed" + ] + }, + "weight-unit": { + "type": "string", + "enum": [ + "kg", + "g", + "lb", + "oz" + ] + }, + "subdevice-role": { + "type": "string", + "enum": [ + "parent", + "child" + ] + }, + "console-port": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "de-9", + "db-25", + "rj-11", + "rj-12", + "rj-45", + "mini-din-8", + "usb-a", + "usb-b", + "usb-c", + "usb-mini-a", + "usb-mini-b", + "usb-micro-a", + "usb-micro-b", + "usb-micro-ab", + "other" + ] + } + } + }, + "console-server-port": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "de-9", + "db-25", + "rj-11", + "rj-12", + "rj-45", + "mini-din-8", + "usb-a", + "usb-b", + "usb-c", + "usb-mini-a", + "usb-mini-b", + "usb-micro-a", + "usb-micro-b", + "usb-micro-ab", + "other" + ] + } + } + }, + "power-port": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "iec-60320-c6", + "iec-60320-c8", + "iec-60320-c14", + "iec-60320-c16", + "iec-60320-c20", + "iec-60320-c22", + "iec-60309-p-n-e-4h", + "iec-60309-p-n-e-6h", + "iec-60309-p-n-e-9h", + "iec-60309-2p-e-4h", + "iec-60309-2p-e-6h", + "iec-60309-2p-e-9h", + "iec-60309-3p-e-4h", + "iec-60309-3p-e-6h", + "iec-60309-3p-e-9h", + "iec-60309-3p-n-e-4h", + "iec-60309-3p-n-e-6h", + "iec-60309-3p-n-e-9h", + "iec-60906-1", + "nbr-14136-10a", + "nbr-14136-20a", + "nema-1-15p", + "nema-5-15p", + "nema-5-20p", + "nema-5-30p", + "nema-5-50p", + "nema-6-15p", + "nema-6-20p", + "nema-6-30p", + "nema-6-50p", + "nema-10-30p", + "nema-10-50p", + "nema-14-20p", + "nema-14-30p", + "nema-14-50p", + "nema-14-60p", + "nema-15-15p", + "nema-15-20p", + "nema-15-30p", + "nema-15-50p", + "nema-15-60p", + "nema-l1-15p", + "nema-l5-15p", + "nema-l5-20p", + "nema-l5-30p", + "nema-l5-50p", + "nema-l6-15p", + "nema-l6-20p", + "nema-l6-30p", + "nema-l6-50p", + "nema-l10-30p", + "nema-l14-20p", + "nema-l14-30p", + "nema-l14-50p", + "nema-l14-60p", + "nema-l15-20p", + "nema-l15-30p", + "nema-l15-50p", + "nema-l15-60p", + "nema-l21-20p", + "nema-l21-30p", + "nema-l22-30p", + "cs6361c", + "cs6365c", + "cs8165c", + "cs8265c", + "cs8365c", + "cs8465c", + "ita-c", + "ita-e", + "ita-f", + "ita-ef", + "ita-g", + "ita-h", + "ita-i", + "ita-j", + "ita-k", + "ita-l", + "ita-m", + "ita-n", + "ita-o", + "usb-a", + "usb-b", + "usb-c", + "usb-mini-a", + "usb-mini-b", + "usb-micro-a", + "usb-micro-b", + "usb-micro-ab", + "usb-3-b", + "usb-3-micro-b", + "dc-terminal", + "saf-d-grid", + "neutrik-powercon-20", + "neutrik-powercon-32", + "neutrik-powercon-true1", + "neutrik-powercon-true1-top", + "ubiquiti-smartpower", + "hardwired", + "other" + ] + } + } + }, + "power-outlet": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "iec-60320-c5", + "iec-60320-c7", + "iec-60320-c13", + "iec-60320-c15", + "iec-60320-c19", + "iec-60320-c21", + "iec-60309-p-n-e-4h", + "iec-60309-p-n-e-6h", + "iec-60309-p-n-e-9h", + "iec-60309-2p-e-4h", + "iec-60309-2p-e-6h", + "iec-60309-2p-e-9h", + "iec-60309-3p-e-4h", + "iec-60309-3p-e-6h", + "iec-60309-3p-e-9h", + "iec-60309-3p-n-e-4h", + "iec-60309-3p-n-e-6h", + "iec-60309-3p-n-e-9h", + "iec-60906-1", + "nbr-14136-10a", + "nbr-14136-20a", + "nema-1-15r", + "nema-5-15r", + "nema-5-20r", + "nema-5-30r", + "nema-5-50r", + "nema-6-15r", + "nema-6-20r", + "nema-6-30r", + "nema-6-50r", + "nema-10-30r", + "nema-10-50r", + "nema-14-20r", + "nema-14-30r", + "nema-14-50r", + "nema-14-60r", + "nema-15-15r", + "nema-15-20r", + "nema-15-30r", + "nema-15-50r", + "nema-15-60r", + "nema-l1-15r", + "nema-l5-15r", + "nema-l5-20r", + "nema-l5-30r", + "nema-l5-50r", + "nema-l6-15r", + "nema-l6-20r", + "nema-l6-30r", + "nema-l6-50r", + "nema-l10-30r", + "nema-l14-20r", + "nema-l14-30r", + "nema-l14-50r", + "nema-l14-60r", + "nema-l15-20r", + "nema-l15-30r", + "nema-l15-50r", + "nema-l15-60r", + "nema-l21-20r", + "nema-l21-30r", + "nema-l22-30r", + "CS6360C", + "CS6364C", + "CS8164C", + "CS8264C", + "CS8364C", + "CS8464C", + "ita-e", + "ita-f", + "ita-g", + "ita-h", + "ita-i", + "ita-j", + "ita-k", + "ita-l", + "ita-m", + "ita-n", + "ita-o", + "ita-multistandard", + "usb-a", + "usb-micro-b", + "usb-c", + "dc-terminal", + "hdot-cx", + "saf-d-grid", + "neutrik-powercon-20a", + "neutrik-powercon-32a", + "neutrik-powercon-true1", + "neutrik-powercon-true1-top", + "ubiquiti-smartpower", + "hardwired", + "other" + ] + }, + "feed-leg": { + "type": "string", + "enum": [ + "A", + "B", + "C" + ] + } + } + }, + "interface": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "virtual", + "bridge", + "lag", + "100base-fx", + "100base-lfx", + "100base-tx", + "100base-t1", + "1000base-t", + "2.5gbase-t", + "5gbase-t", + "10gbase-t", + "10gbase-cx4", + "1000base-x-gbic", + "1000base-x-sfp", + "10gbase-x-sfpp", + "10gbase-x-xfp", + "10gbase-x-xenpak", + "10gbase-x-x2", + "25gbase-x-sfp28", + "50gbase-x-sfp56", + "40gbase-x-qsfpp", + "50gbase-x-sfp28", + "100gbase-x-cfp", + "100gbase-x-cfp2", + "200gbase-x-cfp2", + "100gbase-x-cfp4", + "100gbase-x-cxp", + "100gbase-x-cpak", + "100gbase-x-dsfp", + "100gbase-x-sfpdd", + "100gbase-x-qsfp28", + "100gbase-x-qsfpdd", + "200gbase-x-qsfp56", + "200gbase-x-qsfpdd", + "400gbase-x-qsfpdd", + "400gbase-x-osfp", + "400gbase-x-cdfp", + "400gbase-x-cfp8", + "800gbase-x-qsfpdd", + "800gbase-x-osfp", + "1000base-kx", + "10gbase-kr", + "10gbase-kx4", + "25gbase-kr", + "40gbase-kr4", + "50gbase-kr", + "100gbase-kp4", + "100gbase-kr2", + "100gbase-kr4", + "ieee802.11a", + "ieee802.11g", + "ieee802.11n", + "ieee802.11ac", + "ieee802.11ad", + "ieee802.11ax", + "ieee802.11ay", + "ieee802.15.1", + "other-wireless", + "gsm", + "cdma", + "lte", + "sonet-oc3", + "sonet-oc12", + "sonet-oc48", + "sonet-oc192", + "sonet-oc768", + "sonet-oc1920", + "sonet-oc3840", + "1gfc-sfp", + "2gfc-sfp", + "4gfc-sfp", + "8gfc-sfpp", + "16gfc-sfpp", + "32gfc-sfp28", + "64gfc-qsfpp", + "128gfc-qsfp28", + "infiniband-sdr", + "infiniband-ddr", + "infiniband-qdr", + "infiniband-fdr10", + "infiniband-fdr", + "infiniband-edr", + "infiniband-hdr", + "infiniband-ndr", + "infiniband-xdr", + "t1", + "e1", + "t3", + "e3", + "xdsl", + "docsis", + "gpon", + "xg-pon", + "xgs-pon", + "ng-pon2", + "epon", + "10g-epon", + "cisco-stackwise", + "cisco-stackwise-plus", + "cisco-flexstack", + "cisco-flexstack-plus", + "cisco-stackwise-80", + "cisco-stackwise-160", + "cisco-stackwise-320", + "cisco-stackwise-480", + "cisco-stackwise-1t", + "juniper-vcp", + "extreme-summitstack", + "extreme-summitstack-128", + "extreme-summitstack-256", + "extreme-summitstack-512", + "other" + ] + }, + "poe_mode": { + "type": "string", + "enum": [ + "pd", + "pse" + ] + }, + "poe_type": { + "type": "string", + "enum": [ + "type1-ieee802.3af", + "type2-ieee802.3at", + "type3-ieee802.3bt", + "type4-ieee802.3bt", + "passive-24v-2pair", + "passive-24v-4pair", + "passive-48v-2pair", + "passive-48v-4pair" + ] + } + } + }, + "front-port": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "8p8c", + "8p6c", + "8p4c", + "8p2c", + "6p6c", + "6p4c", + "6p2c", + "4p4c", + "4p2c", + "gg45", + "tera-4p", + "tera-2p", + "tera-1p", + "110-punch", + "bnc", + "f", + "n", + "mrj21", + "fc", + "lc", + "lc-pc", + "lc-upc", + "lc-apc", + "lsh", + "lsh-pc", + "lsh-upc", + "lsh-apc", + "lx5", + "lx5-pc", + "lx5-upc", + "lx5-apc", + "mpo", + "mtrj", + "sc", + "sc-pc", + "sc-upc", + "sc-apc", + "st", + "cs", + "sn", + "sma-905", + "sma-906", + "urm-p2", + "urm-p4", + "urm-p8", + "splice", + "other" + ] + } + } + }, + "rear-port": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "8p8c", + "8p6c", + "8p4c", + "8p2c", + "6p6c", + "6p4c", + "6p2c", + "4p4c", + "4p2c", + "gg45", + "tera-4p", + "tera-2p", + "tera-1p", + "110-punch", + "bnc", + "f", + "n", + "mrj21", + "fc", + "lc", + "lc-pc", + "lc-upc", + "lc-apc", + "lsh", + "lsh-pc", + "lsh-upc", + "lsh-apc", + "lx5", + "lx5-pc", + "lx5-upc", + "lx5-apc", + "mpo", + "mtrj", + "sc", + "sc-pc", + "sc-upc", + "sc-apc", + "st", + "cs", + "sn", + "sma-905", + "sma-906", + "urm-p2", + "urm-p4", + "urm-p8", + "splice", + "other" + ] + } + } + } + } +} diff --git a/docs/development/release-checklist.md b/docs/development/release-checklist.md index 000948ee7..68b777111 100644 --- a/docs/development/release-checklist.md +++ b/docs/development/release-checklist.md @@ -70,6 +70,16 @@ Before each release, update each of NetBox's Python dependencies to its most rec In cases where upgrading a dependency to its most recent release is breaking, it should be constrained to its current minor version in `base_requirements.txt` with an explanatory comment and revisited for the next major NetBox release (see the [Address Constrained Dependencies](#address-constrained-dependencies) section above). +### Rebuild the Device Type Definition Schema + +Run the following command to update the device type definition validation schema: + +```nohighlight +./manage.py buildschema --write +``` + +This will automatically update the schema file at `contrib/generated_schema.json`. + ### Update Version and Changelog * Update the `VERSION` constant in `settings.py` to the new release version. diff --git a/netbox/dcim/management/commands/buildschema.py b/netbox/dcim/management/commands/buildschema.py new file mode 100644 index 000000000..44a0e95f2 --- /dev/null +++ b/netbox/dcim/management/commands/buildschema.py @@ -0,0 +1,62 @@ +import json +import os + +from django.conf import settings +from django.core.management.base import BaseCommand +from jinja2 import FileSystemLoader, Environment + +from dcim.choices import * + +TEMPLATE_FILENAME = 'devicetype_schema.jinja2' +OUTPUT_FILENAME = 'contrib/generated_schema.json' + +CHOICES_MAP = { + 'airflow_choices': DeviceAirflowChoices, + 'weight_unit_choices': WeightUnitChoices, + 'subdevice_role_choices': SubdeviceRoleChoices, + 'console_port_type_choices': ConsolePortTypeChoices, + 'console_server_port_type_choices': ConsolePortTypeChoices, + 'power_port_type_choices': PowerPortTypeChoices, + 'power_outlet_type_choices': PowerOutletTypeChoices, + 'power_outlet_feedleg_choices': PowerOutletFeedLegChoices, + 'interface_type_choices': InterfaceTypeChoices, + 'interface_poe_mode_choices': InterfacePoEModeChoices, + 'interface_poe_type_choices': InterfacePoETypeChoices, + 'front_port_type_choices': PortTypeChoices, + 'rear_port_type_choices': PortTypeChoices, +} + + +class Command(BaseCommand): + help = "Generate JSON schema for validating NetBox device type definitions" + + def add_arguments(self, parser): + parser.add_argument( + '--write', + action='store_true', + help="Write the generated schema to file" + ) + + def handle(self, *args, **kwargs): + # Initialize template + template_loader = FileSystemLoader(searchpath=f'{settings.TEMPLATES_DIR}/extras/schema/') + template_env = Environment(loader=template_loader) + template = template_env.get_template(TEMPLATE_FILENAME) + + # Render template + context = { + key: json.dumps(choices.values()) + for key, choices in CHOICES_MAP.items() + } + rendered = template.render(**context) + + if kwargs['write']: + # $root/contrib/generated_schema.json + filename = os.path.join(os.path.split(settings.BASE_DIR)[0], OUTPUT_FILENAME) + with open(filename, mode='w', encoding='UTF-8') as f: + f.write(json.dumps(json.loads(rendered), indent=4)) + f.write('\n') + f.close() + self.stdout.write(self.style.SUCCESS(f"Schema written to {filename}.")) + else: + self.stdout.write(rendered) diff --git a/netbox/templates/extras/schema/devicetype_schema.jinja2 b/netbox/templates/extras/schema/devicetype_schema.jinja2 new file mode 100644 index 000000000..b08ab24de --- /dev/null +++ b/netbox/templates/extras/schema/devicetype_schema.jinja2 @@ -0,0 +1,93 @@ +{ + "type": "object", + "additionalProperties": false, + "definitions": { + "airflow": { + "type": "string", + "enum": {{ airflow_choices }} + }, + "weight-unit": { + "type": "string", + "enum": {{ weight_unit_choices }} + }, + "subdevice-role": { + "type": "string", + "enum": {{ subdevice_role_choices }} + }, + "console-port": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": {{ console_port_type_choices }} + } + } + }, + "console-server-port": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": {{ console_server_port_type_choices }} + } + } + }, + "power-port": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": {{ power_port_type_choices }} + } + } + }, + "power-outlet": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": {{ power_outlet_type_choices }} + }, + "feed-leg": { + "type": "string", + "enum": {{ power_outlet_feedleg_choices }} + } + } + }, + "interface": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": {{ interface_type_choices }} + }, + "poe_mode": { + "type": "string", + "enum": {{ interface_poe_mode_choices }} + }, + "poe_type": { + "type": "string", + "enum": {{ interface_poe_type_choices }} + } + } + }, + "front-port": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": {{ front_port_type_choices }} + } + } + }, + "rear-port": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": {{ rear_port_type_choices}} + } + } + } + } +} From 5de9d3f15f4f5bc82ebbe5f650dcd528fe9bf250 Mon Sep 17 00:00:00 2001 From: kkthxbye <400797+kkthxbye-code@users.noreply.github.com> Date: Fri, 11 Aug 2023 17:53:16 +0200 Subject: [PATCH 161/170] Fixes #12639 - Make sure name expansions throws a validation error on decrementing ranges (#13326) * Fixes #12639 - Make sure name expansions throws a validation error on decrementing ranges * Fix pep8 * Also fail on equal start & end values --------- Co-authored-by: Jeremy Stretch --- netbox/dcim/forms/object_create.py | 5 ++++- netbox/utilities/forms/utils.py | 7 +++++++ netbox/utilities/tests/test_forms.py | 5 +++-- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/netbox/dcim/forms/object_create.py b/netbox/dcim/forms/object_create.py index 9589ab533..f37edee0a 100644 --- a/netbox/dcim/forms/object_create.py +++ b/netbox/dcim/forms/object_create.py @@ -52,7 +52,10 @@ class ComponentCreateForm(forms.Form): super().clean() # Validate that all replication fields generate an equal number of values - pattern_count = len(self.cleaned_data[self.replication_fields[0]]) + if not (patterns := self.cleaned_data.get(self.replication_fields[0])): + return + + pattern_count = len(patterns) for field_name in self.replication_fields: value_count = len(self.cleaned_data[field_name]) if self.cleaned_data[field_name] and value_count != pattern_count: diff --git a/netbox/utilities/forms/utils.py b/netbox/utilities/forms/utils.py index 5100b1714..4d737f163 100644 --- a/netbox/utilities/forms/utils.py +++ b/netbox/utilities/forms/utils.py @@ -60,6 +60,9 @@ def parse_alphanumeric_range(string): except ValueError: begin, end = dash_range, dash_range if begin.isdigit() and end.isdigit(): + if int(begin) >= int(end): + raise forms.ValidationError(f'Range "{dash_range}" is invalid.') + for n in list(range(int(begin), int(end) + 1)): values.append(n) else: @@ -71,6 +74,10 @@ def parse_alphanumeric_range(string): # Not a valid range (more than a single character) if not len(begin) == len(end) == 1: raise forms.ValidationError(f'Range "{dash_range}" is invalid.') + + if ord(begin) >= ord(end): + raise forms.ValidationError(f'Range "{dash_range}" is invalid.') + for n in list(range(ord(begin), ord(end) + 1)): values.append(chr(n)) return values diff --git a/netbox/utilities/tests/test_forms.py b/netbox/utilities/tests/test_forms.py index b8cff2996..79ba3f4d8 100644 --- a/netbox/utilities/tests/test_forms.py +++ b/netbox/utilities/tests/test_forms.py @@ -264,8 +264,9 @@ class ExpandAlphanumeric(TestCase): self.assertEqual(sorted(expand_alphanumeric_pattern('r[a-9]a')), []) def test_invalid_range_bounds(self): - self.assertEqual(sorted(expand_alphanumeric_pattern('r[9-8]a')), []) - self.assertEqual(sorted(expand_alphanumeric_pattern('r[b-a]a')), []) + with self.assertRaises(forms.ValidationError): + sorted(expand_alphanumeric_pattern('r[9-8]a')) + sorted(expand_alphanumeric_pattern('r[b-a]a')) def test_invalid_range_len(self): with self.assertRaises(forms.ValidationError): From be3f48c6778414a9f129d2bcef8bfbe259d781c1 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Mon, 14 Aug 2023 13:29:11 +0530 Subject: [PATCH 162/170] Fixed spelling for Attributes #13460 --- netbox/ipam/forms/filtersets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/ipam/forms/filtersets.py b/netbox/ipam/forms/filtersets.py index 53fecfe2f..f00082863 100644 --- a/netbox/ipam/forms/filtersets.py +++ b/netbox/ipam/forms/filtersets.py @@ -253,7 +253,7 @@ class IPRangeFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): model = IPRange fieldsets = ( (None, ('q', 'filter_id', 'tag')), - ('Attriubtes', ('family', 'vrf_id', 'status', 'role_id', 'mark_utilized')), + ('Attributes', ('family', 'vrf_id', 'status', 'role_id', 'mark_utilized')), ('Tenant', ('tenant_group_id', 'tenant_id')), ) family = forms.ChoiceField( From b5837707652da3c18739b10efbf49a5fbc2d7762 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 14 Aug 2023 08:51:16 -0400 Subject: [PATCH 163/170] Fixes #13451: Disable table ordering for custom link columns --- docs/release-notes/version-3.5.md | 1 + netbox/netbox/tables/columns.py | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/release-notes/version-3.5.md b/docs/release-notes/version-3.5.md index fe0832c3b..30ffef393 100644 --- a/docs/release-notes/version-3.5.md +++ b/docs/release-notes/version-3.5.md @@ -19,6 +19,7 @@ * [#13369](https://github.com/netbox-community/netbox/issues/13369) - Fix job termination status for failed reports * [#13414](https://github.com/netbox-community/netbox/issues/13414) - Fix support for "hide-if-unset" custom fields on bulk import forms * [#13446](https://github.com/netbox-community/netbox/issues/13446) - Don't disable bulk edit/delete buttons after deselecting "select all" checkbox +* [#13451](https://github.com/netbox-community/netbox/issues/13451) - Disable table ordering for custom link columns --- diff --git a/netbox/netbox/tables/columns.py b/netbox/netbox/tables/columns.py index 9ef327026..399b3c184 100644 --- a/netbox/netbox/tables/columns.py +++ b/netbox/netbox/tables/columns.py @@ -504,9 +504,9 @@ class CustomLinkColumn(tables.Column): """ def __init__(self, customlink, *args, **kwargs): self.customlink = customlink - kwargs['accessor'] = Accessor('pk') - if 'verbose_name' not in kwargs: - kwargs['verbose_name'] = customlink.name + kwargs.setdefault('accessor', Accessor('pk')) + kwargs.setdefault('orderable', False) + kwargs.setdefault('verbose_name', customlink.name) super().__init__(*args, **kwargs) From b9b9c065cc3723bb390b65e8ac1519beb8104e9e Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 14 Aug 2023 08:55:47 -0400 Subject: [PATCH 164/170] Changelog for #10030, #11578, #12639 --- docs/release-notes/version-3.5.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/release-notes/version-3.5.md b/docs/release-notes/version-3.5.md index 30ffef393..cc8f61802 100644 --- a/docs/release-notes/version-3.5.md +++ b/docs/release-notes/version-3.5.md @@ -4,6 +4,7 @@ ### Enhancements +* [#10030](https://github.com/netbox-community/netbox/issues/10030) - Ship a validation schema for the device type library with each release * [#11675](https://github.com/netbox-community/netbox/issues/11675) - Add support for specifying import/export route targets during VRF bulk import * [#11922](https://github.com/netbox-community/netbox/issues/11922) - Automatically populate any VDC assignments from the parent when adding a child interface via the UI * [#12889](https://github.com/netbox-community/netbox/issues/12889) - Add 400GE CFP2 interface type @@ -13,6 +14,8 @@ ### Bug Fixes +* [#11578](https://github.com/netbox-community/netbox/issues/11578) - Fix schema definition for available IP & VLAN REST API endpoints +* [#12639](https://github.com/netbox-community/netbox/issues/12639) - Raise validation error for invalid alphanumeric ranges when creating objects * [#12665](https://github.com/netbox-community/netbox/issues/12665) - Avoid escaping semicolons when rendering custom links * [#12750](https://github.com/netbox-community/netbox/issues/12750) - Automatically delete an AutoSyncRecord when its object is deleted * [#13343](https://github.com/netbox-community/netbox/issues/13343) - Fix filtering of circuits under provider network view From ea107b6b86015f3fd97995bb7ea738a0d95a83c1 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Mon, 14 Aug 2023 18:57:26 +0530 Subject: [PATCH 165/170] adds object view to allow changelog page to be opened #13463 --- netbox/templates/extras/imageattachment.html | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 netbox/templates/extras/imageattachment.html diff --git a/netbox/templates/extras/imageattachment.html b/netbox/templates/extras/imageattachment.html new file mode 100644 index 000000000..1968344cc --- /dev/null +++ b/netbox/templates/extras/imageattachment.html @@ -0,0 +1,4 @@ +{% extends 'generic/object.html' %} + +{% block tabs %} +{% endblock %} From 892c10b1f077b5d5147dd408a5d06ee3e7c3d0f8 Mon Sep 17 00:00:00 2001 From: "Joel D. Tague" Date: Fri, 11 Aug 2023 13:36:34 -0400 Subject: [PATCH 166/170] feat: add 200Gbps & 400Gbps interface speed options --- netbox/dcim/choices.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/netbox/dcim/choices.py b/netbox/dcim/choices.py index 21bd3ed7e..ba722508a 100644 --- a/netbox/dcim/choices.py +++ b/netbox/dcim/choices.py @@ -1141,6 +1141,8 @@ class InterfaceSpeedChoices(ChoiceSet): (25000000, '25 Gbps'), (40000000, '40 Gbps'), (100000000, '100 Gbps'), + (200000000, '200 Gbps'), + (400000000, '400 Gbps'), ] From e61795d5c66c8c9121177a1ece0df71835ebe723 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 15 Aug 2023 09:18:15 -0400 Subject: [PATCH 167/170] Release v3.5.8 --- .github/ISSUE_TEMPLATE/bug_report.yaml | 2 +- .github/ISSUE_TEMPLATE/feature_request.yaml | 2 +- docs/release-notes/version-3.5.md | 3 ++- netbox/netbox/settings.py | 2 +- requirements.txt | 10 +++++----- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index 42a716ae7..b43968731 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -14,7 +14,7 @@ body: attributes: label: NetBox version description: What version of NetBox are you currently running? - placeholder: v3.5.7 + placeholder: v3.5.8 validations: required: true - type: dropdown diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml index b04fda1b6..5df3069ba 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yaml +++ b/.github/ISSUE_TEMPLATE/feature_request.yaml @@ -14,7 +14,7 @@ body: attributes: label: NetBox version description: What version of NetBox are you currently running? - placeholder: v3.5.7 + placeholder: v3.5.8 validations: required: true - type: dropdown diff --git a/docs/release-notes/version-3.5.md b/docs/release-notes/version-3.5.md index cc8f61802..6d9ae5509 100644 --- a/docs/release-notes/version-3.5.md +++ b/docs/release-notes/version-3.5.md @@ -1,6 +1,6 @@ # NetBox v3.5 -## v3.5.8 (FUTURE) +## v3.5.8 (2023-08-15) ### Enhancements @@ -11,6 +11,7 @@ * [#13033](https://github.com/netbox-community/netbox/issues/13033) - Add human-friendly speed column to interfaces table * [#13151](https://github.com/netbox-community/netbox/issues/13151) - Add "assigned" filter for IP addresses * [#13368](https://github.com/netbox-community/netbox/issues/13368) - List installed plugins on the server error report page +* [#13442](https://github.com/netbox-community/netbox/issues/13442) - Add 200 and 400 Gbps speeds to dropdown choices on interface form ### Bug Fixes diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 2744ba701..acad437fc 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -25,7 +25,7 @@ from netbox.constants import RQ_QUEUE_DEFAULT, RQ_QUEUE_HIGH, RQ_QUEUE_LOW # Environment setup # -VERSION = '3.5.8-dev' +VERSION = '3.5.8' # Hostname HOSTNAME = platform.node() diff --git a/requirements.txt b/requirements.txt index f1235fa2c..2ea0f2522 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,8 @@ bleach==6.0.0 -boto3==1.28.14 +boto3==1.28.26 Django==4.1.10 django-cors-headers==4.2.0 -django-debug-toolbar==4.1.0 +django-debug-toolbar==4.2.0 django-filter==23.2 django-graphiql-debug-toolbar==0.2.0 django-mptt==0.14 @@ -16,7 +16,7 @@ django-taggit==4.0.0 django-timezone-field==5.1 djangorestframework==3.14.0 drf-spectacular==0.26.4 -drf-spectacular-sidecar==2023.7.1 +drf-spectacular-sidecar==2023.8.1 dulwich==0.21.5 feedparser==6.0.10 graphene-django==3.0.0 @@ -27,9 +27,9 @@ mkdocs-material==9.1.21 mkdocstrings[python-legacy]==0.22.0 netaddr==0.8.0 Pillow==10.0.0 -psycopg2-binary==2.9.6 +psycopg2-binary==2.9.7 PyYAML==6.0.1 -sentry-sdk==1.28.1 +sentry-sdk==1.29.2 social-auth-app-django==5.2.0 social-auth-core[openidconnect]==4.4.2 svgwrite==1.4.3 From 1c9a8ec6bd0a4b803652a721cf81e91a0bdd5340 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 15 Aug 2023 10:00:24 -0400 Subject: [PATCH 168/170] PRVB --- docs/release-notes/version-3.5.md | 4 ++++ netbox/netbox/settings.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/version-3.5.md b/docs/release-notes/version-3.5.md index 6d9ae5509..f7778275b 100644 --- a/docs/release-notes/version-3.5.md +++ b/docs/release-notes/version-3.5.md @@ -1,5 +1,9 @@ # NetBox v3.5 +## v3.5.9 (FUTURE) + +--- + ## v3.5.8 (2023-08-15) ### Enhancements diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index acad437fc..aace6745a 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -25,7 +25,7 @@ from netbox.constants import RQ_QUEUE_DEFAULT, RQ_QUEUE_HIGH, RQ_QUEUE_LOW # Environment setup # -VERSION = '3.5.8' +VERSION = '3.5.9-dev' # Hostname HOSTNAME = platform.node() From 9450ce4c3a8e24a5bbf0e82fae1f4f944621ca27 Mon Sep 17 00:00:00 2001 From: jose_d Date: Tue, 15 Aug 2023 16:19:31 +0200 Subject: [PATCH 169/170] upgrading.md: there shouldbe OLDVER instead of NEWVER --- docs/installation/upgrading.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation/upgrading.md b/docs/installation/upgrading.md index 95304cd98..adcd91310 100644 --- a/docs/installation/upgrading.md +++ b/docs/installation/upgrading.md @@ -59,7 +59,7 @@ Copy `local_requirements.txt`, `configuration.py`, and `ldap_config.py` (if pres ```no-highlight # Set $OLDVER to the NetBox version currently installed -NEWVER=3.4.9 +OLDVER=3.4.9 sudo cp /opt/netbox-$OLDVER/local_requirements.txt /opt/netbox/ sudo cp /opt/netbox-$OLDVER/netbox/netbox/configuration.py /opt/netbox/netbox/netbox/ sudo cp /opt/netbox-$OLDVER/netbox/netbox/ldap_config.py /opt/netbox/netbox/netbox/ From 16e2283d192c9726c5010144df576baa3b16ccd7 Mon Sep 17 00:00:00 2001 From: Alexander Haase Date: Thu, 3 Aug 2023 12:57:01 +0200 Subject: [PATCH 170/170] Fix git DataSource clone authentication Anonymous git clones (in GitLab) require the username and password not to be set in order to successfully clone. This patch will define clone args only, if the username passed is not empty. --- netbox/core/data_backends.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/netbox/core/data_backends.py b/netbox/core/data_backends.py index 43e6f4e79..8863e1aef 100644 --- a/netbox/core/data_backends.py +++ b/netbox/core/data_backends.py @@ -103,12 +103,13 @@ class GitBackend(DataBackend): } if self.url_scheme in ('http', 'https'): - clone_args.update( - { - "username": self.params.get('username'), - "password": self.params.get('password'), - } - ) + if self.params.get('username'): + clone_args.update( + { + "username": self.params.get('username'), + "password": self.params.get('password'), + } + ) if settings.HTTP_PROXIES and self.url_scheme in ('http', 'https'): if proxy := settings.HTTP_PROXIES.get(self.url_scheme):