drf-query-method 0.1.0__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/.github/workflows/ci.yml +1 -1
  2. drf_query_method-0.2.0/CHANGELOG/0.2.0.md +10 -0
  3. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/PKG-INFO +4 -4
  4. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/README.md +3 -3
  5. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/docs/usage.md +21 -2
  6. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/pyproject.toml +3 -2
  7. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/src/drf_query_method/params.py +9 -1
  8. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/src/drf_query_method/patching.py +26 -3
  9. drf_query_method-0.2.0/tests/test_datatables.py +94 -0
  10. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/uv.lock +17 -1
  11. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/.editorconfig +0 -0
  12. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  13. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  14. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  15. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/.github/dependabot.yml +0 -0
  16. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/.github/pull_request_template.md +0 -0
  17. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/.github/workflows/codeql.yml +0 -0
  18. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/.github/workflows/docs.yml +0 -0
  19. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/.github/workflows/publish.yml +0 -0
  20. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/.github/workflows/zizmor.yml +0 -0
  21. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/.gitignore +0 -0
  22. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/CHANGELOG/0.1.0.md +0 -0
  23. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/CHANGELOG/unreleased.md +0 -0
  24. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/CODE_OF_CONDUCT.md +0 -0
  25. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/CONTRIBUTING.md +0 -0
  26. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/LICENSE +0 -0
  27. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/SECURITY.md +0 -0
  28. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/docs/api.md +0 -0
  29. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/docs/index.md +0 -0
  30. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/docs/installation.md +0 -0
  31. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/justfile +0 -0
  32. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/scripts/release.py +0 -0
  33. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/src/drf_query_method/__init__.py +0 -0
  34. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/src/drf_query_method/django_filters.py +0 -0
  35. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/src/drf_query_method/filters.py +0 -0
  36. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/src/drf_query_method/methods.py +0 -0
  37. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/src/drf_query_method/permissions.py +0 -0
  38. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/src/drf_query_method/py.typed +0 -0
  39. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/src/drf_query_method/routers.py +0 -0
  40. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/src/drf_query_method/test.py +0 -0
  41. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/src/drf_query_method/viewsets.py +0 -0
  42. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/tests/__init__.py +0 -0
  43. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/tests/conftest.py +0 -0
  44. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/tests/settings.py +0 -0
  45. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/tests/test_django_filters.py +0 -0
  46. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/tests/test_drf_query_method.py +0 -0
  47. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/tests/test_filters.py +0 -0
  48. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/tests/test_methods.py +0 -0
  49. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/tests/test_params.py +0 -0
  50. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/tests/test_patching.py +0 -0
  51. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/tests/test_permissions.py +0 -0
  52. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/tests/test_routers.py +0 -0
  53. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/tests/test_test.py +0 -0
  54. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/tests/urls.py +0 -0
  55. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/tests/views.py +0 -0
  56. {drf_query_method-0.1.0 → drf_query_method-0.2.0}/zensical.toml +0 -0
@@ -96,7 +96,7 @@ jobs:
96
96
  drf_req="djangorestframework~=$DRF.0"
97
97
  fi
98
98
  uv venv --python "$PYTHON"
99
- uv pip install -e . "$django_req" "$drf_req" django-filter coverage pytest pytest-django
99
+ uv pip install -e . "$django_req" "$drf_req" django-filter djangorestframework-datatables coverage pytest pytest-django
100
100
 
101
101
  - name: Run tests with coverage
102
102
  run: .venv/bin/coverage run -m pytest
