From e095ec6860f0864a35688d9b15b3a8073976e8b8 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 29 May 2024 11:33:09 -0400 Subject: [PATCH 01/13] Fixes #13422: Rebuild MPTT trees for applicable models when merging staged changes --- netbox/extras/models/staging.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/netbox/extras/models/staging.py b/netbox/extras/models/staging.py index 6e381ce70..7ffbde089 100644 --- a/netbox/extras/models/staging.py +++ b/netbox/extras/models/staging.py @@ -4,6 +4,7 @@ from django.contrib.auth import get_user_model from django.contrib.contenttypes.fields import GenericForeignKey from django.db import models, transaction from django.utils.translation import gettext_lazy as _ +from mptt.models import MPTTModel from extras.choices import ChangeActionChoices from netbox.models import ChangeLoggedModel @@ -124,6 +125,11 @@ class StagedChange(CustomValidationMixin, EventRulesMixin, models.Model): instance = self.model.objects.get(pk=self.object_id) logger.info(f'Deleting {self.model._meta.verbose_name} {instance}') instance.delete() + + # Rebuild the MPTT tree where applicable + if issubclass(self.model, MPTTModel): + self.model.objects.rebuild() + apply.alters_data = True def get_action_color(self): From 26a856f57cdf839c39563099543e7dfc620f36a6 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 31 May 2024 10:29:53 -0400 Subject: [PATCH 02/13] Changelog for #13422, #14810, #15489, #16202, #16286, #16290 --- docs/release-notes/version-4.0.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/release-notes/version-4.0.md b/docs/release-notes/version-4.0.md index 14fdbd1d0..d837d0cf2 100644 --- a/docs/release-notes/version-4.0.md +++ b/docs/release-notes/version-4.0.md @@ -2,6 +2,18 @@ ## v4.0.4 (FUTURE) +### Enhancements + +* [#14810](https://github.com/netbox-community/netbox/issues/14810) - Enable contact assignment for services +* [#15489](https://github.com/netbox-community/netbox/issues/15489) - Add 1000Base-TX interface type +* [#16290](https://github.com/netbox-community/netbox/issues/16290) - Capture entire object in changelog data (but continue to display only non-internal attributes) + +### Bug Fixes + +* [#13422](https://github.com/netbox-community/netbox/issues/13422) - Rebuild MPTT trees for applicable models after merging staged changes +* [#16202](https://github.com/netbox-community/netbox/issues/16202) - Fix site map button URL for certain localizations +* [#16286](https://github.com/netbox-community/netbox/issues/16286) - Fix global search support for provider accounts + --- ## v4.0.3 (2024-05-22) From 0dde0b506e942661d234cb235e67b5b8694a8b44 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 31 May 2024 13:00:07 -0400 Subject: [PATCH 03/13] Fixes #16312: Fix object list navigation for dashboard widgets --- netbox/extras/dashboard/widgets.py | 1 + 1 file changed, 1 insertion(+) diff --git a/netbox/extras/dashboard/widgets.py b/netbox/extras/dashboard/widgets.py index a3d7f05a3..add81a318 100644 --- a/netbox/extras/dashboard/widgets.py +++ b/netbox/extras/dashboard/widgets.py @@ -265,6 +265,7 @@ class ObjectListWidget(DashboardWidget): parameters = self.config.get('url_params') or {} if page_size := self.config.get('page_size'): parameters['per_page'] = page_size + parameters['embedded'] = True if parameters: try: From e18e6cf75625e9c0ca2256ed66d32fde32adce4a Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 1 Jun 2024 05:02:24 +0000 Subject: [PATCH 04/13] Update source translation strings --- netbox/translations/en/LC_MESSAGES/django.po | 32 ++++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/netbox/translations/en/LC_MESSAGES/django.po b/netbox/translations/en/LC_MESSAGES/django.po index 51072d017..bb938e9d7 100644 --- a/netbox/translations/en/LC_MESSAGES/django.po +++ b/netbox/translations/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-05-30 14:23+0000\n" +"POT-Creation-Date: 2024-06-01 05:02+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -1097,7 +1097,7 @@ msgstr "" #: netbox/extras/models/configs.py:45 netbox/extras/models/configs.py:219 #: netbox/extras/models/customfields.py:123 netbox/extras/models/models.py:60 #: netbox/extras/models/models.py:186 netbox/extras/models/models.py:424 -#: netbox/extras/models/models.py:539 netbox/extras/models/staging.py:31 +#: netbox/extras/models/models.py:539 netbox/extras/models/staging.py:32 #: netbox/extras/models/tags.py:32 netbox/netbox/models/__init__.py:109 #: netbox/netbox/models/__init__.py:144 netbox/netbox/models/__init__.py:190 #: netbox/users/models/permissions.py:24 netbox/users/models/tokens.py:58 @@ -1138,7 +1138,7 @@ msgstr "" #: netbox/extras/models/models.py:181 netbox/extras/models/models.py:324 #: netbox/extras/models/models.py:420 netbox/extras/models/models.py:529 #: netbox/extras/models/models.py:624 netbox/extras/models/scripts.py:30 -#: netbox/extras/models/staging.py:26 netbox/ipam/models/asns.py:18 +#: netbox/extras/models/staging.py:27 netbox/ipam/models/asns.py:18 #: netbox/ipam/models/fhrp.py:25 netbox/ipam/models/services.py:52 #: netbox/ipam/models/services.py:88 netbox/ipam/models/vlans.py:26 #: netbox/ipam/models/vlans.py:164 netbox/ipam/models/vrfs.py:22 @@ -1918,7 +1918,7 @@ msgid "completed" msgstr "" #: netbox/core/models/jobs.py:91 netbox/extras/models/models.py:121 -#: netbox/extras/models/staging.py:87 +#: netbox/extras/models/staging.py:88 msgid "data" msgstr "" @@ -6740,33 +6740,33 @@ msgstr "" msgid "Invalid format. URL parameters must be passed as a dictionary." msgstr "" -#: netbox/extras/dashboard/widgets.py:283 +#: netbox/extras/dashboard/widgets.py:284 msgid "RSS Feed" msgstr "" -#: netbox/extras/dashboard/widgets.py:288 +#: netbox/extras/dashboard/widgets.py:289 msgid "Embed an RSS feed from an external website." msgstr "" -#: netbox/extras/dashboard/widgets.py:295 +#: netbox/extras/dashboard/widgets.py:296 msgid "Feed URL" msgstr "" -#: netbox/extras/dashboard/widgets.py:300 +#: netbox/extras/dashboard/widgets.py:301 msgid "The maximum number of objects to display" msgstr "" -#: netbox/extras/dashboard/widgets.py:305 +#: netbox/extras/dashboard/widgets.py:306 msgid "How long to stored the cached content (in seconds)" msgstr "" -#: netbox/extras/dashboard/widgets.py:357 netbox/templates/account/base.html:10 +#: netbox/extras/dashboard/widgets.py:358 netbox/templates/account/base.html:10 #: netbox/templates/account/bookmarks.html:7 #: netbox/templates/inc/user_menu.html:30 msgid "Bookmarks" msgstr "" -#: netbox/extras/dashboard/widgets.py:361 +#: netbox/extras/dashboard/widgets.py:362 msgid "Show your personal bookmarks" msgstr "" @@ -7385,7 +7385,7 @@ msgstr "" msgid "request ID" msgstr "" -#: netbox/extras/models/change_logging.py:52 netbox/extras/models/staging.py:69 +#: netbox/extras/models/change_logging.py:52 netbox/extras/models/staging.py:70 msgid "action" msgstr "" @@ -8096,19 +8096,19 @@ msgstr "" msgid "cached values" msgstr "" -#: netbox/extras/models/staging.py:44 +#: netbox/extras/models/staging.py:45 msgid "branch" msgstr "" -#: netbox/extras/models/staging.py:45 +#: netbox/extras/models/staging.py:46 msgid "branches" msgstr "" -#: netbox/extras/models/staging.py:97 +#: netbox/extras/models/staging.py:98 msgid "staged change" msgstr "" -#: netbox/extras/models/staging.py:98 +#: netbox/extras/models/staging.py:99 msgid "staged changes" msgstr "" From 602754439a55ff13cf9f1c21e7b4d52b608bf16d Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 3 Jun 2024 08:01:50 -0400 Subject: [PATCH 05/13] Update workflows to use most recent release of each action --- .github/workflows/auto-assign-issue.yml | 2 +- .github/workflows/ci.yml | 6 +++--- .github/workflows/update-translation-strings.yml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/auto-assign-issue.yml b/.github/workflows/auto-assign-issue.yml index 4e93d9f0d..309f79800 100644 --- a/.github/workflows/auto-assign-issue.yml +++ b/.github/workflows/auto-assign-issue.yml @@ -12,7 +12,7 @@ jobs: auto-assign: runs-on: ubuntu-latest steps: - - uses: pozil/auto-assign-issue@v1 + - uses: pozil/auto-assign-issue@v2 if: "contains(github.event.issue.labels.*.name, 'status: needs triage')" with: # Weighted assignments diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b32f519bc..a84359bf9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,12 +45,12 @@ jobs: uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} @@ -58,7 +58,7 @@ jobs: run: npm install -g yarn - name: Setup Node.js with Yarn Caching - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} cache: yarn diff --git a/.github/workflows/update-translation-strings.yml b/.github/workflows/update-translation-strings.yml index 5b0c09300..bcd68c887 100644 --- a/.github/workflows/update-translation-strings.yml +++ b/.github/workflows/update-translation-strings.yml @@ -22,7 +22,7 @@ jobs: uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: 3.11 From 24d02cb3818a97202a27c00fe0204959edef60ef Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 31 May 2024 09:35:18 -0400 Subject: [PATCH 06/13] Fixes #15194: Prevent enqueuing duplicate events for an object --- netbox/extras/context_managers.py | 7 +++--- netbox/extras/events.py | 32 +++++++++++++++---------- netbox/extras/signals.py | 29 +++++++--------------- netbox/extras/tests/test_event_rules.py | 27 +++++++++++++++++++-- netbox/netbox/context.py | 2 +- 5 files changed, 57 insertions(+), 40 deletions(-) diff --git a/netbox/extras/context_managers.py b/netbox/extras/context_managers.py index 8de47465e..e72cb8cc2 100644 --- a/netbox/extras/context_managers.py +++ b/netbox/extras/context_managers.py @@ -13,13 +13,14 @@ def event_tracking(request): :param request: WSGIRequest object with a unique `id` set """ current_request.set(request) - events_queue.set([]) + events_queue.set({}) yield # Flush queued webhooks to RQ - flush_events(events_queue.get()) + if events := list(events_queue.get().values()): + flush_events(events) # Clear context vars current_request.set(None) - events_queue.set([]) + events_queue.set({}) diff --git a/netbox/extras/events.py b/netbox/extras/events.py index 34d2ec159..22ce26ba9 100644 --- a/netbox/extras/events.py +++ b/netbox/extras/events.py @@ -58,15 +58,21 @@ def enqueue_object(queue, instance, user, request_id, action): if model_name not in registry['model_features']['event_rules'].get(app_label, []): return - queue.append({ - 'content_type': ContentType.objects.get_for_model(instance), - 'object_id': instance.pk, - 'event': action, - 'data': serialize_for_event(instance), - 'snapshots': get_snapshots(instance, action), - 'username': user.username, - 'request_id': request_id - }) + assert instance.pk is not None + key = f'{app_label}.{model_name}:{instance.pk}' + if key in queue: + queue[key]['data'] = serialize_for_event(instance) + queue[key]['snapshots']['postchange'] = get_snapshots(instance, action)['postchange'] + else: + queue[key] = { + 'content_type': ContentType.objects.get_for_model(instance), + 'object_id': instance.pk, + 'event': action, + 'data': serialize_for_event(instance), + 'snapshots': get_snapshots(instance, action), + 'username': user.username, + 'request_id': request_id + } def process_event_rules(event_rules, model_name, event, data, username=None, snapshots=None, request_id=None): @@ -163,14 +169,14 @@ def process_event_queue(events): ) -def flush_events(queue): +def flush_events(events): """ - Flush a list of object representation to RQ for webhook processing. + Flush a list of object representations to RQ for event processing. """ - if queue: + if events: for name in settings.EVENTS_PIPELINE: try: func = import_string(name) - func(queue) + func(events) except Exception as e: logger.error(_("Cannot import events pipeline {name} error: {error}").format(name=name, error=e)) diff --git a/netbox/extras/signals.py b/netbox/extras/signals.py index 2813ed7ae..9d439ace9 100644 --- a/netbox/extras/signals.py +++ b/netbox/extras/signals.py @@ -55,18 +55,6 @@ def run_validators(instance, validators): clear_events = Signal() -def is_same_object(instance, webhook_data, request_id): - """ - Compare the given instance to the most recent queued webhook object, returning True - if they match. This check is used to avoid creating duplicate webhook entries. - """ - return ( - ContentType.objects.get_for_model(instance) == webhook_data['content_type'] and - instance.pk == webhook_data['object_id'] and - request_id == webhook_data['request_id'] - ) - - @receiver((post_save, m2m_changed)) def handle_changed_object(sender, instance, **kwargs): """ @@ -112,14 +100,13 @@ def handle_changed_object(sender, instance, **kwargs): objectchange.request_id = request.id objectchange.save() - # If this is an M2M change, update the previously queued webhook (from post_save) + # Ensure that we're working with fresh M2M assignments + if m2m_changed: + instance.refresh_from_db() + + # Enqueue the object for event processing queue = events_queue.get() - if m2m_changed and queue and is_same_object(instance, queue[-1], request.id): - instance.refresh_from_db() # Ensure that we're working with fresh M2M assignments - queue[-1]['data'] = serialize_for_event(instance) - queue[-1]['snapshots']['postchange'] = get_snapshots(instance, action)['postchange'] - else: - enqueue_object(queue, instance, request.user, request.id, action) + enqueue_object(queue, instance, request.user, request.id, action) events_queue.set(queue) # Increment metric counters @@ -179,7 +166,7 @@ def handle_deleted_object(sender, instance, **kwargs): obj.snapshot() # Ensure the change record includes the "before" state getattr(obj, related_field_name).remove(instance) - # Enqueue webhooks + # Enqueue the object for event processing queue = events_queue.get() enqueue_object(queue, instance, request.user, request.id, ObjectChangeActionChoices.ACTION_DELETE) events_queue.set(queue) @@ -195,7 +182,7 @@ def clear_events_queue(sender, **kwargs): """ logger = logging.getLogger('events') logger.info(f"Clearing {len(events_queue.get())} queued events ({sender})") - events_queue.set([]) + events_queue.set({}) # diff --git a/netbox/extras/tests/test_event_rules.py b/netbox/extras/tests/test_event_rules.py index 8cea2078a..a1dd8b48e 100644 --- a/netbox/extras/tests/test_event_rules.py +++ b/netbox/extras/tests/test_event_rules.py @@ -4,6 +4,7 @@ from unittest.mock import patch import django_rq from django.http import HttpResponse +from django.test import RequestFactory from django.urls import reverse from requests import Session from rest_framework import status @@ -12,6 +13,7 @@ from core.models import ObjectType from dcim.choices import SiteStatusChoices from dcim.models import Site from extras.choices import EventRuleActionChoices, ObjectChangeActionChoices +from extras.context_managers import event_tracking from extras.events import enqueue_object, flush_events, serialize_for_event from extras.models import EventRule, Tag, Webhook from extras.webhooks import generate_signature, send_webhook @@ -360,7 +362,7 @@ class EventRuleTest(APITestCase): return HttpResponse() # Enqueue a webhook for processing - webhooks_queue = [] + webhooks_queue = {} site = Site.objects.create(name='Site 1', slug='site-1') enqueue_object( webhooks_queue, @@ -369,7 +371,7 @@ class EventRuleTest(APITestCase): request_id=request_id, action=ObjectChangeActionChoices.ACTION_CREATE ) - flush_events(webhooks_queue) + flush_events(list(webhooks_queue.values())) # Retrieve the job from queue job = self.queue.jobs[0] @@ -377,3 +379,24 @@ class EventRuleTest(APITestCase): # Patch the Session object with our dummy_send() method, then process the webhook for sending with patch.object(Session, 'send', dummy_send) as mock_send: send_webhook(**job.kwargs) + + def test_duplicate_triggers(self): + """ + Test for erroneous duplicate event triggers resulting from saving an object multiple times + within the span of a single request. + """ + url = reverse('dcim:site_add') + request = RequestFactory().get(url) + request.id = uuid.uuid4() + request.user = self.user + + self.assertEqual(self.queue.count, 0, msg="Unexpected jobs found in queue") + + with event_tracking(request): + site = Site(name='Site 1', slug='site-1') + site.save() + + # Save the site a second time + site.save() + + self.assertEqual(self.queue.count, 1, msg="Duplicate jobs found in queue") diff --git a/netbox/netbox/context.py b/netbox/netbox/context.py index 56e41cb63..744c36df4 100644 --- a/netbox/netbox/context.py +++ b/netbox/netbox/context.py @@ -7,4 +7,4 @@ __all__ = ( current_request = ContextVar('current_request', default=None) -events_queue = ContextVar('events_queue', default=[]) +events_queue = ContextVar('events_queue', default=dict()) From fdad59c8cc7bad24261e3f265c1843a9056b44f8 Mon Sep 17 00:00:00 2001 From: Daniel Sheppard Date: Mon, 3 Jun 2024 07:47:53 -0500 Subject: [PATCH 07/13] Fixes: #16039 - Fix row highlighting on device components and VM interfaces (#16044) * Fix row highlighting * Minor fix for VMInterfaces * Move duplicated dicts into inheritable meta class * Add CableTerminationTable.Meta class for inheritance of the row_attrs to each descendant Meta class. --- netbox/dcim/tables/devices.py | 46 ++++++------------- .../virtualization/tables/virtualmachines.py | 2 + 2 files changed, 16 insertions(+), 32 deletions(-) diff --git a/netbox/dcim/tables/devices.py b/netbox/dcim/tables/devices.py index 4925fb517..7fa307bc8 100644 --- a/netbox/dcim/tables/devices.py +++ b/netbox/dcim/tables/devices.py @@ -43,14 +43,6 @@ MODULEBAY_STATUS = """ """ -def get_cabletermination_row_class(record): - if record.mark_connected: - return 'success' - elif record.cable: - return record.cable.get_status_color() - return '' - - # # Device roles # @@ -339,6 +331,14 @@ class CableTerminationTable(NetBoxTable): verbose_name=_('Mark Connected'), ) + class Meta: + row_attrs = { + 'data-name': lambda record: record.name, + 'data-mark-connected': lambda record: "true" if record.mark_connected else "false", + 'data-cable-status': lambda record: record.cable.status if record.cable else "", + 'data-type': lambda record: record.type + } + def value_link_peer(self, value): return ', '.join([ f"{termination.parent_object} > {termination}" for termination in value @@ -386,16 +386,13 @@ class DeviceConsolePortTable(ConsolePortTable): extra_buttons=CONSOLEPORT_BUTTONS ) - class Meta(DeviceComponentTable.Meta): + class Meta(CableTerminationTable.Meta, DeviceComponentTable.Meta): model = models.ConsolePort fields = ( 'pk', 'id', 'name', 'module_bay', 'module', 'label', 'type', 'speed', 'description', 'mark_connected', 'cable', 'cable_color', 'link_peer', 'connection', 'tags', 'actions' ) default_columns = ('pk', 'name', 'label', 'type', 'speed', 'description', 'cable', 'connection') - row_attrs = { - 'class': get_cabletermination_row_class - } class ConsoleServerPortTable(ModularDeviceComponentTable, PathEndpointTable): @@ -431,16 +428,13 @@ class DeviceConsoleServerPortTable(ConsoleServerPortTable): extra_buttons=CONSOLESERVERPORT_BUTTONS ) - class Meta(DeviceComponentTable.Meta): + class Meta(CableTerminationTable.Meta, DeviceComponentTable.Meta): model = models.ConsoleServerPort fields = ( 'pk', 'id', 'name', 'module_bay', 'module', 'label', 'type', 'speed', 'description', 'mark_connected', 'cable', 'cable_color', 'link_peer', 'connection', 'tags', 'actions', ) default_columns = ('pk', 'name', 'label', 'type', 'speed', 'description', 'cable', 'connection') - row_attrs = { - 'class': get_cabletermination_row_class - } class PowerPortTable(ModularDeviceComponentTable, PathEndpointTable): @@ -483,7 +477,7 @@ class DevicePowerPortTable(PowerPortTable): extra_buttons=POWERPORT_BUTTONS ) - class Meta(DeviceComponentTable.Meta): + class Meta(CableTerminationTable.Meta, DeviceComponentTable.Meta): model = models.PowerPort fields = ( 'pk', 'id', 'name', 'module_bay', 'module', 'label', 'type', 'maximum_draw', 'allocated_draw', @@ -492,9 +486,6 @@ class DevicePowerPortTable(PowerPortTable): default_columns = ( 'pk', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description', 'cable', 'connection', ) - row_attrs = { - 'class': get_cabletermination_row_class - } class PowerOutletTable(ModularDeviceComponentTable, PathEndpointTable): @@ -534,7 +525,7 @@ class DevicePowerOutletTable(PowerOutletTable): extra_buttons=POWEROUTLET_BUTTONS ) - class Meta(DeviceComponentTable.Meta): + class Meta(CableTerminationTable.Meta, DeviceComponentTable.Meta): model = models.PowerOutlet fields = ( 'pk', 'id', 'name', 'module_bay', 'module', 'label', 'type', 'power_port', 'feed_leg', 'description', @@ -543,9 +534,6 @@ class DevicePowerOutletTable(PowerOutletTable): default_columns = ( 'pk', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description', 'cable', 'connection', ) - row_attrs = { - 'class': get_cabletermination_row_class - } class BaseInterfaceTable(NetBoxTable): @@ -733,7 +721,7 @@ class DeviceFrontPortTable(FrontPortTable): extra_buttons=FRONTPORT_BUTTONS ) - class Meta(DeviceComponentTable.Meta): + class Meta(CableTerminationTable.Meta, DeviceComponentTable.Meta): model = models.FrontPort fields = ( 'pk', 'id', 'name', 'module_bay', 'module', 'label', 'type', 'rear_port', 'rear_port_position', @@ -742,9 +730,6 @@ class DeviceFrontPortTable(FrontPortTable): default_columns = ( 'pk', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description', 'cable', 'link_peer', ) - row_attrs = { - 'class': get_cabletermination_row_class - } class RearPortTable(ModularDeviceComponentTable, CableTerminationTable): @@ -783,7 +768,7 @@ class DeviceRearPortTable(RearPortTable): extra_buttons=REARPORT_BUTTONS ) - class Meta(DeviceComponentTable.Meta): + class Meta(CableTerminationTable.Meta, DeviceComponentTable.Meta): model = models.RearPort fields = ( 'pk', 'id', 'name', 'module_bay', 'module', 'label', 'type', 'positions', 'description', 'mark_connected', @@ -792,9 +777,6 @@ class DeviceRearPortTable(RearPortTable): default_columns = ( 'pk', 'name', 'label', 'type', 'positions', 'description', 'cable', 'link_peer', ) - row_attrs = { - 'class': get_cabletermination_row_class - } class DeviceBayTable(DeviceComponentTable): diff --git a/netbox/virtualization/tables/virtualmachines.py b/netbox/virtualization/tables/virtualmachines.py index ba5360a62..9d194d268 100644 --- a/netbox/virtualization/tables/virtualmachines.py +++ b/netbox/virtualization/tables/virtualmachines.py @@ -173,6 +173,8 @@ class VirtualMachineVMInterfaceTable(VMInterfaceTable): default_columns = ('pk', 'name', 'enabled', 'mac_address', 'mtu', 'mode', 'description', 'ip_addresses') row_attrs = { 'data-name': lambda record: record.name, + 'data-virtual': lambda record: "true", + 'data-enabled': lambda record: "true" if record.enabled else "false", } From 8e48e939aab3b75a50c73cb46b2cc4c30f801ae8 Mon Sep 17 00:00:00 2001 From: Arthur Hanson Date: Mon, 3 Jun 2024 07:24:01 -0700 Subject: [PATCH 08/13] 16261 fix graphql lookup for MultiValueCharFilter fields (#16354) * 16261 fix graphql lookup for MultiValueCharFilter fields * 16261 fix graphql lookup for MultiValueCharFilter fields * 16261 fixup test * 16261 fixup test * Omit redundant assignment --------- Co-authored-by: Jeremy Stretch --- netbox/ipam/tests/test_api.py | 2 +- netbox/netbox/graphql/filter_mixins.py | 5 +++-- netbox/utilities/testing/api.py | 12 ++++++++++-- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/netbox/ipam/tests/test_api.py b/netbox/ipam/tests/test_api.py index 20ba35c6b..2cf7a2f1c 100644 --- a/netbox/ipam/tests/test_api.py +++ b/netbox/ipam/tests/test_api.py @@ -649,7 +649,7 @@ class IPAddressTest(APIViewTestCases.APIViewTestCase): 'description': 'New description', } graphql_filter = { - 'address': '192.168.0.1/24', + 'address': {'lookup': 'i_exact', 'value': '192.168.0.1/24'}, } @classmethod diff --git a/netbox/netbox/graphql/filter_mixins.py b/netbox/netbox/graphql/filter_mixins.py index 322435c72..5075e9aa2 100644 --- a/netbox/netbox/graphql/filter_mixins.py +++ b/netbox/netbox/graphql/filter_mixins.py @@ -23,8 +23,9 @@ def map_strawberry_type(field): elif isinstance(field, MultiValueArrayFilter): pass elif isinstance(field, MultiValueCharFilter): - should_create_function = True - attr_type = List[str] | None + # Note: Need to use the legacy FilterLookup from filters, not from + # strawberry_django.FilterLookup as we currently have USE_DEPRECATED_FILTERS + attr_type = strawberry_django.filters.FilterLookup[str] | None elif isinstance(field, MultiValueDateFilter): attr_type = auto elif isinstance(field, MultiValueDateTimeFilter): diff --git a/netbox/utilities/testing/api.py b/netbox/utilities/testing/api.py index 019d6e6ca..62ac817e2 100644 --- a/netbox/utilities/testing/api.py +++ b/netbox/utilities/testing/api.py @@ -493,10 +493,18 @@ class APIViewTestCases: def _build_filtered_query(self, name, **filters): """ - Create a filtered query: i.e. ip_address_list(filters: {address: "1.1.1.1/24"}){. + Create a filtered query: i.e. device_list(filters: {name: {i_contains: "akron"}}){. """ + # TODO: This should be extended to support AND, OR multi-lookups if filters: - filter_string = ', '.join(f'{k}: "{v}"' for k, v in filters.items()) + for field_name, params in filters.items(): + lookup = params['lookup'] + value = params['value'] + if lookup: + query = f'{{{lookup}: "{value}"}}' + filter_string = f'{field_name}: {query}' + else: + filter_string = f'{field_name}: "{value}"' filter_string = f'(filters: {{{filter_string}}})' else: filter_string = '' From 291e0665d07c257f9ee98eee4db5720ab2b3dba9 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 4 Jun 2024 05:02:13 +0000 Subject: [PATCH 09/13] Update source translation strings --- netbox/translations/en/LC_MESSAGES/django.po | 198 +++++++++---------- 1 file changed, 99 insertions(+), 99 deletions(-) diff --git a/netbox/translations/en/LC_MESSAGES/django.po b/netbox/translations/en/LC_MESSAGES/django.po index bb938e9d7..af150e24c 100644 --- a/netbox/translations/en/LC_MESSAGES/django.po +++ b/netbox/translations/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-06-01 05:02+0000\n" +"POT-Creation-Date: 2024-06-04 05:02+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -172,7 +172,7 @@ msgstr "" #: netbox/dcim/forms/filtersets.py:1524 netbox/dcim/forms/model_forms.py:136 #: netbox/dcim/forms/model_forms.py:164 netbox/dcim/forms/model_forms.py:206 #: netbox/dcim/forms/model_forms.py:406 netbox/dcim/forms/model_forms.py:668 -#: netbox/dcim/forms/object_create.py:391 netbox/dcim/tables/devices.py:158 +#: netbox/dcim/forms/object_create.py:391 netbox/dcim/tables/devices.py:150 #: netbox/dcim/tables/power.py:26 netbox/dcim/tables/power.py:93 #: netbox/dcim/tables/racks.py:62 netbox/dcim/tables/racks.py:138 #: netbox/dcim/tables/sites.py:129 netbox/extras/filtersets.py:477 @@ -492,8 +492,8 @@ msgstr "" #: netbox/dcim/forms/bulk_edit.py:1071 netbox/dcim/forms/bulk_edit.py:1098 #: netbox/dcim/forms/bulk_edit.py:1571 netbox/dcim/forms/filtersets.py:983 #: netbox/dcim/forms/filtersets.py:1359 netbox/dcim/forms/filtersets.py:1380 -#: netbox/dcim/tables/devices.py:699 netbox/dcim/tables/devices.py:759 -#: netbox/dcim/tables/devices.py:986 netbox/dcim/tables/devicetypes.py:245 +#: netbox/dcim/tables/devices.py:687 netbox/dcim/tables/devices.py:744 +#: netbox/dcim/tables/devices.py:968 netbox/dcim/tables/devicetypes.py:245 #: netbox/dcim/tables/devicetypes.py:260 netbox/dcim/tables/racks.py:32 #: netbox/extras/forms/bulk_edit.py:260 netbox/extras/tables/tables.py:333 #: netbox/templates/circuits/circuittype.html:30 @@ -527,8 +527,8 @@ msgstr "" #: netbox/dcim/forms/filtersets.py:1354 netbox/dcim/forms/filtersets.py:1375 #: netbox/dcim/forms/model_forms.py:643 netbox/dcim/forms/model_forms.py:649 #: netbox/dcim/forms/object_import.py:84 netbox/dcim/forms/object_import.py:113 -#: netbox/dcim/forms/object_import.py:145 netbox/dcim/tables/devices.py:183 -#: netbox/dcim/tables/devices.py:815 netbox/dcim/tables/power.py:77 +#: netbox/dcim/forms/object_import.py:145 netbox/dcim/tables/devices.py:175 +#: netbox/dcim/tables/devices.py:797 netbox/dcim/tables/power.py:77 #: netbox/extras/forms/bulk_import.py:39 netbox/extras/tables/tables.py:283 #: netbox/extras/tables/tables.py:355 netbox/extras/tables/tables.py:473 #: netbox/netbox/tables/tables.py:239 netbox/templates/circuits/circuit.html:30 @@ -583,8 +583,8 @@ msgstr "" #: netbox/dcim/forms/filtersets.py:282 netbox/dcim/forms/filtersets.py:728 #: netbox/dcim/forms/filtersets.py:843 netbox/dcim/forms/filtersets.py:877 #: netbox/dcim/forms/filtersets.py:978 netbox/dcim/forms/filtersets.py:1089 -#: netbox/dcim/tables/devices.py:145 netbox/dcim/tables/devices.py:818 -#: netbox/dcim/tables/devices.py:1046 netbox/dcim/tables/modules.py:69 +#: netbox/dcim/tables/devices.py:137 netbox/dcim/tables/devices.py:800 +#: netbox/dcim/tables/devices.py:1028 netbox/dcim/tables/modules.py:69 #: netbox/dcim/tables/power.py:74 netbox/dcim/tables/racks.py:66 #: netbox/dcim/tables/sites.py:82 netbox/dcim/tables/sites.py:133 #: netbox/ipam/forms/bulk_edit.py:241 netbox/ipam/forms/bulk_edit.py:290 @@ -868,7 +868,7 @@ msgstr "" #: netbox/dcim/forms/filtersets.py:1406 netbox/dcim/forms/filtersets.py:1420 #: netbox/dcim/forms/model_forms.py:179 netbox/dcim/forms/model_forms.py:211 #: netbox/dcim/forms/model_forms.py:411 netbox/dcim/forms/model_forms.py:673 -#: netbox/dcim/tables/devices.py:162 netbox/dcim/tables/power.py:30 +#: netbox/dcim/tables/devices.py:154 netbox/dcim/tables/power.py:30 #: netbox/dcim/tables/racks.py:58 netbox/dcim/tables/racks.py:143 #: netbox/extras/filtersets.py:488 netbox/extras/forms/filtersets.py:329 #: netbox/ipam/forms/bulk_edit.py:457 netbox/ipam/forms/filtersets.py:173 @@ -913,7 +913,7 @@ msgstr "" #: netbox/dcim/forms/filtersets.py:1055 netbox/dcim/forms/filtersets.py:1468 #: netbox/dcim/forms/filtersets.py:1492 netbox/dcim/forms/filtersets.py:1516 #: netbox/dcim/forms/model_forms.py:111 netbox/dcim/forms/object_create.py:375 -#: netbox/dcim/tables/devices.py:148 netbox/dcim/tables/sites.py:85 +#: netbox/dcim/tables/devices.py:140 netbox/dcim/tables/sites.py:85 #: netbox/extras/filtersets.py:455 netbox/ipam/forms/bulk_edit.py:206 #: netbox/ipam/forms/bulk_edit.py:438 netbox/ipam/forms/bulk_edit.py:512 #: netbox/ipam/forms/filtersets.py:217 netbox/ipam/forms/filtersets.py:422 @@ -1207,14 +1207,14 @@ msgstr "" #: netbox/core/tables/jobs.py:14 netbox/core/tables/plugins.py:13 #: netbox/core/tables/tasks.py:11 netbox/core/tables/tasks.py:115 #: netbox/dcim/forms/filtersets.py:61 netbox/dcim/forms/object_create.py:43 -#: netbox/dcim/tables/devices.py:60 netbox/dcim/tables/devices.py:97 -#: netbox/dcim/tables/devices.py:139 netbox/dcim/tables/devices.py:294 -#: netbox/dcim/tables/devices.py:380 netbox/dcim/tables/devices.py:424 -#: netbox/dcim/tables/devices.py:476 netbox/dcim/tables/devices.py:528 -#: netbox/dcim/tables/devices.py:644 netbox/dcim/tables/devices.py:726 -#: netbox/dcim/tables/devices.py:776 netbox/dcim/tables/devices.py:842 -#: netbox/dcim/tables/devices.py:957 netbox/dcim/tables/devices.py:977 -#: netbox/dcim/tables/devices.py:1006 netbox/dcim/tables/devices.py:1036 +#: netbox/dcim/tables/devices.py:52 netbox/dcim/tables/devices.py:89 +#: netbox/dcim/tables/devices.py:131 netbox/dcim/tables/devices.py:286 +#: netbox/dcim/tables/devices.py:380 netbox/dcim/tables/devices.py:421 +#: netbox/dcim/tables/devices.py:470 netbox/dcim/tables/devices.py:519 +#: netbox/dcim/tables/devices.py:632 netbox/dcim/tables/devices.py:714 +#: netbox/dcim/tables/devices.py:761 netbox/dcim/tables/devices.py:824 +#: netbox/dcim/tables/devices.py:939 netbox/dcim/tables/devices.py:959 +#: netbox/dcim/tables/devices.py:988 netbox/dcim/tables/devices.py:1018 #: netbox/dcim/tables/devicetypes.py:32 netbox/dcim/tables/power.py:22 #: netbox/dcim/tables/power.py:62 netbox/dcim/tables/racks.py:23 #: netbox/dcim/tables/racks.py:53 netbox/dcim/tables/sites.py:24 @@ -1304,7 +1304,7 @@ msgstr "" #: netbox/virtualization/tables/clusters.py:62 #: netbox/virtualization/tables/virtualmachines.py:54 #: netbox/virtualization/tables/virtualmachines.py:132 -#: netbox/virtualization/tables/virtualmachines.py:185 +#: netbox/virtualization/tables/virtualmachines.py:187 #: netbox/vpn/tables/crypto.py:18 netbox/vpn/tables/crypto.py:57 #: netbox/vpn/tables/crypto.py:93 netbox/vpn/tables/crypto.py:129 #: netbox/vpn/tables/crypto.py:158 netbox/vpn/tables/l2vpn.py:23 @@ -1344,7 +1344,7 @@ msgstr "" #: netbox/circuits/tables/circuits.py:76 netbox/circuits/tables/providers.py:48 #: netbox/circuits/tables/providers.py:82 -#: netbox/circuits/tables/providers.py:107 netbox/dcim/tables/devices.py:1019 +#: netbox/circuits/tables/providers.py:107 netbox/dcim/tables/devices.py:1001 #: netbox/dcim/tables/devicetypes.py:92 netbox/dcim/tables/modules.py:29 #: netbox/dcim/tables/modules.py:72 netbox/dcim/tables/power.py:39 #: netbox/dcim/tables/power.py:96 netbox/dcim/tables/racks.py:76 @@ -1515,7 +1515,7 @@ msgstr "" #: netbox/core/forms/bulk_edit.py:25 netbox/core/forms/filtersets.py:40 #: netbox/core/tables/data.py:26 netbox/dcim/forms/bulk_edit.py:1020 #: netbox/dcim/forms/bulk_edit.py:1293 netbox/dcim/forms/filtersets.py:1276 -#: netbox/dcim/tables/devices.py:553 netbox/dcim/tables/devicetypes.py:221 +#: netbox/dcim/tables/devices.py:541 netbox/dcim/tables/devicetypes.py:221 #: netbox/extras/forms/bulk_edit.py:98 netbox/extras/forms/bulk_edit.py:162 #: netbox/extras/forms/bulk_edit.py:221 netbox/extras/forms/filtersets.py:120 #: netbox/extras/forms/filtersets.py:207 netbox/extras/forms/filtersets.py:268 @@ -2163,7 +2163,7 @@ msgstr "" #: netbox/dcim/forms/model_forms.py:73 netbox/dcim/forms/model_forms.py:92 #: netbox/dcim/forms/model_forms.py:169 netbox/dcim/forms/model_forms.py:1007 #: netbox/dcim/forms/model_forms.py:1446 netbox/dcim/forms/object_import.py:176 -#: netbox/dcim/tables/devices.py:652 netbox/dcim/tables/devices.py:937 +#: netbox/dcim/tables/devices.py:640 netbox/dcim/tables/devices.py:919 #: netbox/extras/tables/tables.py:186 netbox/ipam/tables/fhrp.py:59 #: netbox/ipam/tables/ip.py:374 netbox/ipam/tables/services.py:44 #: netbox/templates/dcim/interface.html:102 @@ -2297,7 +2297,7 @@ msgstr "" #: netbox/dcim/choices.py:979 netbox/dcim/forms/bulk_edit.py:1303 #: netbox/dcim/forms/bulk_import.py:785 netbox/dcim/forms/model_forms.py:919 -#: netbox/dcim/tables/devices.py:656 netbox/templates/dcim/interface.html:106 +#: netbox/dcim/tables/devices.py:644 netbox/templates/dcim/interface.html:106 #: netbox/templates/virtualization/vminterface.html:43 #: netbox/virtualization/forms/bulk_edit.py:212 #: netbox/virtualization/forms/bulk_import.py:158 @@ -2781,7 +2781,7 @@ msgid "Virtual Chassis (ID)" msgstr "" #: netbox/dcim/filtersets.py:1401 netbox/dcim/forms/filtersets.py:107 -#: netbox/dcim/tables/devices.py:211 netbox/netbox/navigation/menu.py:66 +#: netbox/dcim/tables/devices.py:203 netbox/netbox/navigation/menu.py:66 #: netbox/templates/dcim/device.html:120 #: netbox/templates/dcim/device_edit.html:93 #: netbox/templates/dcim/virtualchassis.html:20 @@ -2811,7 +2811,7 @@ msgstr "" #: netbox/dcim/forms/bulk_import.py:836 netbox/dcim/forms/filtersets.py:1334 #: netbox/dcim/forms/model_forms.py:1322 #: netbox/dcim/models/device_components.py:712 -#: netbox/dcim/tables/devices.py:622 netbox/ipam/filtersets.py:316 +#: netbox/dcim/tables/devices.py:610 netbox/ipam/filtersets.py:316 #: netbox/ipam/filtersets.py:327 netbox/ipam/filtersets.py:483 #: netbox/ipam/filtersets.py:584 netbox/ipam/filtersets.py:595 #: netbox/ipam/forms/bulk_edit.py:227 netbox/ipam/forms/bulk_edit.py:282 @@ -2852,7 +2852,7 @@ msgid "L2VPN (ID)" msgstr "" #: netbox/dcim/filtersets.py:1563 netbox/dcim/forms/filtersets.py:1339 -#: netbox/dcim/tables/devices.py:570 netbox/ipam/filtersets.py:1022 +#: netbox/dcim/tables/devices.py:558 netbox/ipam/filtersets.py:1022 #: netbox/ipam/forms/filtersets.py:525 netbox/ipam/tables/vlans.py:133 #: netbox/templates/dcim/interface.html:93 netbox/templates/ipam/vlan.html:66 #: netbox/templates/vpn/l2vpntermination.html:12 @@ -2902,7 +2902,7 @@ msgstr "" msgid "Wireless LAN" msgstr "" -#: netbox/dcim/filtersets.py:1667 netbox/dcim/tables/devices.py:609 +#: netbox/dcim/filtersets.py:1667 netbox/dcim/tables/devices.py:597 msgid "Wireless link" msgstr "" @@ -2957,8 +2957,8 @@ msgstr "" #: netbox/dcim/forms/bulk_create.py:112 netbox/dcim/forms/filtersets.py:1396 #: netbox/dcim/forms/model_forms.py:431 netbox/dcim/forms/model_forms.py:486 #: netbox/dcim/forms/object_create.py:197 -#: netbox/dcim/forms/object_create.py:353 netbox/dcim/tables/devices.py:170 -#: netbox/dcim/tables/devices.py:702 netbox/dcim/tables/devicetypes.py:242 +#: netbox/dcim/forms/object_create.py:353 netbox/dcim/tables/devices.py:162 +#: netbox/dcim/tables/devices.py:690 netbox/dcim/tables/devicetypes.py:242 #: netbox/templates/dcim/device.html:43 netbox/templates/dcim/device.html:130 #: netbox/templates/dcim/modulebay.html:34 #: netbox/templates/dcim/virtualchassis.html:66 @@ -3035,8 +3035,8 @@ msgstr "" #: netbox/dcim/forms/filtersets.py:706 netbox/dcim/forms/filtersets.py:1426 #: netbox/dcim/forms/model_forms.py:219 netbox/dcim/forms/model_forms.py:1015 #: netbox/dcim/forms/model_forms.py:1454 netbox/dcim/forms/object_import.py:181 -#: netbox/dcim/tables/devices.py:174 netbox/dcim/tables/devices.py:810 -#: netbox/dcim/tables/devices.py:921 netbox/dcim/tables/devicetypes.py:300 +#: netbox/dcim/tables/devices.py:166 netbox/dcim/tables/devices.py:792 +#: netbox/dcim/tables/devices.py:903 netbox/dcim/tables/devicetypes.py:300 #: netbox/dcim/tables/racks.py:69 netbox/extras/filtersets.py:504 #: netbox/ipam/forms/bulk_edit.py:246 netbox/ipam/forms/bulk_edit.py:295 #: netbox/ipam/forms/bulk_edit.py:343 netbox/ipam/forms/bulk_edit.py:549 @@ -3159,7 +3159,7 @@ msgstr "" #: netbox/dcim/forms/filtersets.py:954 netbox/dcim/forms/filtersets.py:1086 #: netbox/dcim/forms/model_forms.py:226 netbox/dcim/forms/model_forms.py:248 #: netbox/dcim/forms/model_forms.py:422 netbox/dcim/forms/model_forms.py:700 -#: netbox/dcim/forms/object_create.py:400 netbox/dcim/tables/devices.py:166 +#: netbox/dcim/forms/object_create.py:400 netbox/dcim/tables/devices.py:158 #: netbox/dcim/tables/power.py:70 netbox/dcim/tables/racks.py:148 #: netbox/ipam/forms/bulk_edit.py:465 netbox/ipam/forms/filtersets.py:442 #: netbox/ipam/forms/model_forms.py:610 netbox/templates/dcim/device.html:30 @@ -3193,8 +3193,8 @@ msgstr "" #: netbox/dcim/forms/model_forms.py:281 netbox/dcim/forms/model_forms.py:293 #: netbox/dcim/forms/model_forms.py:339 netbox/dcim/forms/model_forms.py:379 #: netbox/dcim/forms/model_forms.py:1020 netbox/dcim/forms/model_forms.py:1459 -#: netbox/dcim/forms/object_import.py:187 netbox/dcim/tables/devices.py:101 -#: netbox/dcim/tables/devices.py:177 netbox/dcim/tables/devices.py:924 +#: netbox/dcim/forms/object_import.py:187 netbox/dcim/tables/devices.py:93 +#: netbox/dcim/tables/devices.py:169 netbox/dcim/tables/devices.py:906 #: netbox/dcim/tables/devicetypes.py:81 netbox/dcim/tables/devicetypes.py:304 #: netbox/dcim/tables/modules.py:20 netbox/dcim/tables/modules.py:60 #: netbox/templates/dcim/devicetype.html:14 @@ -3277,7 +3277,7 @@ msgstr "" #: netbox/dcim/forms/bulk_edit.py:593 netbox/dcim/forms/bulk_import.py:443 #: netbox/dcim/forms/filtersets.py:725 netbox/dcim/forms/model_forms.py:394 -#: netbox/dcim/forms/model_forms.py:456 netbox/dcim/tables/devices.py:187 +#: netbox/dcim/forms/model_forms.py:456 netbox/dcim/tables/devices.py:179 #: netbox/extras/filtersets.py:515 netbox/templates/dcim/device.html:184 #: netbox/templates/dcim/platform.html:26 #: netbox/templates/virtualization/virtualmachine.html:27 @@ -3309,13 +3309,13 @@ msgstr "" #: netbox/dcim/forms/model_forms.py:794 netbox/dcim/forms/model_forms.py:1153 #: netbox/dcim/forms/model_forms.py:1608 netbox/dcim/forms/object_create.py:257 #: netbox/dcim/tables/connections.py:22 netbox/dcim/tables/connections.py:41 -#: netbox/dcim/tables/connections.py:60 netbox/dcim/tables/devices.py:290 -#: netbox/dcim/tables/devices.py:359 netbox/dcim/tables/devices.py:403 -#: netbox/dcim/tables/devices.py:448 netbox/dcim/tables/devices.py:502 -#: netbox/dcim/tables/devices.py:594 netbox/dcim/tables/devices.py:692 -#: netbox/dcim/tables/devices.py:752 netbox/dcim/tables/devices.py:802 -#: netbox/dcim/tables/devices.py:862 netbox/dcim/tables/devices.py:914 -#: netbox/dcim/tables/devices.py:1040 netbox/dcim/tables/modules.py:52 +#: netbox/dcim/tables/connections.py:60 netbox/dcim/tables/devices.py:282 +#: netbox/dcim/tables/devices.py:359 netbox/dcim/tables/devices.py:400 +#: netbox/dcim/tables/devices.py:442 netbox/dcim/tables/devices.py:493 +#: netbox/dcim/tables/devices.py:582 netbox/dcim/tables/devices.py:680 +#: netbox/dcim/tables/devices.py:737 netbox/dcim/tables/devices.py:784 +#: netbox/dcim/tables/devices.py:844 netbox/dcim/tables/devices.py:896 +#: netbox/dcim/tables/devices.py:1022 netbox/dcim/tables/modules.py:52 #: netbox/extras/forms/filtersets.py:330 netbox/ipam/forms/bulk_import.py:303 #: netbox/ipam/forms/bulk_import.py:489 netbox/ipam/forms/filtersets.py:558 #: netbox/ipam/forms/model_forms.py:317 netbox/ipam/forms/model_forms.py:725 @@ -3489,7 +3489,7 @@ msgid "Wireless role" msgstr "" #: netbox/dcim/forms/bulk_edit.py:1186 netbox/dcim/forms/model_forms.py:609 -#: netbox/dcim/forms/model_forms.py:1168 netbox/dcim/tables/devices.py:313 +#: netbox/dcim/forms/model_forms.py:1168 netbox/dcim/tables/devices.py:305 #: netbox/templates/dcim/consoleport.html:24 #: netbox/templates/dcim/consoleserverport.html:24 #: netbox/templates/dcim/frontport.html:24 @@ -3501,7 +3501,7 @@ msgstr "" msgid "Module" msgstr "" -#: netbox/dcim/forms/bulk_edit.py:1313 netbox/dcim/tables/devices.py:661 +#: netbox/dcim/forms/bulk_edit.py:1313 netbox/dcim/tables/devices.py:649 #: netbox/templates/dcim/interface.html:110 msgid "LAG" msgstr "" @@ -3513,7 +3513,7 @@ msgstr "" #: netbox/dcim/forms/bulk_edit.py:1324 netbox/dcim/forms/bulk_import.py:659 #: netbox/dcim/forms/bulk_import.py:685 netbox/dcim/forms/filtersets.py:1169 #: netbox/dcim/forms/filtersets.py:1191 netbox/dcim/forms/filtersets.py:1264 -#: netbox/dcim/tables/devices.py:606 +#: netbox/dcim/tables/devices.py:594 #: netbox/templates/circuits/inc/circuit_termination_fields.html:67 #: netbox/templates/dcim/consoleport.html:40 #: netbox/templates/dcim/consoleserverport.html:40 @@ -3542,14 +3542,14 @@ msgid "VLAN group" msgstr "" #: netbox/dcim/forms/bulk_edit.py:1369 netbox/dcim/forms/model_forms.py:1304 -#: netbox/dcim/tables/devices.py:579 +#: netbox/dcim/tables/devices.py:567 #: netbox/virtualization/forms/bulk_edit.py:248 #: netbox/virtualization/forms/model_forms.py:326 msgid "Untagged VLAN" msgstr "" #: netbox/dcim/forms/bulk_edit.py:1377 netbox/dcim/forms/model_forms.py:1313 -#: netbox/dcim/tables/devices.py:585 +#: netbox/dcim/tables/devices.py:573 #: netbox/virtualization/forms/bulk_edit.py:256 #: netbox/virtualization/forms/model_forms.py:335 msgid "Tagged VLANs" @@ -3560,7 +3560,7 @@ msgid "Wireless LAN group" msgstr "" #: netbox/dcim/forms/bulk_edit.py:1392 netbox/dcim/forms/model_forms.py:1291 -#: netbox/dcim/tables/devices.py:615 netbox/netbox/navigation/menu.py:133 +#: netbox/dcim/tables/devices.py:603 netbox/netbox/navigation/menu.py:133 #: netbox/templates/dcim/interface.html:280 #: netbox/wireless/tables/wirelesslan.py:24 msgid "Wireless LANs" @@ -3740,7 +3740,7 @@ msgid "Virtual chassis" msgstr "" #: netbox/dcim/forms/bulk_import.py:462 netbox/dcim/forms/model_forms.py:465 -#: netbox/dcim/tables/devices.py:207 netbox/extras/filtersets.py:548 +#: netbox/dcim/tables/devices.py:199 netbox/extras/filtersets.py:548 #: netbox/extras/forms/filtersets.py:331 netbox/ipam/forms/bulk_edit.py:479 #: netbox/ipam/forms/filtersets.py:415 netbox/ipam/forms/filtersets.py:459 #: netbox/ipam/forms/model_forms.py:627 netbox/templates/dcim/device.html:232 @@ -3935,7 +3935,7 @@ msgstr "" msgid "Physical medium classification" msgstr "" -#: netbox/dcim/forms/bulk_import.py:973 netbox/dcim/tables/devices.py:823 +#: netbox/dcim/forms/bulk_import.py:973 netbox/dcim/tables/devices.py:805 msgid "Installed device" msgstr "" @@ -4024,7 +4024,7 @@ msgid "{side_upper} side termination not found: {device} {name}" msgstr "" #: netbox/dcim/forms/bulk_import.py:1244 netbox/dcim/forms/model_forms.py:730 -#: netbox/dcim/tables/devices.py:1010 netbox/templates/dcim/device.html:131 +#: netbox/dcim/tables/devices.py:992 netbox/templates/dcim/device.html:131 #: netbox/templates/dcim/virtualchassis.html:27 #: netbox/templates/dcim/virtualchassis.html:67 msgid "Master" @@ -4205,7 +4205,7 @@ msgid "Transmit power (dBm)" msgstr "" #: netbox/dcim/forms/filtersets.py:1350 netbox/dcim/forms/filtersets.py:1372 -#: netbox/dcim/tables/devices.py:324 netbox/templates/dcim/cable.html:12 +#: netbox/dcim/tables/devices.py:316 netbox/templates/dcim/cable.html:12 #: netbox/templates/dcim/cable_trace.html:46 #: netbox/templates/dcim/frontport.html:77 #: netbox/templates/dcim/htmx/cable_edit.html:50 @@ -4215,7 +4215,7 @@ msgstr "" msgid "Cable" msgstr "" -#: netbox/dcim/forms/filtersets.py:1442 netbox/dcim/tables/devices.py:933 +#: netbox/dcim/forms/filtersets.py:1442 netbox/dcim/tables/devices.py:915 msgid "Discovered" msgstr "" @@ -4381,7 +4381,7 @@ msgid "Front Port" msgstr "" #: netbox/dcim/forms/model_forms.py:1093 netbox/dcim/forms/model_forms.py:1531 -#: netbox/dcim/tables/devices.py:705 +#: netbox/dcim/tables/devices.py:693 #: netbox/templates/circuits/inc/circuit_termination_fields.html:53 #: netbox/templates/dcim/consoleport.html:79 #: netbox/templates/dcim/consoleserverport.html:80 @@ -4394,7 +4394,7 @@ msgid "Rear Port" msgstr "" #: netbox/dcim/forms/model_forms.py:1094 netbox/dcim/forms/model_forms.py:1532 -#: netbox/dcim/tables/connections.py:46 netbox/dcim/tables/devices.py:509 +#: netbox/dcim/tables/connections.py:46 netbox/dcim/tables/devices.py:500 #: netbox/templates/dcim/poweroutlet.html:44 #: netbox/templates/dcim/powerport.html:17 msgid "Power Port" @@ -4481,7 +4481,7 @@ msgid "" msgstr "" #: netbox/dcim/forms/object_create.py:110 -#: netbox/dcim/forms/object_create.py:271 netbox/dcim/tables/devices.py:257 +#: netbox/dcim/forms/object_create.py:271 netbox/dcim/tables/devices.py:249 msgid "Rear ports" msgstr "" @@ -4511,7 +4511,7 @@ msgid "" "selected number of rear port positions ({rearport_count})." msgstr "" -#: netbox/dcim/forms/object_create.py:409 netbox/dcim/tables/devices.py:1016 +#: netbox/dcim/forms/object_create.py:409 netbox/dcim/tables/devices.py:998 #: netbox/ipam/tables/fhrp.py:31 netbox/templates/dcim/virtualchassis.html:53 #: netbox/templates/dcim/virtualchassis_edit.html:47 #: netbox/templates/ipam/fhrpgroup.html:38 @@ -5968,7 +5968,7 @@ msgstr "" msgid "Reachable" msgstr "" -#: netbox/dcim/tables/devices.py:66 netbox/dcim/tables/devices.py:111 +#: netbox/dcim/tables/devices.py:58 netbox/dcim/tables/devices.py:103 #: netbox/dcim/tables/racks.py:81 netbox/dcim/tables/sites.py:143 #: netbox/extras/tables/tables.py:435 netbox/netbox/navigation/menu.py:56 #: netbox/netbox/navigation/menu.py:60 netbox/netbox/navigation/menu.py:62 @@ -5978,12 +5978,12 @@ msgstr "" msgid "Devices" msgstr "" -#: netbox/dcim/tables/devices.py:71 netbox/dcim/tables/devices.py:116 +#: netbox/dcim/tables/devices.py:63 netbox/dcim/tables/devices.py:108 #: netbox/virtualization/tables/clusters.py:88 msgid "VMs" msgstr "" -#: netbox/dcim/tables/devices.py:105 netbox/dcim/tables/devices.py:221 +#: netbox/dcim/tables/devices.py:97 netbox/dcim/tables/devices.py:213 #: netbox/extras/forms/model_forms.py:506 netbox/templates/dcim/device.html:112 #: netbox/templates/dcim/device/render_config.html:11 #: netbox/templates/dcim/device/render_config.html:14 @@ -5997,11 +5997,11 @@ msgstr "" msgid "Config Template" msgstr "" -#: netbox/dcim/tables/devices.py:155 netbox/templates/dcim/sitegroup.html:26 +#: netbox/dcim/tables/devices.py:147 netbox/templates/dcim/sitegroup.html:26 msgid "Site Group" msgstr "" -#: netbox/dcim/tables/devices.py:192 netbox/dcim/tables/devices.py:1051 +#: netbox/dcim/tables/devices.py:184 netbox/dcim/tables/devices.py:1033 #: netbox/ipam/forms/bulk_import.py:511 netbox/ipam/forms/model_forms.py:304 #: netbox/ipam/forms/model_forms.py:313 netbox/ipam/tables/ip.py:352 #: netbox/ipam/tables/ip.py:418 netbox/ipam/tables/ip.py:441 @@ -6010,50 +6010,50 @@ msgstr "" msgid "IP Address" msgstr "" -#: netbox/dcim/tables/devices.py:196 netbox/dcim/tables/devices.py:1055 +#: netbox/dcim/tables/devices.py:188 netbox/dcim/tables/devices.py:1037 #: netbox/virtualization/tables/virtualmachines.py:85 msgid "IPv4 Address" msgstr "" -#: netbox/dcim/tables/devices.py:200 netbox/dcim/tables/devices.py:1059 +#: netbox/dcim/tables/devices.py:192 netbox/dcim/tables/devices.py:1041 #: netbox/virtualization/tables/virtualmachines.py:89 msgid "IPv6 Address" msgstr "" -#: netbox/dcim/tables/devices.py:215 +#: netbox/dcim/tables/devices.py:207 msgid "VC Position" msgstr "" -#: netbox/dcim/tables/devices.py:218 +#: netbox/dcim/tables/devices.py:210 msgid "VC Priority" msgstr "" -#: netbox/dcim/tables/devices.py:225 netbox/templates/dcim/device_edit.html:38 +#: netbox/dcim/tables/devices.py:217 netbox/templates/dcim/device_edit.html:38 #: netbox/templates/dcim/devicebay_populate.html:16 msgid "Parent Device" msgstr "" -#: netbox/dcim/tables/devices.py:230 +#: netbox/dcim/tables/devices.py:222 msgid "Position (Device Bay)" msgstr "" -#: netbox/dcim/tables/devices.py:239 +#: netbox/dcim/tables/devices.py:231 msgid "Console ports" msgstr "" -#: netbox/dcim/tables/devices.py:242 +#: netbox/dcim/tables/devices.py:234 msgid "Console server ports" msgstr "" -#: netbox/dcim/tables/devices.py:245 +#: netbox/dcim/tables/devices.py:237 msgid "Power ports" msgstr "" -#: netbox/dcim/tables/devices.py:248 +#: netbox/dcim/tables/devices.py:240 msgid "Power outlets" msgstr "" -#: netbox/dcim/tables/devices.py:251 netbox/dcim/tables/devices.py:1064 +#: netbox/dcim/tables/devices.py:243 netbox/dcim/tables/devices.py:1046 #: netbox/dcim/tables/devicetypes.py:125 netbox/dcim/views.py:1006 #: netbox/dcim/views.py:1245 netbox/dcim/views.py:1931 #: netbox/netbox/navigation/menu.py:81 netbox/netbox/navigation/menu.py:237 @@ -6071,28 +6071,28 @@ msgstr "" msgid "Interfaces" msgstr "" -#: netbox/dcim/tables/devices.py:254 +#: netbox/dcim/tables/devices.py:246 msgid "Front ports" msgstr "" -#: netbox/dcim/tables/devices.py:260 +#: netbox/dcim/tables/devices.py:252 msgid "Device bays" msgstr "" -#: netbox/dcim/tables/devices.py:263 +#: netbox/dcim/tables/devices.py:255 msgid "Module bays" msgstr "" -#: netbox/dcim/tables/devices.py:266 +#: netbox/dcim/tables/devices.py:258 msgid "Inventory items" msgstr "" -#: netbox/dcim/tables/devices.py:305 netbox/dcim/tables/modules.py:56 +#: netbox/dcim/tables/devices.py:297 netbox/dcim/tables/modules.py:56 #: netbox/templates/dcim/modulebay.html:17 msgid "Module Bay" msgstr "" -#: netbox/dcim/tables/devices.py:318 netbox/dcim/tables/devicetypes.py:48 +#: netbox/dcim/tables/devices.py:310 netbox/dcim/tables/devicetypes.py:48 #: netbox/dcim/tables/devicetypes.py:140 netbox/dcim/views.py:1081 #: netbox/dcim/views.py:2024 netbox/netbox/navigation/menu.py:90 #: netbox/templates/dcim/device/base.html:52 @@ -6103,27 +6103,27 @@ msgstr "" msgid "Inventory Items" msgstr "" -#: netbox/dcim/tables/devices.py:330 +#: netbox/dcim/tables/devices.py:322 msgid "Cable Color" msgstr "" -#: netbox/dcim/tables/devices.py:336 +#: netbox/dcim/tables/devices.py:328 msgid "Link Peers" msgstr "" -#: netbox/dcim/tables/devices.py:339 +#: netbox/dcim/tables/devices.py:331 msgid "Mark Connected" msgstr "" -#: netbox/dcim/tables/devices.py:455 +#: netbox/dcim/tables/devices.py:449 msgid "Maximum draw (W)" msgstr "" -#: netbox/dcim/tables/devices.py:458 +#: netbox/dcim/tables/devices.py:452 msgid "Allocated draw (W)" msgstr "" -#: netbox/dcim/tables/devices.py:558 netbox/ipam/forms/model_forms.py:747 +#: netbox/dcim/tables/devices.py:546 netbox/ipam/forms/model_forms.py:747 #: netbox/ipam/tables/fhrp.py:28 netbox/ipam/views.py:602 #: netbox/ipam/views.py:701 netbox/netbox/navigation/menu.py:145 #: netbox/netbox/navigation/menu.py:147 @@ -6135,12 +6135,12 @@ msgstr "" msgid "IP Addresses" msgstr "" -#: netbox/dcim/tables/devices.py:564 netbox/netbox/navigation/menu.py:189 +#: netbox/dcim/tables/devices.py:552 netbox/netbox/navigation/menu.py:189 #: netbox/templates/ipam/inc/panels/fhrp_groups.html:6 msgid "FHRP Groups" msgstr "" -#: netbox/dcim/tables/devices.py:576 netbox/templates/dcim/interface.html:89 +#: netbox/dcim/tables/devices.py:564 netbox/templates/dcim/interface.html:89 #: netbox/templates/virtualization/vminterface.html:67 #: netbox/templates/vpn/tunnel.html:18 #: netbox/templates/vpn/tunneltermination.html:13 @@ -6151,37 +6151,37 @@ msgstr "" msgid "Tunnel" msgstr "" -#: netbox/dcim/tables/devices.py:601 netbox/dcim/tables/devicetypes.py:224 +#: netbox/dcim/tables/devices.py:589 netbox/dcim/tables/devicetypes.py:224 #: netbox/templates/dcim/interface.html:65 msgid "Management Only" msgstr "" -#: netbox/dcim/tables/devices.py:619 +#: netbox/dcim/tables/devices.py:607 msgid "VDCs" msgstr "" -#: netbox/dcim/tables/devices.py:870 netbox/templates/dcim/modulebay.html:49 +#: netbox/dcim/tables/devices.py:852 netbox/templates/dcim/modulebay.html:49 msgid "Installed Module" msgstr "" -#: netbox/dcim/tables/devices.py:873 +#: netbox/dcim/tables/devices.py:855 msgid "Module Serial" msgstr "" -#: netbox/dcim/tables/devices.py:877 +#: netbox/dcim/tables/devices.py:859 msgid "Module Asset Tag" msgstr "" -#: netbox/dcim/tables/devices.py:886 +#: netbox/dcim/tables/devices.py:868 msgid "Module Status" msgstr "" -#: netbox/dcim/tables/devices.py:928 netbox/dcim/tables/devicetypes.py:308 +#: netbox/dcim/tables/devices.py:910 netbox/dcim/tables/devicetypes.py:308 #: netbox/templates/dcim/inventoryitem.html:40 msgid "Component" msgstr "" -#: netbox/dcim/tables/devices.py:983 +#: netbox/dcim/tables/devices.py:965 msgid "Items" msgstr "" @@ -6770,12 +6770,12 @@ msgstr "" msgid "Show your personal bookmarks" msgstr "" -#: netbox/extras/events.py:128 +#: netbox/extras/events.py:134 #, python-brace-format msgid "Unknown action type for an event rule: {action_type}" msgstr "" -#: netbox/extras/events.py:176 +#: netbox/extras/events.py:182 #, python-brace-format msgid "Cannot import events pipeline {name} error: {error}" msgstr "" @@ -8156,7 +8156,7 @@ msgstr "" msgid "Database changes have been reverted due to error." msgstr "" -#: netbox/extras/signals.py:146 +#: netbox/extras/signals.py:133 #, python-brace-format msgid "Deletion is prevented by a protection rule: {message}" msgstr "" @@ -8937,7 +8937,7 @@ msgstr "" #: netbox/virtualization/forms/filtersets.py:238 #: netbox/virtualization/forms/model_forms.py:220 #: netbox/virtualization/tables/virtualmachines.py:128 -#: netbox/virtualization/tables/virtualmachines.py:181 netbox/vpn/choices.py:45 +#: netbox/virtualization/tables/virtualmachines.py:183 netbox/vpn/choices.py:45 #: netbox/vpn/forms/filtersets.py:293 netbox/vpn/forms/model_forms.py:160 #: netbox/vpn/forms/model_forms.py:171 netbox/vpn/forms/model_forms.py:273 #: netbox/vpn/forms/model_forms.py:454 From 7be003f5a0385ef0fa489c8ad0d7dc0d4669db28 Mon Sep 17 00:00:00 2001 From: "Jamie (Bear) Murphy" <1613241+ITJamie@users.noreply.github.com> Date: Tue, 4 Jun 2024 13:49:08 +0100 Subject: [PATCH 10/13] Allow plugins to extend objectchange view (#16371) * allow plugins to extend objectchangeview with panels * replace tabs with spaces * Update netbox/templates/extras/objectchange.html Co-authored-by: Jeremy Stretch * Eliminate excessive vertical margin --------- Co-authored-by: Jeremy Stretch --- netbox/templates/extras/objectchange.html | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/netbox/templates/extras/objectchange.html b/netbox/templates/extras/objectchange.html index 368a71821..ffd6e77fa 100644 --- a/netbox/templates/extras/objectchange.html +++ b/netbox/templates/extras/objectchange.html @@ -1,5 +1,6 @@ {% extends 'generic/object.html' %} {% load helpers %} +{% load plugins %} {% load i18n %} {% block title %}{{ object }}{% endblock %} @@ -22,7 +23,7 @@ {% block subtitle %}{% endblock %} {% block content %} -
+
{% trans "Change" %}
@@ -104,7 +105,7 @@
-
+
{% trans "Pre-Change Data" %}
@@ -144,7 +145,15 @@
-
+
+
+ {% plugin_left_page object %} +
+
+ {% plugin_right_page object %} +
+
+
{% include 'inc/panel_table.html' with table=related_changes_table heading='Related Changes' panel_class='default' %} {% if related_changes_count > related_changes_table.rows|length %} @@ -158,4 +167,9 @@ {% endif %}
+
+
+ {% plugin_full_width_page object %} +
+
{% endblock %} From 8ab9afb8db5628469ba70424d46bf44b829cf36c Mon Sep 17 00:00:00 2001 From: Daniel Sheppard Date: Tue, 4 Jun 2024 08:02:38 -0500 Subject: [PATCH 11/13] =?UTF-8?q?Fixes:=20#16083=20-=20Add=20font-variant-?= =?UTF-8?q?ligatures=20setting=20to=20disable=20ligatur=E2=80=A6=20(#16383?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fixes: #16083 - Add font-variant-ligatures setting to disable ligatures on chromium * Fix comment * Disable ligatures on input fields * Condense rules & apply to all elements --------- Co-authored-by: Jeremy Stretch --- netbox/project-static/dist/netbox.css | Bin 551631 -> 551692 bytes .../styles/overrides/_tabler.scss | 7 +++++++ 2 files changed, 7 insertions(+) diff --git a/netbox/project-static/dist/netbox.css b/netbox/project-static/dist/netbox.css index 082a5b64d2374a533a0cd977972c5efb72012a24..0ec53a6d27390befa166d8e074df6917b0c345ef 100644 GIT binary patch delta 80 zcmX?qRk7!qVnYjK3sVbo3rh=Y3tJ2O7LMi!Cavn}2QIM4%cbS#mFSiw7G)*^>72~; j#FEmY)MBf={JhlKf}-h#dzgi%7ffX3*}f!#Lt6*{#ycIs delta 31 mcmeCVrg;9UVnYjK3sVbo3rh=Y3tJ2O7LMkK?FS+_e1rhSaSKfV diff --git a/netbox/project-static/styles/overrides/_tabler.scss b/netbox/project-static/styles/overrides/_tabler.scss index b31fdac6b..5ea63be76 100644 --- a/netbox/project-static/styles/overrides/_tabler.scss +++ b/netbox/project-static/styles/overrides/_tabler.scss @@ -1,3 +1,10 @@ +// Disable font-ligatures for Chromium based browsers +// Chromium requires `font-variant-ligatures: none` in addition to `font-feature-settings "liga" 0` +* { + font-feature-settings: "liga" 0; + font-variant-ligatures: none; +} + // Restore default foreground & background colors for
 blocks
 pre {
   background-color: transparent;

From 87109f5539758a3d62ea8200d89641b21bb2d283 Mon Sep 17 00:00:00 2001
From: Julio Oliveira at Encora
 <149191228+Julio-Oliveira-Encora@users.noreply.github.com>
Date: Tue, 4 Jun 2024 10:37:08 -0300
Subject: [PATCH 12/13] 16315 - Cant filter changelog by object type (no
 results found) (#16324)

* Replaced "api=/api/extras/content-types/" with "/api/extras/object-types/" for JournalEntryFilterForm and ObjectChangeFilterForm.

* Addressed PR comment.

* Correct feature classifications

---------

Co-authored-by: Jeremy Stretch 
---
 netbox/extras/forms/filtersets.py | 14 ++++----------
 1 file changed, 4 insertions(+), 10 deletions(-)

diff --git a/netbox/extras/forms/filtersets.py b/netbox/extras/forms/filtersets.py
index d4235c465..e6b001f2c 100644
--- a/netbox/extras/forms/filtersets.py
+++ b/netbox/extras/forms/filtersets.py
@@ -464,13 +464,10 @@ class JournalEntryFilterForm(NetBoxModelFilterSetForm):
         required=False,
         label=_('User')
     )
-    assigned_object_type_id = DynamicModelMultipleChoiceField(
-        queryset=ObjectType.objects.all(),
+    assigned_object_type_id = ContentTypeMultipleChoiceField(
+        queryset=ObjectType.objects.with_feature('journaling'),
         required=False,
         label=_('Object Type'),
-        widget=APISelectMultiple(
-            api_url='/api/extras/content-types/',
-        )
     )
     kind = forms.ChoiceField(
         label=_('Kind'),
@@ -507,11 +504,8 @@ class ObjectChangeFilterForm(SavedFiltersMixin, FilterForm):
         required=False,
         label=_('User')
     )
-    changed_object_type_id = DynamicModelMultipleChoiceField(
-        queryset=ObjectType.objects.all(),
+    changed_object_type_id = ContentTypeMultipleChoiceField(
+        queryset=ObjectType.objects.with_feature('change_logging'),
         required=False,
         label=_('Object Type'),
-        widget=APISelectMultiple(
-            api_url='/api/extras/content-types/',
-        )
     )

From 4242546270a996e84943e1b3ae05b35f2a31f3ac Mon Sep 17 00:00:00 2001
From: Jeremy Stretch 
Date: Tue, 4 Jun 2024 13:51:40 -0400
Subject: [PATCH 13/13] Fixes #16376: Log changes on terminating objects when
 attaching a cable

---
 netbox/dcim/models/cables.py | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/netbox/dcim/models/cables.py b/netbox/dcim/models/cables.py
index 64f0b8560..7afead829 100644
--- a/netbox/dcim/models/cables.py
+++ b/netbox/dcim/models/cables.py
@@ -355,11 +355,11 @@ class CableTermination(ChangeLoggedModel):
         super().save(*args, **kwargs)
 
         # Set the cable on the terminating object
-        termination_model = self.termination._meta.model
-        termination_model.objects.filter(pk=self.termination_id).update(
-            cable=self.cable,
-            cable_end=self.cable_end
-        )
+        termination = self.termination._meta.model.objects.get(pk=self.termination_id)
+        termination.snapshot()
+        termination.cable = self.cable
+        termination.cable_end = self.cable_end
+        termination.save()
 
     def delete(self, *args, **kwargs):