drf-react-by-schema 0.28.0__tar.gz → 0.28.2__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.
- {drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/PKG-INFO +71 -1
- {drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/README.rst +70 -0
- drf_react_by_schema-0.28.2/drf_react_by_schema/filters.py +112 -0
- {drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/metadata.py +2 -1
- {drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/schema_bundle.py +6 -1
- {drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/serializers.py +90 -10
- {drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/utils.py +22 -0
- {drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/viewsets.py +51 -40
- {drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema.egg-info/PKG-INFO +71 -1
- {drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/setup.cfg +1 -1
- drf_react_by_schema-0.28.0/drf_react_by_schema/filters.py +0 -24
- {drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/LICENSE +0 -0
- {drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/MANIFEST.in +0 -0
- {drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/__init__.py +0 -0
- {drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/admin.py +0 -0
- {drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/apps.py +0 -0
- {drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/auth.py +0 -0
- {drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/autocomplete_serializers.py +0 -0
- {drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/autocomplete_viewsets.py +0 -0
- {drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/checks.py +0 -0
- {drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/date_utils.py +0 -0
- {drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/fields/__init__.py +0 -0
- {drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/fields/array.py +0 -0
- {drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/fields/boolean.py +0 -0
- {drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/fields/date.py +0 -0
- {drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/fields/file.py +0 -0
- {drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/fields/geometry.py +0 -0
- {drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/fields/number.py +0 -0
- {drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/fields/relational.py +0 -0
- {drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/fields/serializers.py +0 -0
- {drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/fields/text.py +0 -0
- {drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/management/__init__.py +0 -0
- {drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/management/commands/__init__.py +0 -0
- {drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/management/commands/sync_react.py +0 -0
- {drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/migrations/__init__.py +0 -0
- {drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/mixins.py +0 -0
- {drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/models.py +0 -0
- {drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/pagination.py +0 -0
- {drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/patches/__init__.py +0 -0
- {drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/patches/drf_serializers.py +0 -0
- {drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/renderers.py +0 -0
- {drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/tests.py +0 -0
- {drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/typescript_generator.py +0 -0
- {drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/urls.py +0 -0
- {drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/utils_cache.py +0 -0
- {drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/views.py +0 -0
- {drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema.egg-info/SOURCES.txt +0 -0
- {drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema.egg-info/dependency_links.txt +0 -0
- {drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema.egg-info/requires.txt +0 -0
- {drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema.egg-info/top_level.txt +0 -0
- {drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: drf_react_by_schema
|
|
3
|
-
Version: 0.28.
|
|
3
|
+
Version: 0.28.2
|
|
4
4
|
Summary: A Django app to easily integrate with React from models.
|
|
5
5
|
Home-page: https://gitlab.com/eita/drf-react-by-schema/drf-react-by-schema-django
|
|
6
6
|
Author: Cooperativa EITA
|
|
@@ -115,6 +115,43 @@ single release line. The supported combinations are exercised in CI (see
|
|
|
115
115
|
drf-react-by-schema
|
|
116
116
|
drf-excel<2.6
|
|
117
117
|
|
|
118
|
+
Django 3.2 is kept as **legacy support**: the 3.2-only pin above and its CI job
|
|
119
|
+
will be dropped when the supported floor moves to 4.2. New code should target
|
|
120
|
+
4.2-5.2; Django-3.2-only branches must stay isolated so the migration is a single
|
|
121
|
+
clean-up pass.
|
|
122
|
+
|
|
123
|
+
Accent-insensitive search
|
|
124
|
+
-------------------------
|
|
125
|
+
On PostgreSQL with the ``unaccent`` extension enabled (and
|
|
126
|
+
``django.contrib.postgres`` in ``INSTALLED_APPS``), replace DRF's search filter
|
|
127
|
+
with the package's::
|
|
128
|
+
|
|
129
|
+
REST_FRAMEWORK = {
|
|
130
|
+
"DEFAULT_FILTER_BACKENDS": [
|
|
131
|
+
"drf_react_by_schema.filters.DRFReactBySchemaSearchFilter",
|
|
132
|
+
"drf_react_by_schema.filters.DRFReactBySchemaOrderingFilter",
|
|
133
|
+
],
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
``unaccent`` is applied only to text fields; dates, numbers and booleans keep
|
|
137
|
+
DRF's default lookup, because ``unaccent`` raises on them.
|
|
138
|
+
|
|
139
|
+
The filter is **opt-in**: apps that keep DRF's ``SearchFilter`` are unaffected
|
|
140
|
+
and do not need the extension. It probes the backend, ``INSTALLED_APPS`` and the
|
|
141
|
+
database extension once per connection alias. If the extension is missing — or
|
|
142
|
+
the database is not PostgreSQL, or ``django.contrib.postgres`` is not installed
|
|
143
|
+
— it falls back to DRF's normal lookups: search keeps working (accent-sensitive)
|
|
144
|
+
instead of raising ``FieldError``. Call
|
|
145
|
+
``drf_react_by_schema.filters.reset_unaccent_cache()`` if the extension is
|
|
146
|
+
installed while the process is running.
|
|
147
|
+
|
|
148
|
+
To create the extension, add a migration in the consuming app::
|
|
149
|
+
|
|
150
|
+
from django.contrib.postgres.operations import UnaccentExtension
|
|
151
|
+
|
|
152
|
+
class Migration(migrations.Migration):
|
|
153
|
+
operations = [UnaccentExtension()]
|
|
154
|
+
|
|
118
155
|
Optional Dependencies
|
|
119
156
|
---------------------
|
|
120
157
|
For enhanced caching capabilities in the automatically generated viewsets, you can install optional dependencies:
|
|
@@ -252,6 +289,39 @@ Quick start
|
|
|
252
289
|
|
|
253
290
|
You can customize ``viewsets`` and ``serializers`` to annotate other attributes.
|
|
254
291
|
|
|
292
|
+
List vs. detail serializers
|
|
293
|
+
---------------------------
|
|
294
|
+
The generated ``XSerializer`` is the canonical serializer and serves both ``retrieve``
|
|
295
|
+
and ``list``/``excel``. When a detail-only field would be expensive in lists, declare it
|
|
296
|
+
in ``Meta.list_exclude`` on the app's general serializer:
|
|
297
|
+
|
|
298
|
+
.. code-block:: python
|
|
299
|
+
|
|
300
|
+
from drf_react_by_schema.serializers import (
|
|
301
|
+
ClienteSerializer as BaseClienteSerializer,
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
class ClienteSerializer(BaseClienteSerializer):
|
|
305
|
+
contratacao = drf_serializers.SerializerMethodField() # detail only
|
|
306
|
+
nome = drf_serializers.CharField(read_only=True) # both
|
|
307
|
+
|
|
308
|
+
class Meta(BaseClienteSerializer.Meta):
|
|
309
|
+
list_exclude = ("contratacao",)
|
|
310
|
+
|
|
311
|
+
The package derives (and caches) a list projection that keeps every other field,
|
|
312
|
+
including future additions to the general serializer. ``list_exclude`` takes serializer
|
|
313
|
+
field names, not model ``source`` paths; ``()`` means "the list is identical to the
|
|
314
|
+
detail response". An explicit list serializer remains available as an escape hatch:
|
|
315
|
+
|
|
316
|
+
.. code-block:: python
|
|
317
|
+
|
|
318
|
+
class ClienteViewSet(GenericViewSet):
|
|
319
|
+
serializer_class = ClienteSerializer
|
|
320
|
+
serializer_list_class = ClienteListSerializer # optional, full override
|
|
321
|
+
|
|
322
|
+
``XListSerializer`` classes are no longer generated automatically: the default is the
|
|
323
|
+
general serializer, so app-added fields can never silently disappear from lists.
|
|
324
|
+
|
|
255
325
|
Global permission-aware queryset filtering
|
|
256
326
|
------------------------------------------
|
|
257
327
|
Every generated viewset already supports a ``filter_by_callback`` (and a list-only
|
|
@@ -83,6 +83,43 @@ single release line. The supported combinations are exercised in CI (see
|
|
|
83
83
|
drf-react-by-schema
|
|
84
84
|
drf-excel<2.6
|
|
85
85
|
|
|
86
|
+
Django 3.2 is kept as **legacy support**: the 3.2-only pin above and its CI job
|
|
87
|
+
will be dropped when the supported floor moves to 4.2. New code should target
|
|
88
|
+
4.2-5.2; Django-3.2-only branches must stay isolated so the migration is a single
|
|
89
|
+
clean-up pass.
|
|
90
|
+
|
|
91
|
+
Accent-insensitive search
|
|
92
|
+
-------------------------
|
|
93
|
+
On PostgreSQL with the ``unaccent`` extension enabled (and
|
|
94
|
+
``django.contrib.postgres`` in ``INSTALLED_APPS``), replace DRF's search filter
|
|
95
|
+
with the package's::
|
|
96
|
+
|
|
97
|
+
REST_FRAMEWORK = {
|
|
98
|
+
"DEFAULT_FILTER_BACKENDS": [
|
|
99
|
+
"drf_react_by_schema.filters.DRFReactBySchemaSearchFilter",
|
|
100
|
+
"drf_react_by_schema.filters.DRFReactBySchemaOrderingFilter",
|
|
101
|
+
],
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
``unaccent`` is applied only to text fields; dates, numbers and booleans keep
|
|
105
|
+
DRF's default lookup, because ``unaccent`` raises on them.
|
|
106
|
+
|
|
107
|
+
The filter is **opt-in**: apps that keep DRF's ``SearchFilter`` are unaffected
|
|
108
|
+
and do not need the extension. It probes the backend, ``INSTALLED_APPS`` and the
|
|
109
|
+
database extension once per connection alias. If the extension is missing — or
|
|
110
|
+
the database is not PostgreSQL, or ``django.contrib.postgres`` is not installed
|
|
111
|
+
— it falls back to DRF's normal lookups: search keeps working (accent-sensitive)
|
|
112
|
+
instead of raising ``FieldError``. Call
|
|
113
|
+
``drf_react_by_schema.filters.reset_unaccent_cache()`` if the extension is
|
|
114
|
+
installed while the process is running.
|
|
115
|
+
|
|
116
|
+
To create the extension, add a migration in the consuming app::
|
|
117
|
+
|
|
118
|
+
from django.contrib.postgres.operations import UnaccentExtension
|
|
119
|
+
|
|
120
|
+
class Migration(migrations.Migration):
|
|
121
|
+
operations = [UnaccentExtension()]
|
|
122
|
+
|
|
86
123
|
Optional Dependencies
|
|
87
124
|
---------------------
|
|
88
125
|
For enhanced caching capabilities in the automatically generated viewsets, you can install optional dependencies:
|
|
@@ -220,6 +257,39 @@ Quick start
|
|
|
220
257
|
|
|
221
258
|
You can customize ``viewsets`` and ``serializers`` to annotate other attributes.
|
|
222
259
|
|
|
260
|
+
List vs. detail serializers
|
|
261
|
+
---------------------------
|
|
262
|
+
The generated ``XSerializer`` is the canonical serializer and serves both ``retrieve``
|
|
263
|
+
and ``list``/``excel``. When a detail-only field would be expensive in lists, declare it
|
|
264
|
+
in ``Meta.list_exclude`` on the app's general serializer:
|
|
265
|
+
|
|
266
|
+
.. code-block:: python
|
|
267
|
+
|
|
268
|
+
from drf_react_by_schema.serializers import (
|
|
269
|
+
ClienteSerializer as BaseClienteSerializer,
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
class ClienteSerializer(BaseClienteSerializer):
|
|
273
|
+
contratacao = drf_serializers.SerializerMethodField() # detail only
|
|
274
|
+
nome = drf_serializers.CharField(read_only=True) # both
|
|
275
|
+
|
|
276
|
+
class Meta(BaseClienteSerializer.Meta):
|
|
277
|
+
list_exclude = ("contratacao",)
|
|
278
|
+
|
|
279
|
+
The package derives (and caches) a list projection that keeps every other field,
|
|
280
|
+
including future additions to the general serializer. ``list_exclude`` takes serializer
|
|
281
|
+
field names, not model ``source`` paths; ``()`` means "the list is identical to the
|
|
282
|
+
detail response". An explicit list serializer remains available as an escape hatch:
|
|
283
|
+
|
|
284
|
+
.. code-block:: python
|
|
285
|
+
|
|
286
|
+
class ClienteViewSet(GenericViewSet):
|
|
287
|
+
serializer_class = ClienteSerializer
|
|
288
|
+
serializer_list_class = ClienteListSerializer # optional, full override
|
|
289
|
+
|
|
290
|
+
``XListSerializer`` classes are no longer generated automatically: the default is the
|
|
291
|
+
general serializer, so app-added fields can never silently disappear from lists.
|
|
292
|
+
|
|
223
293
|
Global permission-aware queryset filtering
|
|
224
294
|
------------------------------------------
|
|
225
295
|
Every generated viewset already supports a ``filter_by_callback`` (and a list-only
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
from django.apps import apps
|
|
2
|
+
from django.db import connections
|
|
3
|
+
from django.db.models import CharField, TextField
|
|
4
|
+
from rest_framework.filters import OrderingFilter, SearchFilter
|
|
5
|
+
|
|
6
|
+
from . import utils
|
|
7
|
+
|
|
8
|
+
# Only these field types support the unaccent lookup.
|
|
9
|
+
TEXT_FIELD_CLASSES = (CharField, TextField)
|
|
10
|
+
|
|
11
|
+
# DRF lookups the filter converts to their unaccent-aware counterparts.
|
|
12
|
+
UNACCENT_LOOKUPS = ("icontains", "istartswith", "iexact")
|
|
13
|
+
|
|
14
|
+
# Per-database result of the unaccent probe (one query per alias per process).
|
|
15
|
+
_UNACCENT_AVAILABLE = {}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def reset_unaccent_cache():
|
|
19
|
+
"""Forget the unaccent probe, e.g. after installing the extension."""
|
|
20
|
+
_UNACCENT_AVAILABLE.clear()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def unaccent_available(connection):
|
|
24
|
+
"""Whether ``connection`` can run the ``unaccent`` lookup.
|
|
25
|
+
|
|
26
|
+
False on non-PostgreSQL backends, without ``django.contrib.postgres`` (which
|
|
27
|
+
registers the lookup), or when the extension is missing from the database.
|
|
28
|
+
"""
|
|
29
|
+
if connection.vendor != "postgresql":
|
|
30
|
+
return False
|
|
31
|
+
if not apps.is_installed("django.contrib.postgres"):
|
|
32
|
+
return False
|
|
33
|
+
alias = connection.alias
|
|
34
|
+
if alias not in _UNACCENT_AVAILABLE:
|
|
35
|
+
try:
|
|
36
|
+
with connection.cursor() as cursor:
|
|
37
|
+
cursor.execute(
|
|
38
|
+
"SELECT 1 FROM pg_extension WHERE extname = 'unaccent'"
|
|
39
|
+
)
|
|
40
|
+
_UNACCENT_AVAILABLE[alias] = cursor.fetchone() is not None
|
|
41
|
+
except Exception:
|
|
42
|
+
_UNACCENT_AVAILABLE[alias] = False
|
|
43
|
+
return _UNACCENT_AVAILABLE[alias]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class DRFReactBySchemaSearchFilter(SearchFilter):
|
|
47
|
+
"""``SearchFilter`` with accent-insensitive matches on text fields.
|
|
48
|
+
|
|
49
|
+
Non-text fields keep DRF's default lookup: ``unaccent`` only accepts text,
|
|
50
|
+
so applying it to a date, number or boolean raises ``FieldError``.
|
|
51
|
+
|
|
52
|
+
Accent-insensitive search needs PostgreSQL with the ``unaccent`` extension
|
|
53
|
+
enabled and ``django.contrib.postgres`` in ``INSTALLED_APPS``. When either
|
|
54
|
+
is missing (or the database is not PostgreSQL), the filter silently falls
|
|
55
|
+
back to DRF's lookups, so search still works (accent-sensitive).
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
# Resolved per request by ``filter_queryset``; None on direct calls.
|
|
59
|
+
_unaccent_enabled = None
|
|
60
|
+
|
|
61
|
+
def filter_queryset(self, request, queryset, view):
|
|
62
|
+
self._unaccent_enabled = unaccent_available(connections[queryset.db])
|
|
63
|
+
return super().filter_queryset(request, queryset, view)
|
|
64
|
+
|
|
65
|
+
def construct_search(self, field_name, queryset):
|
|
66
|
+
lookup = super().construct_search(field_name, queryset)
|
|
67
|
+
unaccent_enabled = self._unaccent_enabled
|
|
68
|
+
if unaccent_enabled is None:
|
|
69
|
+
unaccent_enabled = unaccent_available(connections[queryset.db])
|
|
70
|
+
if not unaccent_enabled:
|
|
71
|
+
return lookup
|
|
72
|
+
if not self._is_text_field(queryset.model, field_name):
|
|
73
|
+
return lookup
|
|
74
|
+
for standard_lookup in UNACCENT_LOOKUPS:
|
|
75
|
+
if lookup.endswith(f"__{standard_lookup}"):
|
|
76
|
+
return lookup[: -len(standard_lookup)] + f"unaccent__{standard_lookup}"
|
|
77
|
+
return lookup
|
|
78
|
+
|
|
79
|
+
@staticmethod
|
|
80
|
+
def _is_text_field(model, field_path):
|
|
81
|
+
"""Whether the terminal field in ``field_path`` is a text field."""
|
|
82
|
+
# Strip DRF prefix modifiers: ^, =, @, $
|
|
83
|
+
parts = str(field_path).lstrip("^=@$").split("__")
|
|
84
|
+
try:
|
|
85
|
+
for part in parts[:-1]:
|
|
86
|
+
model = model._meta.get_field(part).related_model
|
|
87
|
+
return isinstance(model._meta.get_field(parts[-1]), TEXT_FIELD_CLASSES)
|
|
88
|
+
except Exception:
|
|
89
|
+
# Unknown paths and non-relational lookups keep the standard lookup.
|
|
90
|
+
return False
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class DRFReactBySchemaOrderingFilter(OrderingFilter):
|
|
94
|
+
def get_ordering(self, request, queryset, view):
|
|
95
|
+
"""
|
|
96
|
+
Ordering is set by a comma delimited ?ordering=... query parameter.
|
|
97
|
+
|
|
98
|
+
The `ordering` query parameter can be overridden by setting
|
|
99
|
+
the `ordering_param` value on the OrderingFilter or by
|
|
100
|
+
specifying an `ORDERING_PARAM` value in the API settings.
|
|
101
|
+
"""
|
|
102
|
+
params = request.query_params.get(self.ordering_param)
|
|
103
|
+
if params:
|
|
104
|
+
fields_raw = [param.strip() for param in params.split(",")]
|
|
105
|
+
fields = utils.serializer_to_model_fields(view, fields_raw)
|
|
106
|
+
if fields is not None:
|
|
107
|
+
ordering = self.remove_invalid_fields(queryset, fields, view, request)
|
|
108
|
+
if ordering:
|
|
109
|
+
return ordering
|
|
110
|
+
|
|
111
|
+
# No ordering was included, or all the ordering fields were invalid
|
|
112
|
+
return self.get_default_ordering(view)
|
|
@@ -15,7 +15,8 @@ class _PinnedSerializerClass:
|
|
|
15
15
|
|
|
16
16
|
``SimpleMetadata.determine_metadata`` derives the POST fields from
|
|
17
17
|
``view.get_serializer()``, which is action-dependent (``list``/``excel``
|
|
18
|
-
use
|
|
18
|
+
may use an explicit list serializer or a ``Meta.list_exclude`` projection).
|
|
19
|
+
Pinning keeps the schema canonical.
|
|
19
20
|
"""
|
|
20
21
|
|
|
21
22
|
def __init__(self, view, serializer_class):
|
{drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/schema_bundle.py
RENAMED
|
@@ -53,6 +53,7 @@ from .utils import (
|
|
|
53
53
|
DATE_FILTER_OPERATORS,
|
|
54
54
|
GRID_FILTER_OPERATORS,
|
|
55
55
|
camel_to_snake,
|
|
56
|
+
is_searchable_model_field,
|
|
56
57
|
sum_rows_capabilities,
|
|
57
58
|
)
|
|
58
59
|
|
|
@@ -543,7 +544,11 @@ def _search_ordering_fields(view_instance) -> tuple[list, list]:
|
|
|
543
544
|
except Exception:
|
|
544
545
|
return [], []
|
|
545
546
|
return (
|
|
546
|
-
[
|
|
547
|
+
[
|
|
548
|
+
str(field)
|
|
549
|
+
for field in (view_instance.search_fields or [])
|
|
550
|
+
if is_searchable_model_field(view_instance, str(field))
|
|
551
|
+
],
|
|
547
552
|
[str(field) for field in (view_instance.ordering_fields or [])],
|
|
548
553
|
)
|
|
549
554
|
|
{drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/serializers.py
RENAMED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import traceback
|
|
2
|
+
from functools import lru_cache
|
|
3
|
+
|
|
2
4
|
from django.apps import apps
|
|
3
5
|
from django.contrib.auth import get_user_model
|
|
4
6
|
from django.contrib.auth import password_validation
|
|
5
7
|
from django.contrib.auth.tokens import default_token_generator
|
|
8
|
+
from django.core.exceptions import ImproperlyConfigured
|
|
6
9
|
from django.core.exceptions import ValidationError as DjangoValidationError
|
|
7
10
|
from django.utils.encoding import force_str
|
|
8
11
|
from django.utils.http import urlsafe_base64_decode
|
|
@@ -148,6 +151,11 @@ class GenericSerializerMetaclass(SerializerMetaclass):
|
|
|
148
151
|
# First, let DRF's SerializerMetaclass do its work
|
|
149
152
|
new_class = super().__new__(mcs, name, bases, attrs)
|
|
150
153
|
|
|
154
|
+
# List projections inherit the detail serializer's related-field setup;
|
|
155
|
+
# re-running it would rebuild update_related/model_fields unnecessarily.
|
|
156
|
+
if attrs.get("_drbs_projection"):
|
|
157
|
+
return new_class
|
|
158
|
+
|
|
151
159
|
# Only process if this class has a Meta with a model
|
|
152
160
|
if hasattr(new_class, "Meta") and hasattr(new_class.Meta, "model"):
|
|
153
161
|
mcs._populate_related_fields(new_class)
|
|
@@ -494,6 +502,88 @@ class GenericSerializer(
|
|
|
494
502
|
fields = "__all__"
|
|
495
503
|
|
|
496
504
|
|
|
505
|
+
class _ListProjectionMixin:
|
|
506
|
+
"""Drop the detail-only fields from a projected list serializer."""
|
|
507
|
+
|
|
508
|
+
def get_fields(self):
|
|
509
|
+
fields = super().get_fields()
|
|
510
|
+
for name in getattr(self.Meta, "list_exclude", ()) or ():
|
|
511
|
+
fields.pop(name, None)
|
|
512
|
+
return fields
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
def _known_serializer_field_names(detail_class):
|
|
516
|
+
"""Field names that ``Meta.list_exclude`` may legitimately reference."""
|
|
517
|
+
known = set(getattr(detail_class, "_declared_fields", {}) or {})
|
|
518
|
+
meta = getattr(detail_class, "Meta", None)
|
|
519
|
+
model = getattr(meta, "model", None)
|
|
520
|
+
if model is not None:
|
|
521
|
+
known.update(field.name for field in model._meta.get_fields())
|
|
522
|
+
meta_fields = getattr(meta, "fields", None)
|
|
523
|
+
if isinstance(meta_fields, (list, tuple)):
|
|
524
|
+
known.update(meta_fields)
|
|
525
|
+
elif isinstance(meta_fields, dict):
|
|
526
|
+
known.update(meta_fields)
|
|
527
|
+
return known
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
def _list_serializer_class_name(detail_class):
|
|
531
|
+
suffix = "Serializer"
|
|
532
|
+
name = detail_class.__name__
|
|
533
|
+
base = name[: -len(suffix)] if name.endswith(suffix) else name
|
|
534
|
+
return f"{base}ListSerializer"
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
@lru_cache(maxsize=None)
|
|
538
|
+
def _build_list_projection(detail_class, excludes):
|
|
539
|
+
meta = type("Meta", (detail_class.Meta,), {"list_exclude": excludes})
|
|
540
|
+
return type(
|
|
541
|
+
_list_serializer_class_name(detail_class),
|
|
542
|
+
(_ListProjectionMixin, detail_class),
|
|
543
|
+
{
|
|
544
|
+
"Meta": meta,
|
|
545
|
+
"__module__": detail_class.__module__,
|
|
546
|
+
"_drbs_projection": True,
|
|
547
|
+
},
|
|
548
|
+
)
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
def list_serializer_for(detail_class):
|
|
552
|
+
"""List serializer derived from ``detail_class``, or ``None``.
|
|
553
|
+
|
|
554
|
+
The general serializer is the canonical shape and is used for list/excel by
|
|
555
|
+
default. Setting ``Meta.list_exclude`` opts into a lighter list that keeps
|
|
556
|
+
every other field (including app-added ones) and drops the listed names.
|
|
557
|
+
"""
|
|
558
|
+
if detail_class is None:
|
|
559
|
+
return None
|
|
560
|
+
meta = getattr(detail_class, "Meta", None)
|
|
561
|
+
excludes = getattr(meta, "list_exclude", None)
|
|
562
|
+
if excludes is None:
|
|
563
|
+
return None
|
|
564
|
+
if isinstance(excludes, str) or not isinstance(
|
|
565
|
+
excludes, (list, tuple, set, frozenset)
|
|
566
|
+
):
|
|
567
|
+
raise ImproperlyConfigured(
|
|
568
|
+
f"{detail_class.__name__}.Meta.list_exclude must be a list or tuple "
|
|
569
|
+
"of serializer field names."
|
|
570
|
+
)
|
|
571
|
+
if isinstance(excludes, (set, frozenset)):
|
|
572
|
+
excludes = tuple(sorted(excludes))
|
|
573
|
+
else:
|
|
574
|
+
excludes = tuple(excludes)
|
|
575
|
+
if not excludes:
|
|
576
|
+
return None
|
|
577
|
+
|
|
578
|
+
unknown = set(excludes) - _known_serializer_field_names(detail_class)
|
|
579
|
+
if unknown:
|
|
580
|
+
raise ImproperlyConfigured(
|
|
581
|
+
f"{detail_class.__name__}.Meta.list_exclude references unknown "
|
|
582
|
+
f"field(s): {', '.join(sorted(unknown))}."
|
|
583
|
+
)
|
|
584
|
+
return _build_list_projection(detail_class, excludes)
|
|
585
|
+
|
|
586
|
+
|
|
497
587
|
######################
|
|
498
588
|
|
|
499
589
|
|
|
@@ -501,19 +591,9 @@ for app in settings["APPS"]:
|
|
|
501
591
|
for name, model in apps.all_models[app].items():
|
|
502
592
|
model_name = model.__name__
|
|
503
593
|
if isinstance(model, type):
|
|
504
|
-
serializer_list_name = f"{model_name}ListSerializer"
|
|
505
594
|
serializer_name = f"{model_name}Serializer"
|
|
506
595
|
if not serializer_name in dir():
|
|
507
596
|
meta = type("Meta", (), {"fields": "__all__", "model": model})
|
|
508
|
-
generated_class = type(
|
|
509
|
-
serializer_list_name,
|
|
510
|
-
(GenericSerializer,),
|
|
511
|
-
{
|
|
512
|
-
"Meta": meta,
|
|
513
|
-
},
|
|
514
|
-
)
|
|
515
|
-
globals()[serializer_list_name] = generated_class
|
|
516
|
-
|
|
517
597
|
generated_class = type(
|
|
518
598
|
serializer_name, (GenericSerializer,), {"Meta": meta}
|
|
519
599
|
)
|
|
@@ -159,6 +159,17 @@ def to_pascal_case(s):
|
|
|
159
159
|
return s.replace("_", " ").title().replace(" ", "")
|
|
160
160
|
|
|
161
161
|
|
|
162
|
+
def list_serializer_name(serializer_name):
|
|
163
|
+
"""Conventional list-serializer name for a detail serializer class name."""
|
|
164
|
+
suffix = "Serializer"
|
|
165
|
+
base = (
|
|
166
|
+
serializer_name[: -len(suffix)]
|
|
167
|
+
if serializer_name.endswith(suffix)
|
|
168
|
+
else serializer_name
|
|
169
|
+
)
|
|
170
|
+
return f"{base}ListSerializer"
|
|
171
|
+
|
|
172
|
+
|
|
162
173
|
def get_target_field(view, field, prefix=None):
|
|
163
174
|
field_name = getattr(field, "name", None)
|
|
164
175
|
related_model = getattr(field, "related_model", None)
|
|
@@ -321,6 +332,17 @@ def _resolve_model_field(view, path):
|
|
|
321
332
|
return field
|
|
322
333
|
|
|
323
334
|
|
|
335
|
+
def is_searchable_model_field(view, path):
|
|
336
|
+
"""Whether ``path`` accepts DRF's default ``icontains`` search lookup.
|
|
337
|
+
|
|
338
|
+
A path ending on a relation (FK, reverse or m2m) has no ``icontains``
|
|
339
|
+
lookup: putting it in ``search_fields`` makes every ``?search=`` request
|
|
340
|
+
raise ``FieldError``. Unknown paths (e.g. annotations) are left untouched.
|
|
341
|
+
"""
|
|
342
|
+
field = _resolve_model_field(view, path)
|
|
343
|
+
return field is None or not field.is_relation
|
|
344
|
+
|
|
345
|
+
|
|
324
346
|
def _is_non_string_field(view, target_field, column_field_serializer):
|
|
325
347
|
"""Whether the grid filter must treat the target as non-string.
|
|
326
348
|
|
|
@@ -78,53 +78,66 @@ class DRFReactBySchemaBaseModelViewSet(
|
|
|
78
78
|
|
|
79
79
|
def __init__(self, *args, **kwargs):
|
|
80
80
|
super().__init__(*args, **kwargs)
|
|
81
|
-
if not self.model:
|
|
81
|
+
if not self.model and self.model_name:
|
|
82
82
|
self.model = utils.get_model(self.app, self.model_name)
|
|
83
83
|
if self.app and self.serializer_class is not None:
|
|
84
|
-
|
|
85
|
-
|
|
84
|
+
module_name = (
|
|
85
|
+
"autocomplete_serializers"
|
|
86
|
+
if self.is_autocomplete
|
|
87
|
+
else "serializers"
|
|
88
|
+
)
|
|
86
89
|
try:
|
|
87
90
|
app_serializers = importlib.import_module(
|
|
88
|
-
f"{self.app}.
|
|
89
|
-
if not self.is_autocomplete
|
|
90
|
-
else f"{self.app}.autocomplete_serializers"
|
|
91
|
+
f"{self.app}.{module_name}"
|
|
91
92
|
)
|
|
92
|
-
except:
|
|
93
|
+
except ImportError:
|
|
93
94
|
return
|
|
94
95
|
|
|
95
|
-
|
|
96
|
-
app_serializers,
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
found += 1
|
|
101
|
-
|
|
102
|
-
if (
|
|
103
|
-
self.serializer_list_class
|
|
104
|
-
and app_serializer_name == self.serializer_list_class.__name__
|
|
105
|
-
):
|
|
106
|
-
self.serializer_list_class = app_serializer
|
|
107
|
-
found += 1
|
|
96
|
+
self.serializer_class = getattr(
|
|
97
|
+
app_serializers,
|
|
98
|
+
self.serializer_class.__name__,
|
|
99
|
+
self.serializer_class,
|
|
100
|
+
)
|
|
108
101
|
|
|
109
|
-
|
|
110
|
-
|
|
102
|
+
# The list serializer is optional: list/excel default to the general
|
|
103
|
+
# serializer, and an app-defined ``XListSerializer`` is honored when
|
|
104
|
+
# the viewset does not point at one explicitly.
|
|
105
|
+
list_name = (
|
|
106
|
+
self.serializer_list_class.__name__
|
|
107
|
+
if self.serializer_list_class is not None
|
|
108
|
+
else utils.list_serializer_name(self.serializer_class.__name__)
|
|
109
|
+
)
|
|
110
|
+
resolved_list_class = getattr(app_serializers, list_name, None)
|
|
111
|
+
if resolved_list_class is not None:
|
|
112
|
+
self.serializer_list_class = resolved_list_class
|
|
111
113
|
|
|
112
114
|
def get_schema_serializer_class(self):
|
|
113
115
|
"""Serializer describing the model shape (OPTIONS/TS generation).
|
|
114
116
|
|
|
115
117
|
Always the canonical ``serializer_class``: ``get_serializer_class`` may
|
|
116
|
-
return
|
|
117
|
-
but the schema must not change with the action.
|
|
118
|
+
return an explicit list serializer or a ``Meta.list_exclude`` projection
|
|
119
|
+
for ``list``/``excel``, but the schema must not change with the action.
|
|
118
120
|
"""
|
|
119
121
|
return self.serializer_class
|
|
120
122
|
|
|
121
|
-
def
|
|
122
|
-
"""
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
123
|
+
def get_list_serializer_class(self):
|
|
124
|
+
"""Serializer used by ``list``/``excel``.
|
|
125
|
+
|
|
126
|
+
An explicit ``serializer_list_class`` wins; otherwise a
|
|
127
|
+
``Meta.list_exclude`` projection of the general serializer is used; when
|
|
128
|
+
neither exists the general serializer itself serves the list.
|
|
129
|
+
"""
|
|
130
|
+
if self.serializer_list_class is not None:
|
|
127
131
|
return self.serializer_list_class
|
|
132
|
+
return (
|
|
133
|
+
serializers.list_serializer_for(self.serializer_class)
|
|
134
|
+
or self.serializer_class
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
def get_serializer_class(self):
|
|
138
|
+
"""Use the list serializer for ``list``/``excel``, if one applies."""
|
|
139
|
+
if getattr(self, "action", None) in ("list", "excel"):
|
|
140
|
+
return self.get_list_serializer_class()
|
|
128
141
|
return super().get_serializer_class()
|
|
129
142
|
|
|
130
143
|
def resolve_filter_callback(self, callback_attr):
|
|
@@ -537,8 +550,14 @@ class DRFReactBySchemaBaseModelViewSet(
|
|
|
537
550
|
self, list(serializer_fields.keys())
|
|
538
551
|
)
|
|
539
552
|
|
|
540
|
-
if not self.search_fields:
|
|
541
|
-
|
|
553
|
+
if not self.search_fields and target_fields:
|
|
554
|
+
# Relation-terminated paths have no ``icontains`` lookup; DRF
|
|
555
|
+
# would raise on every ``?search=``. Ordering them is valid.
|
|
556
|
+
self.search_fields = [
|
|
557
|
+
target_field
|
|
558
|
+
for target_field in target_fields
|
|
559
|
+
if utils.is_searchable_model_field(self, target_field)
|
|
560
|
+
]
|
|
542
561
|
if not self.ordering_fields:
|
|
543
562
|
self.ordering_fields = target_fields
|
|
544
563
|
|
|
@@ -791,9 +810,6 @@ for app in settings["APPS"]:
|
|
|
791
810
|
"serializer_class": getattr(
|
|
792
811
|
serializers, f"{model_name}Serializer"
|
|
793
812
|
),
|
|
794
|
-
"serializer_list_class": getattr(
|
|
795
|
-
serializers, f"{model_name}ListSerializer"
|
|
796
|
-
),
|
|
797
813
|
},
|
|
798
814
|
)
|
|
799
815
|
globals()[viewset_name] = generated_class
|
|
@@ -824,11 +840,6 @@ for app in settings["APPS"]:
|
|
|
824
840
|
"serializer_class": getattr(
|
|
825
841
|
serializers, f"{related_model_name}Serializer"
|
|
826
842
|
),
|
|
827
|
-
"serializer_list_class": getattr(
|
|
828
|
-
serializers,
|
|
829
|
-
f"{related_model_name}ListSerializer",
|
|
830
|
-
None,
|
|
831
|
-
),
|
|
832
843
|
"related_name": field.name,
|
|
833
844
|
"many": True,
|
|
834
845
|
"parent_model": model,
|
{drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: drf-react-by-schema
|
|
3
|
-
Version: 0.28.
|
|
3
|
+
Version: 0.28.2
|
|
4
4
|
Summary: A Django app to easily integrate with React from models.
|
|
5
5
|
Home-page: https://gitlab.com/eita/drf-react-by-schema/drf-react-by-schema-django
|
|
6
6
|
Author: Cooperativa EITA
|
|
@@ -115,6 +115,43 @@ single release line. The supported combinations are exercised in CI (see
|
|
|
115
115
|
drf-react-by-schema
|
|
116
116
|
drf-excel<2.6
|
|
117
117
|
|
|
118
|
+
Django 3.2 is kept as **legacy support**: the 3.2-only pin above and its CI job
|
|
119
|
+
will be dropped when the supported floor moves to 4.2. New code should target
|
|
120
|
+
4.2-5.2; Django-3.2-only branches must stay isolated so the migration is a single
|
|
121
|
+
clean-up pass.
|
|
122
|
+
|
|
123
|
+
Accent-insensitive search
|
|
124
|
+
-------------------------
|
|
125
|
+
On PostgreSQL with the ``unaccent`` extension enabled (and
|
|
126
|
+
``django.contrib.postgres`` in ``INSTALLED_APPS``), replace DRF's search filter
|
|
127
|
+
with the package's::
|
|
128
|
+
|
|
129
|
+
REST_FRAMEWORK = {
|
|
130
|
+
"DEFAULT_FILTER_BACKENDS": [
|
|
131
|
+
"drf_react_by_schema.filters.DRFReactBySchemaSearchFilter",
|
|
132
|
+
"drf_react_by_schema.filters.DRFReactBySchemaOrderingFilter",
|
|
133
|
+
],
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
``unaccent`` is applied only to text fields; dates, numbers and booleans keep
|
|
137
|
+
DRF's default lookup, because ``unaccent`` raises on them.
|
|
138
|
+
|
|
139
|
+
The filter is **opt-in**: apps that keep DRF's ``SearchFilter`` are unaffected
|
|
140
|
+
and do not need the extension. It probes the backend, ``INSTALLED_APPS`` and the
|
|
141
|
+
database extension once per connection alias. If the extension is missing — or
|
|
142
|
+
the database is not PostgreSQL, or ``django.contrib.postgres`` is not installed
|
|
143
|
+
— it falls back to DRF's normal lookups: search keeps working (accent-sensitive)
|
|
144
|
+
instead of raising ``FieldError``. Call
|
|
145
|
+
``drf_react_by_schema.filters.reset_unaccent_cache()`` if the extension is
|
|
146
|
+
installed while the process is running.
|
|
147
|
+
|
|
148
|
+
To create the extension, add a migration in the consuming app::
|
|
149
|
+
|
|
150
|
+
from django.contrib.postgres.operations import UnaccentExtension
|
|
151
|
+
|
|
152
|
+
class Migration(migrations.Migration):
|
|
153
|
+
operations = [UnaccentExtension()]
|
|
154
|
+
|
|
118
155
|
Optional Dependencies
|
|
119
156
|
---------------------
|
|
120
157
|
For enhanced caching capabilities in the automatically generated viewsets, you can install optional dependencies:
|
|
@@ -252,6 +289,39 @@ Quick start
|
|
|
252
289
|
|
|
253
290
|
You can customize ``viewsets`` and ``serializers`` to annotate other attributes.
|
|
254
291
|
|
|
292
|
+
List vs. detail serializers
|
|
293
|
+
---------------------------
|
|
294
|
+
The generated ``XSerializer`` is the canonical serializer and serves both ``retrieve``
|
|
295
|
+
and ``list``/``excel``. When a detail-only field would be expensive in lists, declare it
|
|
296
|
+
in ``Meta.list_exclude`` on the app's general serializer:
|
|
297
|
+
|
|
298
|
+
.. code-block:: python
|
|
299
|
+
|
|
300
|
+
from drf_react_by_schema.serializers import (
|
|
301
|
+
ClienteSerializer as BaseClienteSerializer,
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
class ClienteSerializer(BaseClienteSerializer):
|
|
305
|
+
contratacao = drf_serializers.SerializerMethodField() # detail only
|
|
306
|
+
nome = drf_serializers.CharField(read_only=True) # both
|
|
307
|
+
|
|
308
|
+
class Meta(BaseClienteSerializer.Meta):
|
|
309
|
+
list_exclude = ("contratacao",)
|
|
310
|
+
|
|
311
|
+
The package derives (and caches) a list projection that keeps every other field,
|
|
312
|
+
including future additions to the general serializer. ``list_exclude`` takes serializer
|
|
313
|
+
field names, not model ``source`` paths; ``()`` means "the list is identical to the
|
|
314
|
+
detail response". An explicit list serializer remains available as an escape hatch:
|
|
315
|
+
|
|
316
|
+
.. code-block:: python
|
|
317
|
+
|
|
318
|
+
class ClienteViewSet(GenericViewSet):
|
|
319
|
+
serializer_class = ClienteSerializer
|
|
320
|
+
serializer_list_class = ClienteListSerializer # optional, full override
|
|
321
|
+
|
|
322
|
+
``XListSerializer`` classes are no longer generated automatically: the default is the
|
|
323
|
+
general serializer, so app-added fields can never silently disappear from lists.
|
|
324
|
+
|
|
255
325
|
Global permission-aware queryset filtering
|
|
256
326
|
------------------------------------------
|
|
257
327
|
Every generated viewset already supports a ``filter_by_callback`` (and a list-only
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
from rest_framework.filters import OrderingFilter, SearchFilter
|
|
2
|
-
from . import utils
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
class DRFReactBySchemaOrderingFilter(OrderingFilter):
|
|
6
|
-
def get_ordering(self, request, queryset, view):
|
|
7
|
-
"""
|
|
8
|
-
Ordering is set by a comma delimited ?ordering=... query parameter.
|
|
9
|
-
|
|
10
|
-
The `ordering` query parameter can be overridden by setting
|
|
11
|
-
the `ordering_param` value on the OrderingFilter or by
|
|
12
|
-
specifying an `ORDERING_PARAM` value in the API settings.
|
|
13
|
-
"""
|
|
14
|
-
params = request.query_params.get(self.ordering_param)
|
|
15
|
-
if params:
|
|
16
|
-
fields_raw = [param.strip() for param in params.split(",")]
|
|
17
|
-
fields = utils.serializer_to_model_fields(view, fields_raw)
|
|
18
|
-
if fields is not None:
|
|
19
|
-
ordering = self.remove_invalid_fields(queryset, fields, view, request)
|
|
20
|
-
if ordering:
|
|
21
|
-
return ordering
|
|
22
|
-
|
|
23
|
-
# No ordering was included, or all the ordering fields were invalid
|
|
24
|
-
return self.get_default_ordering(view)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/fields/__init__.py
RENAMED
|
File without changes
|
{drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/fields/array.py
RENAMED
|
File without changes
|
{drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/fields/boolean.py
RENAMED
|
File without changes
|
{drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/fields/date.py
RENAMED
|
File without changes
|
{drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/fields/file.py
RENAMED
|
File without changes
|
{drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/fields/geometry.py
RENAMED
|
File without changes
|
{drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/fields/number.py
RENAMED
|
File without changes
|
{drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/fields/relational.py
RENAMED
|
File without changes
|
{drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/fields/serializers.py
RENAMED
|
File without changes
|
{drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/fields/text.py
RENAMED
|
File without changes
|
{drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/management/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/migrations/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/patches/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema/utils_cache.py
RENAMED
|
File without changes
|
|
File without changes
|
{drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema.egg-info/requires.txt
RENAMED
|
File without changes
|
{drf_react_by_schema-0.28.0 → drf_react_by_schema-0.28.2}/drf_react_by_schema.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|