@@ -0,0 +1,10 @@
1
+ # drf-query-method 0.2.0
2
+
3
+ ## Added
4
+
5
+ - `patch()` makes [django-rest-framework-datatables](https://github.com/izimobil/django-rest-framework-datatables) read
6
+ `QUERY` request bodies, so server-side DataTables can send their parameters as a `QUERY` body.
7
+
8
+ ## Changed
9
+
10
+ - `filter_params()` caches its result on the request.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: drf-query-method
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: HTTP QUERY method (RFC 10008) support for Django REST framework.
5
5
  Project-URL: bugs, https://github.com/BenA-SA/drf-query-method/issues
6
6
  Project-URL: changelog, https://github.com/BenA-SA/drf-query-method/releases
@@ -73,8 +73,8 @@ class NoteViewSet(ModelViewSet):
73
73
  return self.list(request, *args, **kwargs)
74
74
  ```
75
75
 
76
- Routers then send `QUERY /notes/` to `query()`, and `SearchFilter`, `OrderingFilter` and django-filter's
77
- `DjangoFilterBackend` read their parameters from the request body. If you'd rather not patch DRF, import drop-in
76
+ Routers then send `QUERY /notes/` to `query()`, and `SearchFilter`, `OrderingFilter`, django-filter's
77
+ `DjangoFilterBackend` and django-rest-framework-datatables read their parameters from the request body. If you'd rather not patch DRF, import drop-in
78
78
  classes with the same names from `drf_query_method` instead. See the [documentation](https://BenA-SA.github.io/drf-query-method/usage/)
79
79
  for both approaches, and for sending `QUERY` requests from JavaScript and other clients.
80
80
 
@@ -85,7 +85,7 @@ The package is in early development and not yet released.
85
85
  * Python 3.10+
86
86
  * Django 4.2, 5.0, 5.1, 5.2, 6.0 and 6.1. Django 6.2 adds `QUERY` natively; on earlier versions this package adds it.
87
87
  * Django REST framework 3.16+
88
- * django-filter, optionally
88
+ * django-filter and django-rest-framework-datatables, optionally
89
89
 
90
90
  ## Installation
91
91
 
@@ -35,8 +35,8 @@ class NoteViewSet(ModelViewSet):
35
35
  return self.list(request, *args, **kwargs)
36
36
  ```
37
37
 
38
- Routers then send `QUERY /notes/` to `query()`, and `SearchFilter`, `OrderingFilter` and django-filter's
39
- `DjangoFilterBackend` read their parameters from the request body. If you'd rather not patch DRF, import drop-in
38
+ Routers then send `QUERY /notes/` to `query()`, and `SearchFilter`, `OrderingFilter`, django-filter's
39
+ `DjangoFilterBackend` and django-rest-framework-datatables read their parameters from the request body. If you'd rather not patch DRF, import drop-in
40
40
  classes with the same names from `drf_query_method` instead. See the [documentation](https://BenA-SA.github.io/drf-query-method/usage/)
41
41
  for both approaches, and for sending `QUERY` requests from JavaScript and other clients.
42
42
 
@@ -47,7 +47,7 @@ The package is in early development and not yet released.
47
47
  * Python 3.10+
48
48
  * Django 4.2, 5.0, 5.1, 5.2, 6.0 and 6.1. Django 6.2 adds `QUERY` natively; on earlier versions this package adds it.
49
49
  * Django REST framework 3.16+
50
- * django-filter, optionally
50
+ * django-filter and django-rest-framework-datatables, optionally
51
51
 
52
52
  ## Installation
53
53
 
@@ -57,6 +57,8 @@ were merged:
57
57
  * DRF's deprecated OpenAPI generator leaves `QUERY` out, as OpenAPI 3.0 has no query operation.
58
58
  * If [django-filter](https://github.com/carltongibson/django-filter) is installed, `DjangoFilterBackend` reads the
59
59
  request body too.
60
+ * If [django-rest-framework-datatables](https://github.com/izimobil/django-rest-framework-datatables) is installed, its
61
+ filter backends, pagination and renderer read the request body too. See [DataTables](#datatables) below.
60
62
 
61
63
  `patch()` is safe to call more than once, and leaves DRF alone once DRF supports `QUERY` itself.
62
64
 
@@ -73,6 +75,8 @@ Every class has the same name as DRF's (or django-filter's), so you change the i
73
75
  | `rest_framework.test.APIRequestFactory`, `APIClient`, `APITestCase` and friends | `drf_query_method.test` |
74
76
  | `django_filters.rest_framework.DjangoFilterBackend` | `drf_query_method.django_filters` |
75
77
 
78
+ django-rest-framework-datatables is supported through `patch()` only.
79
+
76
80
  Use the router and viewset classes together: the viewsets stop a `query()` method answering `QUERY` on detail routes.
77
81
 
78
82
  On Django versions before 6.2, also call `drf_query_method.add_query_method()` at startup (`patch()` does this for you).
@@ -137,8 +141,23 @@ A few things to check before relying on `QUERY` in production:
137
141
  application through everything in front of it.
138
142
  * **Caching.** `QUERY` responses are cacheable in principle, but caches generally don't key on request bodies, and
139
143
  Django's `cache_page` ignores `QUERY` from 6.2. Don't expect responses to be cached.
140
- * **django-rest-framework-datatables** reads its parameters with its own helper, which looks at the request body only
141
- for `POST`. It doesn't read `QUERY` bodies yet.
144
+ ## DataTables
145
+
146
+ With `patch()` and [django-rest-framework-datatables](https://github.com/izimobil/django-rest-framework-datatables),
147
+ a server-side DataTable can send its parameters as a `QUERY` body instead of a long query string. Keep `format=datatables`
148
+ in the URL, and set the Ajax type:
149
+
150
+ ```javascript
151
+ new DataTable("#notes", {
152
+ serverSide: true,
153
+ ajax: {url: "/api/notes/?format=datatables", type: "QUERY"},
154
+ columns: [{data: "title"}, {data: "created"}],
155
+ });
156
+ ```
157
+
158
+ jQuery sends DataTables' parameters as a form-encoded body for any method other than `GET` and `HEAD`, which is the
159
+ form django-rest-framework-datatables expects. The view needs a `query()` handler, as above. Its `?keep=` parameter is
160
+ still read from the URL.
142
161
 
143
162
  ## Django versions before 6.2
144
163
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "drf-query-method"
7
- version = "0.1.0"
7
+ version = "0.2.0"
8
8
  description = "HTTP QUERY method (RFC 10008) support for Django REST framework."
9
9
  readme = "README.md"
10
10
  authors = [
@@ -54,6 +54,7 @@ lint = [
54
54
  test = [
55
55
  "coverage",
56
56
  "django-filter",
57
+ "djangorestframework-datatables",
57
58
  "pytest",
58
59
  "pytest-django",
59
60
  ]
@@ -81,7 +82,7 @@ homepage = "https://github.com/BenA-SA/drf-query-method"
81
82
 
82
83
  [[tool.ty.overrides]]
83
84
  # These tests use attributes that patch() adds to DRF at runtime.
84
- include = ["tests/test_patching.py"]
85
+ include = ["tests/test_patching.py", "tests/test_datatables.py"]
85
86
  rules = { unresolved-attribute = "ignore" }
86
87
 
87
88
  [tool.ruff]
@@ -6,6 +6,8 @@ from django.http import QueryDict
6
6
  from django.utils.datastructures import MultiValueDict
7
7
  from rest_framework.exceptions import ParseError
8
8
 
9
+ _CACHE_ATTRIBUTE = "_drf_query_method_filter_params"
10
+
9
11
 
10
12
  def merge_filter_params(request) -> QueryDict:
11
13
  """Return ``request.query_params`` with a QUERY request's body merged over it.
@@ -17,11 +19,16 @@ def merge_filter_params(request) -> QueryDict:
17
19
  values, as repeated query-string parameters do.
18
20
 
19
21
  Raises ``ParseError`` (400) when the body is not an object of parameters,
20
- such as a JSON array.
22
+ such as a JSON array. The result is cached on the request, as packages
23
+ such as django-rest-framework-datatables read many parameters each.
21
24
  """
22
25
  if request.method != "QUERY":
23
26
  return request.query_params
24
27
 
28
+ cached = request.__dict__.get(_CACHE_ATTRIBUTE)
29
+ if cached is not None:
30
+ return cached
31
+
25
32
  body = request.data
26
33
  if not isinstance(body, Mapping):
27
34
  raise ParseError(f"QUERY request body must be an object of parameters, not {type(body).__name__}.")
@@ -32,6 +39,7 @@ def merge_filter_params(request) -> QueryDict:
32
39
  params = request.query_params.copy()
33
40
  for key, values in body.lists():
34
41
  params.setlist(key, values)
42
+ request.__dict__[_CACHE_ATTRIBUTE] = params
35
43
  return params
36
44
 
37
45
 
@@ -2,8 +2,9 @@
2
2
 
3
3
  :func:`patch` makes DRF behave as if encode/django-rest-framework#10091 were
4
4
  merged, so existing views, routers and settings pick up ``QUERY`` without
5
- changing imports. Call it once at startup, for example from an
6
- ``AppConfig.ready()``. It does nothing to DRF once DRF supports ``QUERY``
5
+ changing imports. When django-filter or django-rest-framework-datatables
6
+ is installed, they read QUERY request bodies too. Call it once at startup,
7
+ for example from an ``AppConfig.ready()``. It does nothing to DRF once DRF supports ``QUERY``
7
8
  itself.
8
9
  """
9
10
 
@@ -78,8 +79,28 @@ def _patch_django_filter() -> None:
78
79
  )
79
80
 
80
81
 
82
+ def _patch_drf_datatables() -> None:
83
+ """Make django-rest-framework-datatables read QUERY request bodies.
84
+
85
+ Its modules import ``get_param`` by name, so each binding is replaced.
86
+ """
87
+ from rest_framework_datatables import filters, pagination, renderers, utils
88
+
89
+ from drf_query_method.params import filter_params
90
+
91
+ original = utils.get_param
92
+
93
+ def get_param(request, param, default=None):
94
+ if request.method == "QUERY":
95
+ return filter_params(request).get(param, default)
96
+ return original(request, param, default)
97
+
98
+ for module in (utils, filters, pagination, renderers):
99
+ _set(module, "get_param", get_param)
100
+
101
+
81
102
  def patch() -> None:
82
- """Add QUERY support to Django, DRF and django-filter in place.
103
+ """Add QUERY support to Django, DRF, django-filter and django-rest-framework-datatables in place.
83
104
 
84
105
  Safe to call more than once. On Django versions before 6.2 this also
85
106
  calls :func:`~drf_query_method.add_query_method`, which :func:`unpatch`
@@ -95,6 +116,8 @@ def patch() -> None:
95
116
  _patch_drf()
96
117
  if importlib.util.find_spec("django_filters") is not None:
97
118
  _patch_django_filter()
119
+ if importlib.util.find_spec("rest_framework_datatables") is not None:
120
+ _patch_drf_datatables()
98
121
 
99
122
 
100
123
  def unpatch() -> None:
@@ -0,0 +1,94 @@
1
+ from urllib.parse import urlencode
2
+
3
+ import django_filters
4
+ import pytest
5
+ from django.contrib.auth.models import User
6
+ from rest_framework import generics
7
+ from rest_framework.renderers import JSONRenderer
8
+ from rest_framework.test import APIRequestFactory
9
+ from rest_framework_datatables import filters, pagination, renderers
10
+ from rest_framework_datatables.django_filters.backends import DatatablesFilterBackend
11
+ from rest_framework_datatables.django_filters.filters import GlobalFilter
12
+ from rest_framework_datatables.django_filters.filterset import DatatablesFilterSet
13
+
14
+ from tests.views import UserSerializer
15
+
16
+ DATATABLES_PARAMS = {
17
+ "draw": "3",
18
+ "start": "1",
19
+ "length": "1",
20
+ "columns[0][data]": "username",
21
+ "columns[0][name]": "username",
22
+ "columns[0][searchable]": "true",
23
+ "columns[0][orderable]": "true",
24
+ "columns[0][search][value]": "",
25
+ "columns[0][search][regex]": "false",
26
+ "order[0][column]": "0",
27
+ "order[0][dir]": "desc",
28
+ "search[value]": "o",
29
+ "search[regex]": "false",
30
+ }
31
+
32
+
33
+ class GlobalCharFilter(GlobalFilter, django_filters.CharFilter):
34
+ pass
35
+
36
+
37
+ class UserGlobalFilterSet(DatatablesFilterSet):
38
+ username = GlobalCharFilter(lookup_expr="icontains")
39
+
40
+ class Meta:
41
+ model = User
42
+ fields = ["username"]
43
+
44
+
45
+ def build_view(filter_backend):
46
+ class View(generics.ListAPIView):
47
+ queryset = User.objects.order_by("pk")
48
+ serializer_class = UserSerializer
49
+ permission_classes = []
50
+ filterset_class = UserGlobalFilterSet
51
+ renderer_classes = [JSONRenderer, renderers.DatatablesRenderer]
52
+ filter_backends = [filter_backend]
53
+ pagination_class = pagination.DatatablesPageNumberPagination
54
+
55
+ def query(self, request, *args, **kwargs):
56
+ return self.list(request, *args, **kwargs)
57
+
58
+ return View.as_view()
59
+
60
+
61
+ @pytest.fixture
62
+ def users(db):
63
+ for username in ["alice", "bob", "carol"]:
64
+ User.objects.create(username=username)
65
+
66
+
67
+ @pytest.mark.parametrize("filter_backend", [filters.DatatablesFilterBackend, DatatablesFilterBackend])
68
+ def test_datatables_reads_query_body(patched, users, filter_backend):
69
+ request = APIRequestFactory().query(
70
+ "/?format=datatables", urlencode(DATATABLES_PARAMS), content_type="application/x-www-form-urlencoded"
71
+ )
72
+ response = build_view(filter_backend)(request)
73
+ response.render()
74
+ assert response.data["draw"] == 3
75
+ assert response.data["recordsTotal"] == 3
76
+ assert response.data["recordsFiltered"] == 2
77
+ assert [user["username"] for user in response.data["data"]] == ["bob"]
78
+
79
+
80
+ def test_datatables_get_is_unchanged(patched, users):
81
+ request = APIRequestFactory().get("/", {**DATATABLES_PARAMS, "format": "datatables"})
82
+ response = build_view(filters.DatatablesFilterBackend)(request)
83
+ assert [user["username"] for user in response.data["data"]] == ["bob"]
84
+
85
+
86
+ def test_merged_filter_params_are_cached_per_request():
87
+ from rest_framework.parsers import JSONParser
88
+ from rest_framework.request import Request
89
+
90
+ from drf_query_method.params import merge_filter_params
91
+ from drf_query_method.test import APIRequestFactory as QueryRequestFactory
92
+
93
+ request = Request(QueryRequestFactory().query("/", {"a": "1"}, format="json"), parsers=[JSONParser()])
94
+ assert merge_filter_params(request) is merge_filter_params(request)
@@ -408,9 +408,21 @@ wheels = [
408
408
  { url = "https://files.pythonhosted.org/packages/c4/dd/de5f4543765bec94e036a5e9593a42f8fa7efec8de78eb56b9ca834385f3/djangorestframework-3.18.3-py3-none-any.whl", hash = "sha256:8544bb674846731b1e3c9b309236ee1dc412905a0aa725be2ec193ca950a7d12", size = 903986, upload-time = "2026-10-06T21:33:49.917Z" },
409
409
  ]
410
410
 
411
+ [[package]]
412
+ name = "djangorestframework-datatables"
413
+ version = "0.7.2"
414
+ source = { registry = "https://pypi.org/simple" }
415
+ dependencies = [
416
+ { name = "djangorestframework" },
417
+ ]
418
+ sdist = { url = "https://files.pythonhosted.org/packages/c7/a0/7481fee713387fd5c632b05c1d95c32f5b2ea6e1e8f15c9cf1894e1895a8/djangorestframework_datatables-0.7.2.tar.gz", hash = "sha256:d7953968088ceb14968621a0c0a83d2f39ec1cda24b5ebe1240f3c9619bc7129", size = 19647, upload-time = "2024-06-14T17:11:41.742Z" }
419
+ wheels = [
420
+ { url = "https://files.pythonhosted.org/packages/f3/25/17cd01982c7299cbca9e20247e73c83bfa53a0ed9e8b7735c915c1bb6675/djangorestframework_datatables-0.7.2-py2.py3-none-any.whl", hash = "sha256:fd5747b1e3d0bfb84713a975394dd4d879b57ee8bc66cc0f97566fa67f4406f4", size = 15303, upload-time = "2024-06-14T17:11:39.658Z" },
421
+ ]
422
+
411
423
  [[package]]
412
424
  name = "drf-query-method"
413
- version = "0.1.0"
425
+ version = "0.2.0"
414
426
  source = { editable = "." }
415
427
  dependencies = [
416
428
  { name = "django", version = "5.2.18", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" },
@@ -430,6 +442,7 @@ dev = [
430
442
  { name = "coverage", version = "7.17.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
431
443
  { name = "django-filter", version = "26.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
432
444
  { name = "django-filter", version = "26.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
445
+ { name = "djangorestframework-datatables" },
433
446
  { name = "pytest" },
434
447
  { name = "pytest-django" },
435
448
  { name = "ruff" },
@@ -449,6 +462,7 @@ test = [
449
462
  { name = "coverage", version = "7.17.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
450
463
  { name = "django-filter", version = "26.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
451
464
  { name = "django-filter", version = "26.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
465
+ { name = "djangorestframework-datatables" },
452
466
  { name = "pytest" },
453
467
  { name = "pytest-django" },
454
468
  ]
@@ -468,6 +482,7 @@ provides-extras = ["django-filter"]
468
482
  dev = [
469
483
  { name = "coverage" },
470
484
  { name = "django-filter" },
485
+ { name = "djangorestframework-datatables" },
471
486
  { name = "pytest" },
472
487
  { name = "pytest-django" },
473
488
  { name = "ruff" },
@@ -481,6 +496,7 @@ lint = [{ name = "ruff" }]
481
496
  test = [
482
497
  { name = "coverage" },
483
498
  { name = "django-filter" },
499
+ { name = "djangorestframework-datatables" },
484
500
  { name = "pytest" },
485
501
  { name = "pytest-django" },
486
502
  ]