Skip to content

Commit

Permalink
Removed deprecated schema generation methods from DRF backend. (#1698)
Browse files Browse the repository at this point in the history
  • Loading branch information
carltongibson authored Dec 28, 2024
1 parent 2494df9 commit 2a644e1
Show file tree
Hide file tree
Showing 6 changed files with 16 additions and 328 deletions.
9 changes: 9 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
Version 25.1 (Unreleased)
-------------------------

* Removed the in-built API schema generation methods, which have been
deprecated since v23.2.

You should use `drf-spectacular <https://drf-spectacular.readthedocs.io/en/latest/>`_
for generating OpenAPI schemas with DRF.

Version 24.3 (2024-08-02)
-------------------------

Expand Down
10 changes: 1 addition & 9 deletions django_filters/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from . import rest_framework
del importlib_util

__version__ = "24.3"
__version__ = "25.1"


def parse_version(version):
Expand All @@ -29,11 +29,3 @@ def parse_version(version):


VERSION = parse_version(__version__)



assert VERSION < (25,0), "Remove deprecated code"


class RemovedInDjangoFilter25Warning(DeprecationWarning):
pass
14 changes: 0 additions & 14 deletions django_filters/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,3 @@

def is_crispy():
return "crispy_forms" in settings.INSTALLED_APPS and crispy_forms


# coreapi is optional (Note that uritemplate is a dependency of coreapi)
# Fixes #525 - cannot simply import from rest_framework.compat, due to
# import issues w/ django-guardian.
try:
import coreapi
except ImportError:
coreapi = None

try:
import coreschema
except ImportError:
coreschema = None
86 changes: 1 addition & 85 deletions django_filters/rest_framework/backends.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import warnings

from django.template import loader

from .. import compat, utils
from . import filters, filterset
from . import filterset


class DjangoFilterBackend:
Expand Down Expand Up @@ -81,85 +79,3 @@ def to_html(self, request, queryset, view):
template = loader.get_template(self.template)
context = {"filter": filterset}
return template.render(context, request)

def get_coreschema_field(self, field):
if isinstance(field, filters.NumberFilter):
field_cls = compat.coreschema.Number
else:
field_cls = compat.coreschema.String
return field_cls(description=str(field.extra.get("help_text", "")))

def get_schema_fields(self, view):
# This is not compatible with widgets where the query param differs from the
# filter's attribute name. Notably, this includes `MultiWidget`, where query
# params will be of the format `<name>_0`, `<name>_1`, etc...
from django_filters import RemovedInDjangoFilter25Warning
warnings.warn(
"Built-in schema generation is deprecated. Use drf-spectacular.",
category=RemovedInDjangoFilter25Warning,
)
assert (
compat.coreapi is not None
), "coreapi must be installed to use `get_schema_fields()`"
assert (
compat.coreschema is not None
), "coreschema must be installed to use `get_schema_fields()`"

try:
queryset = view.get_queryset()
except Exception:
queryset = None
warnings.warn(
"{} is not compatible with schema generation".format(view.__class__)
)

filterset_class = self.get_filterset_class(view, queryset)

return (
[]
if not filterset_class
else [
compat.coreapi.Field(
name=field_name,
required=field.extra["required"],
location="query",
schema=self.get_coreschema_field(field),
)
for field_name, field in filterset_class.base_filters.items()
]
)

def get_schema_operation_parameters(self, view):
from django_filters import RemovedInDjangoFilter25Warning
warnings.warn(
"Built-in schema generation is deprecated. Use drf-spectacular.",
category=RemovedInDjangoFilter25Warning,
)
try:
queryset = view.get_queryset()
except Exception:
queryset = None
warnings.warn(
"{} is not compatible with schema generation".format(view.__class__)
)

filterset_class = self.get_filterset_class(view, queryset)

if not filterset_class:
return []

parameters = []
for field_name, field in filterset_class.base_filters.items():
parameter = {
"name": field_name,
"required": field.extra["required"],
"in": "query",
"description": field.label if field.label is not None else field_name,
"schema": {
"type": "string",
},
}
if field.extra and "choices" in field.extra:
parameter["schema"]["enum"] = [c[0] for c in field.extra["choices"]]
parameters.append(parameter)
return parameters
58 changes: 0 additions & 58 deletions docs/guide/rest_framework.txt
Original file line number Diff line number Diff line change
Expand Up @@ -149,64 +149,6 @@ You can override these methods on a case-by-case basis for each view, creating u
'author': self.get_author(),
}

Schema Generation with Core API and Open API
--------------------------------------------

The backend class integrates with DRF's schema generation by implementing ``get_schema_fields()`` and ``get_schema_operation_parameters()``. ``get_schema_fields()`` is automatically enabled when Core API is installed. ``get_schema_operation_parameters()`` is always enabled for Open API (new since DRF 3.9). Schema generation usually functions seamlessly, however the implementation does expect to invoke the view's ``get_queryset()`` method. There is a caveat in that views are artificially constructed during schema generation, so the ``args`` and ``kwargs`` attributes will be empty. If you depend on arguments parsed from the URL, you will need to handle their absence in ``get_queryset()``.

For example, your get queryset method may look like this:

.. code-block:: python

class IssueViewSet(views.ModelViewSet):
queryset = models.Issue.objects.all()

def get_project(self):
return models.Project.objects.get(pk=self.kwargs['project_id'])

def get_queryset(self):
project = self.get_project()

return self.queryset \
.filter(project=project) \
.filter(author=self.request.user)

This could be rewritten like so:

.. code-block:: python

class IssueViewSet(views.ModelViewSet):
queryset = models.Issue.objects.all()

def get_project(self):
try:
return models.Project.objects.get(pk=self.kwargs['project_id'])
except models.Project.DoesNotExist:
return None

def get_queryset(self):
project = self.get_project()

if project is None:
return self.queryset.none()

return self.queryset \
.filter(project=project) \
.filter(author=self.request.user)

Or more simply as:

.. code-block:: python

class IssueViewSet(views.ModelViewSet):
queryset = models.Issue.objects.all()

def get_queryset(self):
# project_id may be None
return self.queryset \
.filter(project_id=self.kwargs.get('project_id')) \
.filter(author=self.request.user)


Crispy Forms
------------
Expand Down
Loading

0 comments on commit 2a644e1

Please sign in to comment.