From 90257e9dee40e0317bcbd8cab81d99e6ab57c140 Mon Sep 17 00:00:00 2001 From: seros1521 <55374617+seros1521@users.noreply.github.com> Date: Fri, 4 Mar 2022 13:15:24 +0700 Subject: [PATCH] Fixes #8715: eliminates duplicates when used in many-to-many field constraints When using permissions that use tags, a user may receive multiple permissions of the same type if multiple tags are assigned to the device. This causes the RestrictedQuerySet class to generate a query similar to this: >>> dcim.models.Device.objects.filter(Q(tags__name='tag1')|Q(tags__name='tag2')) , ]> This query returns the same object twice if both tags are assigned to it. This is due to the use of the django-taggit library. The library's documentation describes this behavior as expected and suggests using an explicit distinct() call in queries to avoid duplicates. However, the use of DISTINCT in queries has a global side effect - deduplication of responses, which may or may not be acceptable behavior (depending on further use). Since it is not known how RestrictedQuerySet will be used in the rest of the code, it was decided to dedupe using a subquery. --- netbox/utilities/querysets.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/netbox/utilities/querysets.py b/netbox/utilities/querysets.py index 738b72dc31e..97d2e877911 100644 --- a/netbox/utilities/querysets.py +++ b/netbox/utilities/querysets.py @@ -39,6 +39,12 @@ def restrict(self, user, action='view'): # Any permission with null constraints grants access to _all_ instances attrs = Q() break + else: + # for else, when no break + # avoid duplicates when JOIN on many-to-many fields without using DISTINCT. + # DISTINCT acts globally on the entire request, which may not be desirable. + allowed_objects = self.model.objects.filter(attrs) + attrs = Q(pk__in=allowed_objects) qs = self.filter(attrs) return qs