Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix DISTINCT for Oracle databases #2935

Closed
wants to merge 6 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions rest_framework/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from django.core.exceptions import ImproperlyConfigured
from django.db import models
from django.utils import six
from django.conf import settings
from rest_framework.compat import django_filters, guardian, get_model_name
from rest_framework.settings import api_settings
from functools import reduce
Expand Down Expand Up @@ -57,6 +58,7 @@ class AutoFilterSet(self.default_filter_set):
class Meta:
model = queryset.model
fields = filter_fields

return AutoFilterSet

return None
Expand Down Expand Up @@ -97,14 +99,23 @@ def filter_queryset(self, request, queryset, view):

if not search_fields:
return queryset

if settings.DATABASES[queryset.db]["ENGINE"] == "django.db.backends.oracle":
# Remember queryset for Oracle db users
original_queryset = queryset
orm_lookups = [self.construct_search(six.text_type(search_field))
for search_field in search_fields]

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer it if we didn't introduce the and_queries. We should keep each pull request as absolutely minimal as possible. Seems like there's two different changes here - the Oracle support as one, and the change in the filtering style as another. Let's just adopt the first of those two.
Apologies for the slow progress on this, but thanks for getting it nearly there! :)

for search_term in self.get_search_terms(request):
or_queries = [models.Q(**{orm_lookup: search_term})
for orm_lookup in orm_lookups]
queryset = queryset.filter(reduce(operator.or_, or_queries)).distinct()
queryset = queryset.filter(reduce(operator.or_, or_queries))
if settings.DATABASES[queryset.db]["ENGINE"] != "django.db.backends.oracle":
# Oracle db don't support distinct on *LOB fields
queryset = queryset.distinct()

if settings.DATABASES[queryset.db]["ENGINE"] == "django.db.backends.oracle":
# distinct analogue for Oracle users
queryset = original_queryset.filter(pk__in=set(queryset.values_list('pk', flat=True)))

return queryset

Expand Down Expand Up @@ -152,7 +163,7 @@ def remove_invalid_fields(self, queryset, fields, view):
field.source or field_name
for field_name, field in serializer_class().fields.items()
if not getattr(field, 'write_only', False)
]
]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't need to be indented another level.

This is what is causing Flake8 to fail by the way.

elif valid_fields == '__all__':
# View explicitly allows filtering on any model field
valid_fields = [field.name for field in queryset.model._meta.fields]
Expand All @@ -174,6 +185,7 @@ class DjangoObjectPermissionsFilter(BaseFilterBackend):
A filter backend that limits results to those where the requesting user
has read object level permissions.
"""

def __init__(self):
assert guardian, 'Using DjangoObjectPermissionsFilter, but django-guardian is not installed'

Expand Down