Skip to content

Commit 2c8b899

Browse files
UN-3375 [FIX] Support multiple nullable FK paths in OrganizationFilterBackend (#1889)
Add org_filter_paths ViewSet attribute for models with multiple nullable FK paths to Organization (e.g. Notification → Pipeline or API → Workflow → Organization). When set, the filter uses OR across all paths instead of BFS which only finds one path and misses records linked via the other. Apply org_filter_paths to NotificationViewSet to fix 404 on notification PUT/DELETE operations. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 65b6b64 commit 2c8b899

2 files changed

Lines changed: 30 additions & 1 deletion

File tree

backend/notification_v2/views.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313

1414
class NotificationViewSet(viewsets.ModelViewSet):
1515
serializer_class = NotificationSerializer
16+
org_filter_paths = [
17+
"pipeline__workflow__organization",
18+
"api__workflow__organization",
19+
]
1620

1721
def get_queryset(self):
1822
queryset = Notification.objects.all()

backend/utils/filters/organization_filter.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
import logging
22

3+
from django.db.models import Q
34
from rest_framework.filters import BaseFilterBackend
45

56
from utils.models.org_path_discovery import get_org_path
67
from utils.user_context import UserContext
78

89
logger = logging.getLogger(__name__)
910

11+
# ViewSet attributes read by OrganizationFilterBackend.
12+
# Use these constants when setting attributes on ViewSets to avoid typos.
13+
SKIP_ORG_FILTER = "skip_org_filter"
14+
ORG_FILTER_PATHS = "org_filter_paths"
15+
1016

1117
class OrganizationFilterBackend(BaseFilterBackend):
1218
"""Global filter backend that enforces organization scoping.
@@ -24,10 +30,20 @@ class OrganizationFilterBackend(BaseFilterBackend):
2430
rather than replacing it.
2531
2632
Opt-out: set skip_org_filter = True on the viewset.
33+
34+
For models with multiple nullable FK paths to Organization, set
35+
org_filter_paths on the viewset to use OR filtering across all
36+
paths (skips BFS):
37+
38+
class NotificationViewSet(viewsets.ModelViewSet):
39+
org_filter_paths = [
40+
"pipeline__workflow__organization",
41+
"api__workflow__organization",
42+
]
2743
"""
2844

2945
def filter_queryset(self, request, queryset, view):
30-
if getattr(view, "skip_org_filter", False):
46+
if getattr(view, SKIP_ORG_FILTER, False):
3147
return queryset
3248

3349
model = queryset.model
@@ -43,6 +59,15 @@ def filter_queryset(self, request, queryset, view):
4359
)
4460
return queryset.none()
4561

62+
# Use explicit paths if set on the viewset (for models with
63+
# multiple nullable FK paths to Organization).
64+
explicit_paths = getattr(view, ORG_FILTER_PATHS, None)
65+
if explicit_paths:
66+
q = Q()
67+
for path in explicit_paths:
68+
q |= Q(**{path: org})
69+
return queryset.filter(q)
70+
4671
path = get_org_path(model)
4772
if path:
4873
return queryset.filter(**{path: org})

0 commit comments

Comments
 (0)