From a7f83de8c4d3f17573556ecb72d537af7f6e8d17 Mon Sep 17 00:00:00 2001 From: bctiemann Date: Mon, 26 Aug 2024 16:41:58 -0400 Subject: [PATCH 1/9] Fixes: #16973 - Resolve $user token to User.id for use in permissions based on custom fields (#17268) * Resolve $user token to User.id for use in permissions based on custom fields * Cleaner type check * Simplify User object check by updating tokens instead of resolved values --- netbox/utilities/permissions.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/netbox/utilities/permissions.py b/netbox/utilities/permissions.py index 893cc619e..ba245dae1 100644 --- a/netbox/utilities/permissions.py +++ b/netbox/utilities/permissions.py @@ -1,7 +1,10 @@ from django.conf import settings +from django.apps import apps from django.db.models import Q from django.utils.translation import gettext_lazy as _ +from users.constants import CONSTRAINT_TOKEN_USER + __all__ = ( 'get_permission_for_model', 'permission_is_exempt', @@ -90,6 +93,11 @@ def qs_filter_from_constraints(constraints, tokens=None): if tokens is None: tokens = {} + User = apps.get_model('users.User') + for token, value in tokens.items(): + if token == CONSTRAINT_TOKEN_USER and isinstance(value, User): + tokens[token] = value.id + def _replace_tokens(value, tokens): if type(value) is list: return list(map(lambda v: tokens.get(v, v), value)) From fd013d6c5cd4c45007427df3cedc0b9216bc8eb7 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 27 Aug 2024 05:02:05 +0000 Subject: [PATCH 2/9] Update source translation strings --- netbox/translations/en/LC_MESSAGES/django.po | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/netbox/translations/en/LC_MESSAGES/django.po b/netbox/translations/en/LC_MESSAGES/django.po index 905beb442..dbccc7a78 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-08-23 05:01+0000\n" +"POT-Creation-Date: 2024-08-27 05:01+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -14154,14 +14154,14 @@ msgstr "" msgid "Missing required value for static query param: '{static_params}'" msgstr "" -#: netbox/utilities/permissions.py:39 +#: netbox/utilities/permissions.py:42 #, python-brace-format msgid "" "Invalid permission name: {name}. Must be in the format ." "_" msgstr "" -#: netbox/utilities/permissions.py:57 +#: netbox/utilities/permissions.py:60 #, python-brace-format msgid "Unknown app_label/model_name for {name}" msgstr "" From 420613daedce568fbaedb6064de9a7c66ac6a26e Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 27 Aug 2024 08:55:46 -0400 Subject: [PATCH 3/9] Fixes #16385: Restore support for white, gray, and black background colors --- netbox/project-static/dist/netbox.css | Bin 543569 -> 543613 bytes .../styles/overrides/_bootstrap.scss | 11 +++++++++++ 2 files changed, 11 insertions(+) diff --git a/netbox/project-static/dist/netbox.css b/netbox/project-static/dist/netbox.css index 7d95ac540829f2baf8771e4554549c0621074058..b599521fa8697b259a6bfe97ec037771824cbae5 100644 GIT binary patch delta 121 zcmcaOP4VwE#fBEfElf41_;mD2QY%VylhSq5ixMlRCtP8&ns%2-YWsy#Oae^&2-)(C z%#zgU56&=^BMBzuBqnEr^-O>afLpdKxF diff --git a/netbox/project-static/styles/overrides/_bootstrap.scss b/netbox/project-static/styles/overrides/_bootstrap.scss index 59c248541..3b58767fd 100644 --- a/netbox/project-static/styles/overrides/_bootstrap.scss +++ b/netbox/project-static/styles/overrides/_bootstrap.scss @@ -20,3 +20,14 @@ hr.dropdown-divider { margin-bottom: 0.25rem; margin-top: 0.25rem; } + +// Restore support for old Bootstrap v3 colors +.text-bg-black { + @extend .text-bg-dark; +} +.text-bg-gray { + @extend .text-bg-secondary; +} +.text-bg-white { + @extend .text-bg-light; +} From 8bea914163e3264274d8efee181103b3b773a2b8 Mon Sep 17 00:00:00 2001 From: Jonathan Senecal Date: Fri, 5 Jul 2024 08:48:32 -0400 Subject: [PATCH 4/9] Update related_objects.html --- netbox/templates/inc/panels/related_objects.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/netbox/templates/inc/panels/related_objects.html b/netbox/templates/inc/panels/related_objects.html index 57605f110..321f5f869 100644 --- a/netbox/templates/inc/panels/related_objects.html +++ b/netbox/templates/inc/panels/related_objects.html @@ -5,7 +5,8 @@
{% trans "Related Objects" %}
From 515d041560e709820f301bf2543ce216f82171e2 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 27 Aug 2024 09:30:15 -0400 Subject: [PATCH 5/9] Fixes #17230: Ensure consistent rendering for all dashboard widget colors --- netbox/extras/dashboard/widgets.py | 16 ------ netbox/templates/extras/dashboard/widget.html | 54 ++++++++++--------- 2 files changed, 29 insertions(+), 41 deletions(-) diff --git a/netbox/extras/dashboard/widgets.py b/netbox/extras/dashboard/widgets.py index c5e0f5fc3..df41cd34b 100644 --- a/netbox/extras/dashboard/widgets.py +++ b/netbox/extras/dashboard/widgets.py @@ -131,22 +131,6 @@ class DashboardWidget: def name(self): return f'{self.__class__.__module__.split(".")[0]}.{self.__class__.__name__}' - @property - def fg_color(self): - """ - Return the appropriate foreground (text) color for the widget's color. - """ - if self.color in ( - ButtonColorChoices.CYAN, - ButtonColorChoices.GRAY, - ButtonColorChoices.GREY, - ButtonColorChoices.TEAL, - ButtonColorChoices.WHITE, - ButtonColorChoices.YELLOW, - ): - return ButtonColorChoices.BLACK - return ButtonColorChoices.WHITE - @property def form_data(self): return { diff --git a/netbox/templates/extras/dashboard/widget.html b/netbox/templates/extras/dashboard/widget.html index 39be16145..18be2cc92 100644 --- a/netbox/templates/extras/dashboard/widget.html +++ b/netbox/templates/extras/dashboard/widget.html @@ -9,31 +9,35 @@ gs-id="{{ widget.id }}" >
-
- - - -
- {% if widget.title %} - {{ widget.title }} - {% endif %} + {% with bg_color=widget.color|default:"secondary" %} +
+ + + +
+ {% if widget.title %} + {{ widget.title }} + {% endif %} +
+ + +
- - - -
-
- {% render_widget widget %} -
+
+ {% render_widget widget %} +
+ {% endwith %}
From 3fee28cd5ea0f28fe462fa5aeab77e894a677174 Mon Sep 17 00:00:00 2001 From: Arthur Hanson Date: Tue, 27 Aug 2024 07:59:14 -0700 Subject: [PATCH 6/9] 16946 return empty queryset if filterset is not valid (#17015) * 16946 raise error if filterset is not valid * 16946 cleanup * 16946 change to None qs return and add test * Remove obsolete logic * Clean up test case --------- Co-authored-by: Jeremy Stretch --- netbox/netbox/graphql/filter_mixins.py | 9 +++-- netbox/netbox/tests/test_graphql.py | 50 +++++++++++++++++++++++++- 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/netbox/netbox/graphql/filter_mixins.py b/netbox/netbox/graphql/filter_mixins.py index 5075e9aa2..76cfd8915 100644 --- a/netbox/netbox/graphql/filter_mixins.py +++ b/netbox/netbox/graphql/filter_mixins.py @@ -4,7 +4,7 @@ from typing import List import django_filters import strawberry import strawberry_django -from django.core.exceptions import FieldDoesNotExist +from django.core.exceptions import FieldDoesNotExist, ValidationError from strawberry import auto from ipam.fields import ASNField from netbox.graphql.scalars import BigInt @@ -201,4 +201,9 @@ def autotype_decorator(filterset): class BaseFilterMixin: def filter_by_filterset(self, queryset, key): - return self.filterset(data={key: getattr(self, key)}, queryset=queryset).qs + filterset = self.filterset(data={key: getattr(self, key)}, queryset=queryset) + if not filterset.is_valid(): + # We could raise validation error but strawberry logs it all to the + # console i.e. raise ValidationError(f"{k}: {v[0]}") + return filterset.qs.none() + return filterset.qs diff --git a/netbox/netbox/tests/test_graphql.py b/netbox/netbox/tests/test_graphql.py index 2cf9ee87b..ab80c79c7 100644 --- a/netbox/netbox/tests/test_graphql.py +++ b/netbox/netbox/tests/test_graphql.py @@ -1,7 +1,13 @@ +import json + from django.test import override_settings from django.urls import reverse +from rest_framework import status -from utilities.testing import disable_warnings, TestCase +from core.models import ObjectType +from dcim.models import Site, Location +from users.models import ObjectPermission +from utilities.testing import disable_warnings, APITestCase, TestCase class GraphQLTestCase(TestCase): @@ -34,3 +40,45 @@ class GraphQLTestCase(TestCase): response = self.client.get(url, **header) with disable_warnings('django.request'): self.assertHttpStatus(response, 302) # Redirect to login page + + +class GraphQLAPITestCase(APITestCase): + + @override_settings(LOGIN_REQUIRED=True) + @override_settings(EXEMPT_VIEW_PERMISSIONS=['*', 'auth.user']) + def test_graphql_filter_objects(self): + """ + Test the operation of filters for GraphQL API requests. + """ + sites = ( + Site(name='Site 1', slug='site-1'), + Site(name='Site 2', slug='site-2'), + ) + Site.objects.bulk_create(sites) + Location.objects.create(site=sites[0], name='Location 1', slug='location-1'), + Location.objects.create(site=sites[1], name='Location 2', slug='location-2'), + + # Add object-level permission + obj_perm = ObjectPermission( + name='Test permission', + actions=['view'] + ) + obj_perm.save() + obj_perm.users.add(self.user) + obj_perm.object_types.add(ObjectType.objects.get_for_model(Location)) + + # A valid request should return the filtered list + url = reverse('graphql') + query = '{location_list(filters: {site_id: "' + str(sites[0].pk) + '"}) {id site {id}}}' + response = self.client.post(url, data={'query': query}, format="json", **self.header) + self.assertHttpStatus(response, status.HTTP_200_OK) + data = json.loads(response.content) + self.assertNotIn('errors', data) + self.assertEqual(len(data['data']['location_list']), 1) + + # An invalid request should return an empty list + query = '{location_list(filters: {site_id: "99999"}) {id site {id}}}' # Invalid site ID + response = self.client.post(url, data={'query': query}, format="json", **self.header) + self.assertHttpStatus(response, status.HTTP_200_OK) + data = json.loads(response.content) + self.assertEqual(len(data['data']['location_list']), 0) From 0238aeec22c5fe28b01184e8ed42e26ce32af1e1 Mon Sep 17 00:00:00 2001 From: Arthur Hanson Date: Tue, 27 Aug 2024 09:01:16 -0700 Subject: [PATCH 7/9] 16959 Fix filter reset button (#17154) * 16959 fix filter reset button * 16959 fix filter reset button * Move reset button initialization logic to initFormElements() --------- Co-authored-by: Jeremy Stretch --- netbox/project-static/dist/netbox.js | Bin 391545 -> 391722 bytes netbox/project-static/dist/netbox.js.map | Bin 528502 -> 528655 bytes netbox/project-static/src/forms/elements.ts | 9 ++++++++- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/netbox/project-static/dist/netbox.js b/netbox/project-static/dist/netbox.js index e9e9da59d2fc2bdc701a3e72478e85d3dfe6b7c6..d86103d2a6262febadc30d2a1a7e19c41dde29dd 100644 GIT binary patch delta 192 zcmezQNqp58@rD-07N#xC=Z(1XG8OXE6iPBR^QIfNFiVRRW!lSShWYFg){ zmMEl7H>hKl)Kp3;Eh)**i%v-_Nz^S$Elw@bEl$lzO)iO5(zH&^RJ66tE6vGKQ%eOI zB=1>Wo|%`DU#^#vpPX2dnV+YZSX`W$o~MB>oL`igo|&hOE?kgUl988~o2psc&S=aG N#4Otxjae&>0RZ;{M124N delta 59 zcmZ4WMf~R{@rD-07N#xC=Z&~hGZpgF6iPBRQ>Q1+XO`y8%e1x2tklTM)XB`$Y~N(e O48$zkHyN{590LHeEEX*Q diff --git a/netbox/project-static/dist/netbox.js.map b/netbox/project-static/dist/netbox.js.map index 7c8a8866e235467de0c6bb44c1dec3f1265d7dca..76cd496f342c2a91c4526cfb2ce7fd0d6e7d8681 100644 GIT binary patch delta 259 zcmeyiK%sw;LPHB<3sVd87M64wM(61nGAwTOu8y8XZaSWhj=sSC;P!8*x~ zj+xFnMox~7Mov1ej?P7Hjym3sj_FQ1zK+g0PL3d@kvf?``AjFBK#&fQ2|CV>&W6r9 z{*I3R5M#hB6mBq*EKpydE?AE%kaTng60SZVzMGD-bCwIl3}=wZ22LR13g_t)TUdps zM|ZLEw7-{O0b*7lX50Q=hW+mX?xNJ<)DoxCl9K$q>52Q#ShJxy}} delta 71 zcmeBQr0{KlLPHB<3sVd87M64wM%U>XGAwRl&W^5SZaU76&OpN1(Ai%n*wHaKxZO{d W1&CRJm~FeCEW5_S?XCORyMh20;}xU; diff --git a/netbox/project-static/src/forms/elements.ts b/netbox/project-static/src/forms/elements.ts index 356a8d51e..e047ea738 100644 --- a/netbox/project-static/src/forms/elements.ts +++ b/netbox/project-static/src/forms/elements.ts @@ -39,10 +39,17 @@ export function initFormElements(): void { // Find each of the form's submitters. Most object edit forms have a "Create" and // a "Create & Add", so we need to add a listener to both. const submitters = form.querySelectorAll('button[type=submit]'); - for (const submitter of submitters) { // Add the event listener to each submitter. submitter.addEventListener('click', (event: Event) => handleFormSubmit(event, form)); } + + // Initialize any reset buttons so that when clicked, the page is reloaded without query parameters. + const resetButton = document.querySelector('button[data-reset-select]'); + if (resetButton !== null) { + resetButton.addEventListener('click', () => { + window.location.assign(window.location.origin + window.location.pathname); + }); + } } } From 263664ae52d76c055479ccbe5ae3dddd27188af5 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 27 Aug 2024 12:08:02 -0400 Subject: [PATCH 8/9] Update changelog --- docs/release-notes/version-4.0.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/release-notes/version-4.0.md b/docs/release-notes/version-4.0.md index 941a1239e..c43d7feb0 100644 --- a/docs/release-notes/version-4.0.md +++ b/docs/release-notes/version-4.0.md @@ -11,10 +11,16 @@ ### Bug Fixes +* [#16385](https://github.com/netbox-community/netbox/issues/16385) - Restore support for white, gray, and black background colors * [#16640](https://github.com/netbox-community/netbox/issues/16640) - Fix potential corruption of JSON values in custom fields that are not UI-editable +* [#16825](https://github.com/netbox-community/netbox/issues/16825) - Avoid `NoReverseMatch` exception when displaying count of related object type with no list view +* [#16946](https://github.com/netbox-community/netbox/issues/16946) - GraphQL API requests with an invalid filter should return an empty set +* [#16959](https://github.com/netbox-community/netbox/issues/16959) - Fix function of "reset" button on objects filter form +* [#16973](https://github.com/netbox-community/netbox/issues/16973) - Fix support for evaluating user token (`$user`) against custom field values in permission constraints * [#17070](https://github.com/netbox-community/netbox/issues/17070) - Image height & width values should not be required when creating an image attachment via the REST API * [#17108](https://github.com/netbox-community/netbox/issues/17108) - Ensure template date & time filters always return localtime-aware values * [#17117](https://github.com/netbox-community/netbox/issues/17117) - Work around Safari rendering bug +* [#17230](https://github.com/netbox-community/netbox/issues/17230) - Ensure consistent rendering for all dashboard widget colors --- From 4f225b4e5644d3634d10375fb00a61657b3a319c Mon Sep 17 00:00:00 2001 From: Arthur Hanson Date: Tue, 27 Aug 2024 09:57:14 -0700 Subject: [PATCH 9/9] 16670 fix OpenAPI schema generation with nested serializers (#17078) * 16670 fix OpenAPI schema generation with nested serializers * 16670 fix comment * Add comment; misc cleanup --------- Co-authored-by: Jeremy Stretch --- netbox/core/api/schema.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/netbox/core/api/schema.py b/netbox/core/api/schema.py index bcc49d3fc..7c4ae722e 100644 --- a/netbox/core/api/schema.py +++ b/netbox/core/api/schema.py @@ -126,9 +126,18 @@ class NetBoxAutoSchema(AutoSchema): return response_serializers + def _get_serializer_name(self, serializer, direction, bypass_extensions=False) -> str: + name = super()._get_serializer_name(serializer, direction, bypass_extensions) + + # If this serializer is nested, prepend its name with "Brief" + if getattr(serializer, 'nested', False): + name = f'Brief{name}' + + return name + def get_serializer_ref_name(self, serializer): # from drf-yasg.utils - """Get serializer's ref_name (or None for ModelSerializer if it is named 'NestedSerializer') + """Get serializer's ref_name :param serializer: Serializer instance :return: Serializer's ``ref_name`` or ``None`` for inline serializer :rtype: str or None @@ -137,8 +146,6 @@ class NetBoxAutoSchema(AutoSchema): serializer_name = type(serializer).__name__ if hasattr(serializer_meta, 'ref_name'): ref_name = serializer_meta.ref_name - elif serializer_name == 'NestedSerializer' and isinstance(serializer, serializers.ModelSerializer): - ref_name = None else: ref_name = serializer_name if ref_name.endswith('Serializer'):