drf-react-by-schema 0.29.0__tar.gz → 0.29.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.29.0 → drf_react_by_schema-0.29.2}/PKG-INFO +1 -1
- drf_react_by_schema-0.29.2/drf_react_by_schema/autocomplete_viewsets.py +135 -0
- {drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/metadata.py +105 -86
- {drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/serializers.py +15 -4
- {drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema.egg-info/PKG-INFO +1 -1
- {drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/setup.cfg +1 -1
- drf_react_by_schema-0.29.0/drf_react_by_schema/autocomplete_viewsets.py +0 -22
- {drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/LICENSE +0 -0
- {drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/MANIFEST.in +0 -0
- {drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/README.rst +0 -0
- {drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/__init__.py +0 -0
- {drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/admin.py +0 -0
- {drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/apps.py +0 -0
- {drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/auth.py +0 -0
- {drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/autocomplete_serializers.py +0 -0
- {drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/checks.py +0 -0
- {drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/date_utils.py +0 -0
- {drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/fields/__init__.py +0 -0
- {drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/fields/array.py +0 -0
- {drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/fields/boolean.py +0 -0
- {drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/fields/date.py +0 -0
- {drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/fields/file.py +0 -0
- {drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/fields/geometry.py +0 -0
- {drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/fields/number.py +0 -0
- {drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/fields/relational.py +0 -0
- {drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/fields/serializers.py +0 -0
- {drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/fields/text.py +0 -0
- {drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/filters.py +0 -0
- {drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/management/__init__.py +0 -0
- {drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/management/commands/__init__.py +0 -0
- {drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/management/commands/sync_react.py +0 -0
- {drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/migrations/0001_initial.py +0 -0
- {drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/migrations/__init__.py +0 -0
- {drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/mixins.py +0 -0
- {drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/models.py +0 -0
- {drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/pagination.py +0 -0
- {drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/patches/__init__.py +0 -0
- {drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/patches/drf_serializers.py +0 -0
- {drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/renderers.py +0 -0
- {drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/schema_bundle.py +0 -0
- {drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/tests.py +0 -0
- {drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/typescript_generator.py +0 -0
- {drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/urls.py +0 -0
- {drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/utils.py +0 -0
- {drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/utils_cache.py +0 -0
- {drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/views.py +0 -0
- {drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/viewsets.py +0 -0
- {drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema.egg-info/SOURCES.txt +0 -0
- {drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema.egg-info/dependency_links.txt +0 -0
- {drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema.egg-info/requires.txt +0 -0
- {drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema.egg-info/top_level.txt +0 -0
- {drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/setup.py +0 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
|
|
3
|
+
from django.apps import apps
|
|
4
|
+
from django.core.exceptions import FieldDoesNotExist
|
|
5
|
+
from rest_framework import serializers as drf_serializers
|
|
6
|
+
|
|
7
|
+
from . import settings, autocomplete_serializers, viewsets
|
|
8
|
+
from .utils import camel_to_snake, to_pascal_case
|
|
9
|
+
|
|
10
|
+
# ``settings`` is ``django.conf.settings.__dict__`` (see the package ``__init__``):
|
|
11
|
+
# ``override_settings`` clears that dict when it swaps ``_wrapped``, so the apps
|
|
12
|
+
# list is snapshotted here (as the generation loop below already implicitly is).
|
|
13
|
+
_APPS = tuple(settings["APPS"])
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
for app in _APPS:
|
|
17
|
+
for name, model in apps.all_models[app].items():
|
|
18
|
+
model_name = model.__name__
|
|
19
|
+
if isinstance(model, type):
|
|
20
|
+
viewset_name = f"{model_name}ViewSet"
|
|
21
|
+
if viewset_name not in globals():
|
|
22
|
+
generated_class = type(
|
|
23
|
+
viewset_name,
|
|
24
|
+
(viewsets.GenericAutocompleteViewSet,),
|
|
25
|
+
{
|
|
26
|
+
"model_name": model_name,
|
|
27
|
+
"app": app,
|
|
28
|
+
"serializer_class": getattr(
|
|
29
|
+
autocomplete_serializers, f"{model_name}Serializer"
|
|
30
|
+
),
|
|
31
|
+
},
|
|
32
|
+
)
|
|
33
|
+
globals()[viewset_name] = generated_class
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _is_nested_field(field):
|
|
37
|
+
"""Whether a declared serializer field exposes a related model (a relation).
|
|
38
|
+
|
|
39
|
+
Nested model serializers (FK/M2M/O2O and declared reverse relations) are
|
|
40
|
+
``BaseSerializer`` instances; ``NestedSerializerMethodField`` wraps a
|
|
41
|
+
serializer class in ``serializer_class``. Plain scalars and ``source``-based
|
|
42
|
+
``CharField``s are not relations and carry no autocomplete route.
|
|
43
|
+
"""
|
|
44
|
+
return isinstance(field, drf_serializers.BaseSerializer) or hasattr(
|
|
45
|
+
field, "serializer_class"
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _related_model(serializer_model, field_name):
|
|
50
|
+
"""The related model of ``field_name`` on ``serializer_model``, if any."""
|
|
51
|
+
try:
|
|
52
|
+
model_field = serializer_model._meta.get_field(field_name)
|
|
53
|
+
except FieldDoesNotExist:
|
|
54
|
+
return None
|
|
55
|
+
return getattr(model_field, "related_model", None)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _iter_serializer_classes():
|
|
59
|
+
"""Serializer classes whose model belongs to the configured apps.
|
|
60
|
+
|
|
61
|
+
Covers the package-generated serializers (whose forward relations are
|
|
62
|
+
auto-populated) and the app's own ``serializers`` module (which declares the
|
|
63
|
+
reverse relations rendered as nested/inline serializers).
|
|
64
|
+
"""
|
|
65
|
+
from . import serializers as generated_serializers
|
|
66
|
+
|
|
67
|
+
modules = [generated_serializers]
|
|
68
|
+
for app in _APPS:
|
|
69
|
+
try:
|
|
70
|
+
modules.append(importlib.import_module(f"{app}.serializers"))
|
|
71
|
+
except ImportError:
|
|
72
|
+
pass
|
|
73
|
+
|
|
74
|
+
seen = set()
|
|
75
|
+
for module in modules:
|
|
76
|
+
for obj in vars(module).values():
|
|
77
|
+
if not isinstance(obj, type) or obj in seen:
|
|
78
|
+
continue
|
|
79
|
+
seen.add(obj)
|
|
80
|
+
yield obj
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def generate_field_name_viewsets(serializer_classes):
|
|
84
|
+
"""Autocomplete viewsets named after the relation field they serve.
|
|
85
|
+
|
|
86
|
+
The React client asks ``/autocomplete/<field>/`` using the serializer field
|
|
87
|
+
name, but the package only registers ``/autocomplete/<model>/``. A relation
|
|
88
|
+
field whose name differs from its related model — a reverse FK/M2M/O2O with a
|
|
89
|
+
custom ``related_name`` (``projetos`` for ``Projeto``), or a forward FK named
|
|
90
|
+
differently (``responsavel`` for ``Pessoa``) — therefore has no route and had
|
|
91
|
+
to be declared by hand in the consuming app.
|
|
92
|
+
|
|
93
|
+
This derives that route from the related model's autocomplete viewset, so its
|
|
94
|
+
``serializer_class`` and configuration are inherited. An app-defined viewset
|
|
95
|
+
of the same name still wins: the app's ``autocomplete_viewsets`` module is
|
|
96
|
+
registered first and the router skips an already-registered basename.
|
|
97
|
+
"""
|
|
98
|
+
for serializer_class in serializer_classes:
|
|
99
|
+
meta = getattr(serializer_class, "Meta", None)
|
|
100
|
+
serializer_model = getattr(meta, "model", None)
|
|
101
|
+
if (
|
|
102
|
+
serializer_model is None
|
|
103
|
+
or serializer_model._meta.app_label not in _APPS
|
|
104
|
+
):
|
|
105
|
+
continue
|
|
106
|
+
|
|
107
|
+
for field_name, field in getattr(
|
|
108
|
+
serializer_class, "_declared_fields", {}
|
|
109
|
+
).items():
|
|
110
|
+
if not _is_nested_field(field):
|
|
111
|
+
continue
|
|
112
|
+
related_model = _related_model(serializer_model, field_name)
|
|
113
|
+
if (
|
|
114
|
+
related_model is None
|
|
115
|
+
or related_model._meta.app_label not in _APPS
|
|
116
|
+
):
|
|
117
|
+
continue
|
|
118
|
+
# The related model's own route already serves this name.
|
|
119
|
+
if camel_to_snake(related_model.__name__) == field_name:
|
|
120
|
+
continue
|
|
121
|
+
|
|
122
|
+
base_viewset = globals().get(f"{related_model.__name__}ViewSet")
|
|
123
|
+
if base_viewset is None:
|
|
124
|
+
continue
|
|
125
|
+
viewset_name = f"{to_pascal_case(field_name)}ViewSet"
|
|
126
|
+
if viewset_name in globals():
|
|
127
|
+
continue
|
|
128
|
+
globals()[viewset_name] = type(
|
|
129
|
+
viewset_name,
|
|
130
|
+
(base_viewset,),
|
|
131
|
+
{"__module__": __name__},
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
generate_field_name_viewsets(_iter_serializer_classes())
|
|
@@ -64,66 +64,19 @@ class Metadata(SimpleMetadata):
|
|
|
64
64
|
return None
|
|
65
65
|
return getter()
|
|
66
66
|
|
|
67
|
-
def
|
|
68
|
-
|
|
69
|
-
if serializer_class is None:
|
|
70
|
-
metadata = super().determine_metadata(request, view)
|
|
71
|
-
else:
|
|
72
|
-
with _PinnedSerializerClass(view, serializer_class):
|
|
73
|
-
metadata = super().determine_metadata(request, view)
|
|
74
|
-
|
|
75
|
-
if not metadata.get("actions", {}).get("POST") or not hasattr(
|
|
76
|
-
view, "get_serializer"
|
|
77
|
-
):
|
|
78
|
-
return metadata
|
|
79
|
-
|
|
80
|
-
try:
|
|
81
|
-
serializer = (
|
|
82
|
-
serializer_class() if serializer_class else view.get_serializer()
|
|
83
|
-
)
|
|
84
|
-
except (AttributeError, TypeError, ValueError):
|
|
85
|
-
return metadata
|
|
86
|
-
|
|
87
|
-
model = getattr(getattr(serializer, "Meta", None), "model", None)
|
|
88
|
-
if not model:
|
|
89
|
-
return metadata
|
|
90
|
-
|
|
91
|
-
metadata.update(
|
|
92
|
-
{
|
|
93
|
-
"verbose_name": getattr(model._meta, "verbose_name", None),
|
|
94
|
-
"verbose_name_plural": getattr(
|
|
95
|
-
model._meta, "verbose_name_plural", None
|
|
96
|
-
),
|
|
97
|
-
}
|
|
98
|
-
)
|
|
99
|
-
|
|
100
|
-
try:
|
|
101
|
-
serializer_fields = serializer.get_fields()
|
|
102
|
-
except (AttributeError, TypeError, ValueError):
|
|
103
|
-
return metadata
|
|
104
|
-
|
|
105
|
-
# Type derived method fields and expand nested serializers; the client
|
|
106
|
-
# reads ``derived`` to tell them apart from editable relations.
|
|
107
|
-
self._apply_serializer_field_info(metadata["actions"]["POST"], serializer)
|
|
108
|
-
|
|
109
|
-
for serializer_field_name, serializer_field in serializer_fields.items():
|
|
110
|
-
if serializer_field_name not in metadata["actions"]["POST"]:
|
|
111
|
-
continue
|
|
112
|
-
|
|
113
|
-
if (
|
|
114
|
-
isinstance(serializer_field, serializers.CharField)
|
|
115
|
-
and serializer_field.style
|
|
116
|
-
):
|
|
117
|
-
metadata["actions"]["POST"][serializer_field_name][
|
|
118
|
-
"style"
|
|
119
|
-
] = serializer_field.style
|
|
67
|
+
def _apply_model_field_info(self, field_infos, serializer, model):
|
|
68
|
+
"""Enrich a schema's field infos with the model field metadata.
|
|
120
69
|
|
|
70
|
+
Applied to the top-level POST schema and, recursively, to every nested
|
|
71
|
+
serializer's children, so an inline formset's fields carry the same keys
|
|
72
|
+
(``conditional_visible``, ``ui_required``, ``model_required``, ...) as the
|
|
73
|
+
standalone model schema.
|
|
74
|
+
"""
|
|
75
|
+
serializer_fields = serializer.get_fields()
|
|
121
76
|
for field in model._meta.get_fields():
|
|
122
77
|
field_name = getattr(field, "name", None)
|
|
123
78
|
|
|
124
|
-
if not field_name or field_name not in
|
|
125
|
-
"POST", {}
|
|
126
|
-
):
|
|
79
|
+
if not field_name or field_name not in field_infos:
|
|
127
80
|
continue
|
|
128
81
|
|
|
129
82
|
serializer_field = serializer_fields.get(field_name)
|
|
@@ -153,7 +106,7 @@ class Metadata(SimpleMetadata):
|
|
|
153
106
|
)
|
|
154
107
|
|
|
155
108
|
if is_now_default:
|
|
156
|
-
|
|
109
|
+
field_infos[field_name]["model_default"] = (
|
|
157
110
|
"{dateTimeNow}" if is_datetime else "{dateNow}"
|
|
158
111
|
)
|
|
159
112
|
elif default_value:
|
|
@@ -162,25 +115,25 @@ class Metadata(SimpleMetadata):
|
|
|
162
115
|
model_instance = field.related_model.objects.get(
|
|
163
116
|
pk=default_value
|
|
164
117
|
)
|
|
165
|
-
|
|
118
|
+
field_infos[field_name]["model_default"] = (
|
|
166
119
|
serializer_field.to_representation(model_instance)
|
|
167
120
|
)
|
|
168
121
|
except field.related_model.DoesNotExist:
|
|
169
|
-
|
|
122
|
+
field_infos[field_name][
|
|
170
123
|
"model_default_error"
|
|
171
124
|
] = "Related object not found"
|
|
172
125
|
pass
|
|
173
126
|
else:
|
|
174
|
-
|
|
127
|
+
field_infos[field_name][
|
|
175
128
|
"model_default"
|
|
176
129
|
] = default_value
|
|
177
130
|
elif serializer_field and serializer_field.default:
|
|
178
131
|
if isinstance(serializer_field.default, (int, str)):
|
|
179
|
-
|
|
132
|
+
field_infos[field_name][
|
|
180
133
|
"model_default"
|
|
181
134
|
] = serializer_field.default
|
|
182
135
|
elif isinstance(serializer_field.default, CurrentUserDefault):
|
|
183
|
-
|
|
136
|
+
field_infos[field_name][
|
|
184
137
|
"model_default"
|
|
185
138
|
] = "{currentUser}"
|
|
186
139
|
|
|
@@ -197,7 +150,7 @@ class Metadata(SimpleMetadata):
|
|
|
197
150
|
}
|
|
198
151
|
)
|
|
199
152
|
if len(validators_regex) > 0:
|
|
200
|
-
|
|
153
|
+
field_infos[field_name][
|
|
201
154
|
"validators_regex"
|
|
202
155
|
] = validators_regex
|
|
203
156
|
|
|
@@ -206,11 +159,11 @@ class Metadata(SimpleMetadata):
|
|
|
206
159
|
serializer_fields[field_name],
|
|
207
160
|
serializers.ManyRelatedField,
|
|
208
161
|
):
|
|
209
|
-
|
|
162
|
+
field_infos[field_name]["required"] = False
|
|
210
163
|
|
|
211
164
|
# Add ui_required to OPTIONS:
|
|
212
165
|
is_ui_required = getattr(field, "ui_required", False)
|
|
213
|
-
|
|
166
|
+
field_infos[field_name]["ui_required"] = is_ui_required
|
|
214
167
|
|
|
215
168
|
# Add model_required to OPTIONS:
|
|
216
169
|
# Reverse relations (a declared reverse-FK/M2M/O2O nested list) never
|
|
@@ -231,19 +184,19 @@ class Metadata(SimpleMetadata):
|
|
|
231
184
|
)
|
|
232
185
|
)
|
|
233
186
|
)
|
|
234
|
-
|
|
187
|
+
field_infos[field_name]["model_required"] = is_required
|
|
235
188
|
|
|
236
189
|
# Add pattern_format to OPTIONS:
|
|
237
190
|
pattern_format = get_pattern_format(getattr(field, "pattern_format", None))
|
|
238
191
|
if pattern_format:
|
|
239
|
-
|
|
192
|
+
field_infos[field_name][
|
|
240
193
|
"pattern_format"
|
|
241
194
|
] = pattern_format
|
|
242
195
|
|
|
243
196
|
# Add help_text to OPTIONS:
|
|
244
197
|
help_text = getattr(field, "help_text", None)
|
|
245
198
|
if help_text:
|
|
246
|
-
|
|
199
|
+
field_infos[field_name]["help_text"] = help_text
|
|
247
200
|
|
|
248
201
|
# Add related model is editable to OPTIONS:
|
|
249
202
|
# Non-editable (editable=False) model fields are never written by the
|
|
@@ -252,57 +205,57 @@ class Metadata(SimpleMetadata):
|
|
|
252
205
|
if related_editable is not None:
|
|
253
206
|
if not getattr(field, "editable", True):
|
|
254
207
|
related_editable = False
|
|
255
|
-
|
|
208
|
+
field_infos[field_name][
|
|
256
209
|
"related_editable"
|
|
257
210
|
] = related_editable
|
|
258
211
|
|
|
259
212
|
# Add DecimalField decimal_places in OPTIONS:
|
|
260
213
|
decimal_places = getattr(field, "decimal_places", None)
|
|
261
214
|
if decimal_places is not None:
|
|
262
|
-
|
|
215
|
+
field_infos[field_name][
|
|
263
216
|
"decimal_places"
|
|
264
217
|
] = decimal_places
|
|
265
218
|
|
|
266
219
|
# Add DecimalField max_digits in OPTIONS:
|
|
267
220
|
max_digits = getattr(field, "max_digits", None)
|
|
268
221
|
if max_digits is not None:
|
|
269
|
-
|
|
222
|
+
field_infos[field_name]["max_digits"] = max_digits
|
|
270
223
|
|
|
271
224
|
# Add DecimalField is_currency in OPTIONS:
|
|
272
225
|
is_currency = getattr(field, "is_currency", None)
|
|
273
226
|
if is_currency is not None:
|
|
274
|
-
|
|
227
|
+
field_infos[field_name]["is_currency"] = is_currency
|
|
275
228
|
|
|
276
229
|
# Add DecimalField prefix in OPTIONS:
|
|
277
230
|
prefix = getattr(field, "prefix", None)
|
|
278
231
|
if prefix is not None:
|
|
279
|
-
|
|
232
|
+
field_infos[field_name]["prefix"] = prefix
|
|
280
233
|
|
|
281
234
|
# Add DecimalField suffix in OPTIONS:
|
|
282
235
|
suffix = getattr(field, "suffix", None)
|
|
283
236
|
if suffix is not None:
|
|
284
|
-
|
|
237
|
+
field_infos[field_name]["suffix"] = suffix
|
|
285
238
|
|
|
286
239
|
# Add DateField views in OPTIONS:
|
|
287
240
|
views = getattr(field, "views", None)
|
|
288
241
|
if views is not None:
|
|
289
|
-
|
|
242
|
+
field_infos[field_name]["date_views"] = views
|
|
290
243
|
|
|
291
244
|
if field_name in serializer_fields and (
|
|
292
245
|
isinstance(
|
|
293
246
|
serializer_fields[field_name],
|
|
294
247
|
(serializers.ManyRelatedField, serializers.ListSerializer),
|
|
295
248
|
)
|
|
296
|
-
or
|
|
249
|
+
or field_infos[field_name]["type"] == "list"
|
|
297
250
|
):
|
|
298
|
-
|
|
251
|
+
field_infos[field_name]["many"] = True
|
|
299
252
|
|
|
300
253
|
# Mirror the parent's required onto the child of list/array fields.
|
|
301
254
|
# DRF's SimpleMetadata always reports the child serializer field as
|
|
302
255
|
# required (e.g. a CharField child of a ListField), which is wrong
|
|
303
256
|
# when the parent ArrayField itself is optional (blank=True/null=True).
|
|
304
257
|
# The child "required" should follow the parent's "required".
|
|
305
|
-
field_info =
|
|
258
|
+
field_info = field_infos[field_name]
|
|
306
259
|
if field_info.get("type") == "list" and isinstance(
|
|
307
260
|
field_info.get("child"), dict
|
|
308
261
|
):
|
|
@@ -313,18 +266,18 @@ class Metadata(SimpleMetadata):
|
|
|
313
266
|
serializer_fields[field_name],
|
|
314
267
|
serializers.ManyRelatedField,
|
|
315
268
|
):
|
|
316
|
-
|
|
269
|
+
field_infos[field_name]["required"] = False
|
|
317
270
|
|
|
318
271
|
# Print multiline in OPTIONS:
|
|
319
272
|
if isinstance(field, models.TextField):
|
|
320
|
-
|
|
273
|
+
field_infos[field_name]["model_multiline"] = True
|
|
321
274
|
# Rich-text fields store HTML and get a WYSIWYG widget.
|
|
322
275
|
if getattr(field, "rich_text", False):
|
|
323
|
-
|
|
276
|
+
field_infos[field_name]["model_html"] = True
|
|
324
277
|
|
|
325
278
|
# Force slug to be read_only:
|
|
326
279
|
if field_name == SLUG_FIELD_NAME:
|
|
327
|
-
|
|
280
|
+
field_infos[field_name].update(
|
|
328
281
|
{
|
|
329
282
|
"required": False,
|
|
330
283
|
"read_only": True,
|
|
@@ -334,22 +287,79 @@ class Metadata(SimpleMetadata):
|
|
|
334
287
|
# Add max_file_size for ImageField and FileField in OPTIONS:
|
|
335
288
|
max_file_size = getattr(field, "max_file_size", None)
|
|
336
289
|
if max_file_size is not None:
|
|
337
|
-
|
|
290
|
+
field_infos[field_name]["max_file_size"] = max_file_size
|
|
338
291
|
|
|
339
292
|
# Add allowed_mime_types for ImageField and FileField in OPTIONS:
|
|
340
293
|
allowed_mime_types = getattr(field, "allowed_mime_types", None)
|
|
341
294
|
if allowed_mime_types is not None:
|
|
342
|
-
|
|
295
|
+
field_infos[field_name][
|
|
343
296
|
"allowed_mime_types"
|
|
344
297
|
] = allowed_mime_types
|
|
345
298
|
|
|
346
299
|
# Add help_text to OPTIONS:
|
|
347
300
|
conditional_visible = getattr(field, "conditional_visible", None)
|
|
348
301
|
if conditional_visible:
|
|
349
|
-
|
|
302
|
+
field_infos[field_name][
|
|
350
303
|
"conditional_visible"
|
|
351
304
|
] = conditional_visible
|
|
352
305
|
|
|
306
|
+
|
|
307
|
+
def determine_metadata(self, request, view):
|
|
308
|
+
serializer_class = self.get_schema_serializer_class(view)
|
|
309
|
+
if serializer_class is None:
|
|
310
|
+
metadata = super().determine_metadata(request, view)
|
|
311
|
+
else:
|
|
312
|
+
with _PinnedSerializerClass(view, serializer_class):
|
|
313
|
+
metadata = super().determine_metadata(request, view)
|
|
314
|
+
|
|
315
|
+
if not metadata.get("actions", {}).get("POST") or not hasattr(
|
|
316
|
+
view, "get_serializer"
|
|
317
|
+
):
|
|
318
|
+
return metadata
|
|
319
|
+
|
|
320
|
+
try:
|
|
321
|
+
serializer = (
|
|
322
|
+
serializer_class() if serializer_class else view.get_serializer()
|
|
323
|
+
)
|
|
324
|
+
except (AttributeError, TypeError, ValueError):
|
|
325
|
+
return metadata
|
|
326
|
+
|
|
327
|
+
model = getattr(getattr(serializer, "Meta", None), "model", None)
|
|
328
|
+
if not model:
|
|
329
|
+
return metadata
|
|
330
|
+
|
|
331
|
+
metadata.update(
|
|
332
|
+
{
|
|
333
|
+
"verbose_name": getattr(model._meta, "verbose_name", None),
|
|
334
|
+
"verbose_name_plural": getattr(
|
|
335
|
+
model._meta, "verbose_name_plural", None
|
|
336
|
+
),
|
|
337
|
+
}
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
try:
|
|
341
|
+
serializer_fields = serializer.get_fields()
|
|
342
|
+
except (AttributeError, TypeError, ValueError):
|
|
343
|
+
return metadata
|
|
344
|
+
|
|
345
|
+
# Type derived method fields and expand nested serializers; the client
|
|
346
|
+
# reads ``derived`` to tell them apart from editable relations.
|
|
347
|
+
self._apply_serializer_field_info(metadata["actions"]["POST"], serializer)
|
|
348
|
+
|
|
349
|
+
for serializer_field_name, serializer_field in serializer_fields.items():
|
|
350
|
+
if serializer_field_name not in metadata["actions"]["POST"]:
|
|
351
|
+
continue
|
|
352
|
+
|
|
353
|
+
if (
|
|
354
|
+
isinstance(serializer_field, serializers.CharField)
|
|
355
|
+
and serializer_field.style
|
|
356
|
+
):
|
|
357
|
+
metadata["actions"]["POST"][serializer_field_name][
|
|
358
|
+
"style"
|
|
359
|
+
] = serializer_field.style
|
|
360
|
+
|
|
361
|
+
self._apply_model_field_info(metadata["actions"]["POST"], serializer, model)
|
|
362
|
+
|
|
353
363
|
# PostGIS geometry fields have no DRF type that SimpleMetadata can emit,
|
|
354
364
|
# so set the drf-react-by-schema geometry types directly. Only applied
|
|
355
365
|
# when the database engine is PostGIS.
|
|
@@ -412,9 +422,18 @@ class Metadata(SimpleMetadata):
|
|
|
412
422
|
self._apply_inline_object_info(info, field, info["children"])
|
|
413
423
|
|
|
414
424
|
def _nested_serializer_info(self, serializer, parent_model=None):
|
|
415
|
-
"""Schema info for a nested serializer, with its derived fields typed.
|
|
425
|
+
"""Schema info for a nested serializer, with its derived fields typed.
|
|
426
|
+
|
|
427
|
+
The model-field enrichment (``conditional_visible``, ``ui_required``,
|
|
428
|
+
``model_required``, ...) is applied here too, so an inline formset's child
|
|
429
|
+
fields carry the same keys as the standalone model schema — the widget
|
|
430
|
+
reads ``conditional_visible`` from these nested children.
|
|
431
|
+
"""
|
|
416
432
|
info = self.get_serializer_info(serializer)
|
|
417
433
|
self._apply_serializer_field_info(info, serializer)
|
|
434
|
+
nested_model = getattr(getattr(serializer, "Meta", None), "model", None)
|
|
435
|
+
if nested_model is not None:
|
|
436
|
+
self._apply_model_field_info(info, serializer, nested_model)
|
|
418
437
|
if parent_model is not None:
|
|
419
438
|
self._mark_auto_managed(info, serializer, parent_model)
|
|
420
439
|
return info
|
{drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/serializers.py
RENAMED
|
@@ -600,15 +600,26 @@ class GenericSerializer(
|
|
|
600
600
|
Binding the row's existing instance for the duration of the validation
|
|
601
601
|
lets DRF's validators exclude it — the write itself still applies the
|
|
602
602
|
implicit diff (see ``utils.update_nested_relations``).
|
|
603
|
+
|
|
604
|
+
A row that targets an existing object is also a *partial* write: the
|
|
605
|
+
client sends only the fields it changed (an upload-first file is omitted
|
|
606
|
+
when it is unchanged), so a field absent from the payload keeps its stored
|
|
607
|
+
value and must be skipped, not required. DRF reads ``partial`` from the
|
|
608
|
+
root serializer (``Field.validate_empty_values``), so it is toggled there
|
|
609
|
+
for the duration of this row's validation.
|
|
603
610
|
"""
|
|
604
611
|
nested_instance = self._nested_row_instance(data)
|
|
605
|
-
if nested_instance is
|
|
606
|
-
|
|
612
|
+
if nested_instance is None:
|
|
613
|
+
return super().run_validation(data)
|
|
614
|
+
self.instance = nested_instance
|
|
615
|
+
root = self.root
|
|
616
|
+
was_partial = root.partial
|
|
617
|
+
root.partial = True
|
|
607
618
|
try:
|
|
608
619
|
return super().run_validation(data)
|
|
609
620
|
finally:
|
|
610
|
-
|
|
611
|
-
|
|
621
|
+
root.partial = was_partial
|
|
622
|
+
self.instance = None
|
|
612
623
|
|
|
613
624
|
def _nested_write_context(self):
|
|
614
625
|
"""``(parent_serializer, back_fk)`` when validating a nested-write row.
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
from django.apps import apps
|
|
2
|
-
|
|
3
|
-
from . import settings, autocomplete_serializers, viewsets
|
|
4
|
-
|
|
5
|
-
for app in settings["APPS"]:
|
|
6
|
-
for name, model in apps.all_models[app].items():
|
|
7
|
-
model_name = model.__name__
|
|
8
|
-
if isinstance(model, type):
|
|
9
|
-
viewset_name = f"{model_name}ViewSet"
|
|
10
|
-
if viewset_name not in globals():
|
|
11
|
-
generated_class = type(
|
|
12
|
-
viewset_name,
|
|
13
|
-
(viewsets.GenericAutocompleteViewSet,),
|
|
14
|
-
{
|
|
15
|
-
"model_name": model_name,
|
|
16
|
-
"app": app,
|
|
17
|
-
"serializer_class": getattr(
|
|
18
|
-
autocomplete_serializers, f"{model_name}Serializer"
|
|
19
|
-
),
|
|
20
|
-
},
|
|
21
|
-
)
|
|
22
|
-
globals()[viewset_name] = generated_class
|
|
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.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/fields/__init__.py
RENAMED
|
File without changes
|
{drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/fields/array.py
RENAMED
|
File without changes
|
{drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/fields/boolean.py
RENAMED
|
File without changes
|
{drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/fields/date.py
RENAMED
|
File without changes
|
{drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/fields/file.py
RENAMED
|
File without changes
|
{drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/fields/geometry.py
RENAMED
|
File without changes
|
{drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/fields/number.py
RENAMED
|
File without changes
|
{drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/fields/relational.py
RENAMED
|
File without changes
|
{drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/fields/serializers.py
RENAMED
|
File without changes
|
{drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/fields/text.py
RENAMED
|
File without changes
|
|
File without changes
|
{drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/management/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.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.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/patches/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/schema_bundle.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema/utils_cache.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema.egg-info/requires.txt
RENAMED
|
File without changes
|
{drf_react_by_schema-0.29.0 → drf_react_by_schema-0.29.2}/drf_react_by_schema.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|