django-camomilla-cms 6.0.0b2__py2.py3-none-any.whl → 6.0.0b4__py2.py3-none-any.whl
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.
- camomilla/__init__.py +1 -1
- camomilla/apps.py +3 -0
- camomilla/contrib/modeltranslation/hvad_migration.py +1 -2
- camomilla/contrib/rest_framework/serializer.py +31 -1
- camomilla/dynamic_pages_urls.py +7 -1
- camomilla/fields/json.py +12 -9
- camomilla/management/commands/regenerate_thumbnails.py +0 -1
- camomilla/model_api.py +6 -4
- camomilla/models/__init__.py +5 -5
- camomilla/models/article.py +0 -1
- camomilla/models/media.py +1 -2
- camomilla/models/menu.py +44 -42
- camomilla/models/mixins/__init__.py +0 -1
- camomilla/models/page.py +66 -32
- camomilla/openapi/schema.py +27 -0
- camomilla/parsers.py +0 -1
- camomilla/serializers/fields/json.py +7 -75
- camomilla/serializers/mixins/__init__.py +16 -2
- camomilla/serializers/page.py +47 -0
- camomilla/serializers/user.py +2 -3
- camomilla/serializers/utils.py +22 -17
- camomilla/settings.py +21 -1
- camomilla/storages/optimize.py +1 -1
- camomilla/structured/__init__.py +90 -75
- camomilla/structured/cache.py +193 -0
- camomilla/structured/fields.py +132 -275
- camomilla/structured/models.py +45 -138
- camomilla/structured/utils.py +114 -0
- camomilla/templatetags/camomilla_filters.py +0 -1
- camomilla/theme/__init__.py +1 -1
- camomilla/theme/admin.py +96 -0
- camomilla/theme/apps.py +12 -1
- camomilla/translation.py +4 -2
- camomilla/urls.py +13 -6
- camomilla/utils/__init__.py +1 -1
- camomilla/utils/getters.py +11 -1
- camomilla/utils/templates.py +2 -2
- camomilla/utils/translation.py +9 -6
- camomilla/views/__init__.py +1 -1
- camomilla/views/articles.py +0 -1
- camomilla/views/contents.py +0 -1
- camomilla/views/decorators.py +26 -0
- camomilla/views/medias.py +1 -2
- camomilla/views/menus.py +45 -1
- camomilla/views/pages.py +13 -1
- camomilla/views/tags.py +0 -1
- camomilla/views/users.py +0 -2
- {django_camomilla_cms-6.0.0b2.dist-info → django_camomilla_cms-6.0.0b4.dist-info}/METADATA +4 -3
- {django_camomilla_cms-6.0.0b2.dist-info → django_camomilla_cms-6.0.0b4.dist-info}/RECORD +53 -49
- tests/test_api.py +1 -0
- {django_camomilla_cms-6.0.0b2.dist-info → django_camomilla_cms-6.0.0b4.dist-info}/LICENSE +0 -0
- {django_camomilla_cms-6.0.0b2.dist-info → django_camomilla_cms-6.0.0b4.dist-info}/WHEEL +0 -0
- {django_camomilla_cms-6.0.0b2.dist-info → django_camomilla_cms-6.0.0b4.dist-info}/top_level.txt +0 -0
@@ -1,15 +1,6 @@
|
|
1
1
|
from rest_framework import serializers
|
2
2
|
from rest_framework.utils import model_meta
|
3
3
|
|
4
|
-
from camomilla.serializers.utils import build_standard_model_serializer
|
5
|
-
from camomilla.structured.fields import (
|
6
|
-
EmbeddedField,
|
7
|
-
ForeignKey,
|
8
|
-
ForeignKeyList,
|
9
|
-
ListField,
|
10
|
-
)
|
11
|
-
from camomilla.structured.models import Model
|
12
|
-
|
13
4
|
|
14
5
|
class StructuredJSONField(serializers.JSONField):
|
15
6
|
def __init__(self, **kwargs):
|
@@ -21,72 +12,13 @@ class StructuredJSONField(serializers.JSONField):
|
|
21
12
|
info = model_meta.get_field_info(parent.Meta.model)
|
22
13
|
field = info.fields[field_name]
|
23
14
|
self.schema = field.schema
|
15
|
+
self.many = field.many
|
16
|
+
self.json_schema = field.schema.json_schema()
|
24
17
|
super().bind(field_name, parent)
|
25
18
|
|
26
|
-
def to_internal_value(self, data):
|
27
|
-
if isinstance(data, dict):
|
28
|
-
data = to_internal_model_value(self.schema, data)
|
29
|
-
elif isinstance(data, list):
|
30
|
-
data = [to_internal_model_value(self.schema, v) for v in data]
|
31
|
-
return super().to_internal_value(data)
|
32
|
-
|
33
19
|
def to_representation(self, instance):
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
for _, name, field in schema.iterate_with_name():
|
40
|
-
value = field.__get__(schema)
|
41
|
-
if value is None:
|
42
|
-
continue
|
43
|
-
elif isinstance(field, ForeignKey):
|
44
|
-
serializer = build_standard_model_serializer(field.model, depth=1)
|
45
|
-
data = serializer(instance=value).data
|
46
|
-
elif isinstance(field, ForeignKeyList):
|
47
|
-
serializer = build_standard_model_serializer(field.inner_model, depth=1)
|
48
|
-
data = serializer(instance=value, many=True).data
|
49
|
-
elif isinstance(field, EmbeddedField):
|
50
|
-
data = expanded_model_rapresentation(value)
|
51
|
-
elif isinstance(field, ListField):
|
52
|
-
data = []
|
53
|
-
for v in value:
|
54
|
-
if isinstance(v, Model):
|
55
|
-
v = expanded_model_rapresentation(v)
|
56
|
-
data.append(v)
|
57
|
-
else:
|
58
|
-
data = field.to_struct(value)
|
59
|
-
stack[name] = data
|
60
|
-
return stack
|
61
|
-
|
62
|
-
|
63
|
-
def expanded_rapresentation(value):
|
64
|
-
if isinstance(value, dict):
|
65
|
-
value = {k: expanded_rapresentation(v) for k, v in value.items()}
|
66
|
-
elif isinstance(value, list):
|
67
|
-
value = [expanded_rapresentation(v) for v in value]
|
68
|
-
elif isinstance(value, Model):
|
69
|
-
value = expanded_model_rapresentation(value)
|
70
|
-
return value
|
71
|
-
|
72
|
-
|
73
|
-
def to_internal_model_value(schema: Model, data):
|
74
|
-
stack = {}
|
75
|
-
for _, name, field in schema.iterate_with_name():
|
76
|
-
value = data.get(name, None)
|
77
|
-
if value is None:
|
78
|
-
continue
|
79
|
-
if isinstance(field, ForeignKey) and isinstance(value, dict):
|
80
|
-
value = value.get(field.model._meta.pk.attname, None)
|
81
|
-
elif isinstance(field, ForeignKeyList) and isinstance(value, list):
|
82
|
-
attname = field.model._meta.pk.attname
|
83
|
-
value = [v.get(attname, None) for v in value]
|
84
|
-
elif isinstance(field, EmbeddedField):
|
85
|
-
value = to_internal_model_value(field._get_embed_type(), value)
|
86
|
-
elif isinstance(field, ListField):
|
87
|
-
main_type = field._get_main_type()
|
88
|
-
value = [to_internal_model_value(main_type, v) for v in value]
|
89
|
-
else:
|
90
|
-
value = field.to_struct(value)
|
91
|
-
stack[name] = value
|
92
|
-
return stack
|
20
|
+
if isinstance(instance, list) and self.many:
|
21
|
+
return super().to_representation(
|
22
|
+
self.schema.dump_python(instance, exclude_unset=True)
|
23
|
+
)
|
24
|
+
return super().to_representation(instance.model_dump(exclude_unset=True))
|
@@ -20,6 +20,9 @@ if django.VERSION >= (4, 0):
|
|
20
20
|
else:
|
21
21
|
from django.contrib.postgres.fields import JSONField as DjangoJSONField
|
22
22
|
|
23
|
+
from typing import TYPE_CHECKING
|
24
|
+
if TYPE_CHECKING:
|
25
|
+
from camomilla.models.page import AbstractPage
|
23
26
|
|
24
27
|
# TODO: decide what to do with LangInfoMixin mixin!
|
25
28
|
class LangInfoMixin(metaclass=serializers.SerializerMetaclass):
|
@@ -121,8 +124,17 @@ class NestMixin:
|
|
121
124
|
return field_class, field_kwargs
|
122
125
|
|
123
126
|
|
124
|
-
|
125
127
|
class AbstractPageMixin(serializers.ModelSerializer):
|
128
|
+
breadcrumbs = serializers.SerializerMethodField()
|
129
|
+
routerlink = serializers.CharField(read_only=True)
|
130
|
+
template = serializers.SerializerMethodField()
|
131
|
+
|
132
|
+
def get_template(self, instance: 'AbstractPage'):
|
133
|
+
return instance.get_template_path()
|
134
|
+
|
135
|
+
def get_breadcrumbs(self, instance: 'AbstractPage'):
|
136
|
+
return instance.breadcrumbs
|
137
|
+
|
126
138
|
LANG_PERMALINK_FIELDS = [
|
127
139
|
build_localized_fieldname("permalink", lang)
|
128
140
|
for lang in AVAILABLE_LANGUAGES
|
@@ -134,6 +146,9 @@ class AbstractPageMixin(serializers.ModelSerializer):
|
|
134
146
|
return super().translation_fields + ["permalink"]
|
135
147
|
|
136
148
|
def get_default_field_names(self, *args):
|
149
|
+
from camomilla.contrib.rest_framework.serializer import RemoveTranslationsMixin
|
150
|
+
if RemoveTranslationsMixin in self.__class__.__bases__: # noqa: E501
|
151
|
+
return super().get_default_field_names(*args)
|
137
152
|
return (
|
138
153
|
[f for f in super().get_default_field_names(*args) if f != "url_node"]
|
139
154
|
+ self.LANG_PERMALINK_FIELDS
|
@@ -150,4 +165,3 @@ class AbstractPageMixin(serializers.ModelSerializer):
|
|
150
165
|
|
151
166
|
def get_validators(self):
|
152
167
|
return super().get_validators() + [UniquePermalinkValidator()]
|
153
|
-
|
camomilla/serializers/page.py
CHANGED
@@ -1,7 +1,13 @@
|
|
1
|
+
from camomilla.models.page import UrlNode
|
1
2
|
from camomilla.serializers.mixins import AbstractPageMixin
|
2
3
|
from camomilla.models import Content, Page
|
3
4
|
from camomilla.serializers.base import BaseModelSerializer
|
5
|
+
from rest_framework import serializers
|
4
6
|
|
7
|
+
from camomilla.serializers.utils import (
|
8
|
+
build_standard_model_serializer,
|
9
|
+
get_standard_bases,
|
10
|
+
)
|
5
11
|
|
6
12
|
|
7
13
|
class ContentSerializer(BaseModelSerializer):
|
@@ -14,3 +20,44 @@ class PageSerializer(AbstractPageMixin, BaseModelSerializer):
|
|
14
20
|
class Meta:
|
15
21
|
model = Page
|
16
22
|
fields = "__all__"
|
23
|
+
|
24
|
+
|
25
|
+
class BasicUrlNodeSerializer(BaseModelSerializer):
|
26
|
+
is_public = serializers.SerializerMethodField()
|
27
|
+
status = serializers.SerializerMethodField()
|
28
|
+
indexable = serializers.SerializerMethodField()
|
29
|
+
|
30
|
+
class Meta:
|
31
|
+
model = UrlNode
|
32
|
+
fields = ("id", "permalink", "status", "indexable", "is_public")
|
33
|
+
|
34
|
+
def get_is_public(self, instance: UrlNode):
|
35
|
+
return instance.page.is_public
|
36
|
+
|
37
|
+
def get_status(self, instance: UrlNode):
|
38
|
+
return instance.page.status
|
39
|
+
|
40
|
+
def get_indexable(self, instance: UrlNode):
|
41
|
+
return instance.page.indexable
|
42
|
+
|
43
|
+
|
44
|
+
class UrlNodeSerializer(BasicUrlNodeSerializer):
|
45
|
+
alternates = serializers.SerializerMethodField()
|
46
|
+
|
47
|
+
def get_alternates(self, instance: UrlNode):
|
48
|
+
return instance.page.alternate_urls()
|
49
|
+
|
50
|
+
def to_representation(self, instance: UrlNode):
|
51
|
+
model_serializer = build_standard_model_serializer(
|
52
|
+
instance.page.__class__,
|
53
|
+
depth=10,
|
54
|
+
bases=(AbstractPageMixin,) + get_standard_bases(),
|
55
|
+
)
|
56
|
+
return {
|
57
|
+
**super().to_representation(instance),
|
58
|
+
**model_serializer(instance.page, context=self.context).data,
|
59
|
+
}
|
60
|
+
|
61
|
+
class Meta:
|
62
|
+
model = UrlNode
|
63
|
+
fields = "__all__"
|
camomilla/serializers/user.py
CHANGED
@@ -40,8 +40,8 @@ class UserProfileSerializer(BaseModelSerializer):
|
|
40
40
|
def get_all_permissions(self, instance):
|
41
41
|
return PermissionSerializer(
|
42
42
|
Permission.objects.filter(
|
43
|
-
Q(group__pk__in=instance.groups.values_list("pk", flat=True))
|
44
|
-
Q(pk__in=instance.user_permissions.values_list("pk", flat=True))
|
43
|
+
Q(group__pk__in=instance.groups.values_list("pk", flat=True))
|
44
|
+
| Q(pk__in=instance.user_permissions.values_list("pk", flat=True))
|
45
45
|
),
|
46
46
|
context=self.context,
|
47
47
|
many=True,
|
@@ -68,7 +68,6 @@ class UserProfileSerializer(BaseModelSerializer):
|
|
68
68
|
|
69
69
|
|
70
70
|
class UserSerializer(BaseModelSerializer):
|
71
|
-
|
72
71
|
id = serializers.IntegerField(read_only=True)
|
73
72
|
password = serializers.CharField(
|
74
73
|
write_only=True, required=False, allow_null=True, allow_blank=True
|
camomilla/serializers/utils.py
CHANGED
@@ -1,22 +1,27 @@
|
|
1
|
+
def get_standard_bases() -> tuple:
|
2
|
+
from rest_framework.serializers import ModelSerializer
|
3
|
+
from camomilla.serializers.fields import FieldsOverrideMixin
|
4
|
+
from camomilla.contrib.rest_framework.serializer import RemoveTranslationsMixin
|
5
|
+
from camomilla.serializers.mixins import (
|
6
|
+
JSONFieldPatchMixin,
|
7
|
+
NestMixin,
|
8
|
+
OrderingMixin,
|
9
|
+
SetupEagerLoadingMixin,
|
10
|
+
)
|
11
|
+
|
12
|
+
return (
|
13
|
+
NestMixin,
|
14
|
+
FieldsOverrideMixin,
|
15
|
+
JSONFieldPatchMixin,
|
16
|
+
OrderingMixin,
|
17
|
+
RemoveTranslationsMixin,
|
18
|
+
SetupEagerLoadingMixin,
|
19
|
+
ModelSerializer,
|
20
|
+
)
|
21
|
+
|
1
22
|
def build_standard_model_serializer(model, depth, bases=None):
|
2
23
|
if bases is None:
|
3
|
-
|
4
|
-
from camomilla.serializers.fields import FieldsOverrideMixin
|
5
|
-
from camomilla.serializers.mixins import (
|
6
|
-
JSONFieldPatchMixin,
|
7
|
-
NestMixin,
|
8
|
-
OrderingMixin,
|
9
|
-
SetupEagerLoadingMixin,
|
10
|
-
)
|
11
|
-
|
12
|
-
bases = (
|
13
|
-
NestMixin,
|
14
|
-
FieldsOverrideMixin,
|
15
|
-
JSONFieldPatchMixin,
|
16
|
-
OrderingMixin,
|
17
|
-
SetupEagerLoadingMixin,
|
18
|
-
ModelSerializer,
|
19
|
-
)
|
24
|
+
bases = get_standard_bases()
|
20
25
|
return type(
|
21
26
|
f"{model.__name__}StandardSerializer",
|
22
27
|
bases,
|
camomilla/settings.py
CHANGED
@@ -67,6 +67,20 @@ ENABLE_MEDIA_OPTIMIZATION = pointed_getter(
|
|
67
67
|
|
68
68
|
API_NESTING_DEPTH = pointed_getter(django_settings, "CAMOMILLA.API.NESTING_DEPTH", 10)
|
69
69
|
|
70
|
+
AUTO_CREATE_HOMEPAGE = pointed_getter(
|
71
|
+
django_settings, "CAMOMILLA.RENDER.AUTO_CREATE_HOMEPAGE", True
|
72
|
+
)
|
73
|
+
|
74
|
+
TEMPLATE_CONTEXT_FILES = pointed_getter(
|
75
|
+
django_settings, "CAMOMILLA.RENDER.TEMPLATE_CONTEXT_FILES", []
|
76
|
+
)
|
77
|
+
|
78
|
+
STRUCTURED_FIELD_CACHE_ENABLED = pointed_getter(
|
79
|
+
django_settings, "CAMOMILLA.STRUCTURED_FIELD.CACHE_ENABLED", True
|
80
|
+
)
|
81
|
+
|
82
|
+
DEBUG = pointed_getter(django_settings, "CAMOMILLA.DEBUG", django_settings.DEBUG)
|
83
|
+
|
70
84
|
# camomilla settings example
|
71
85
|
# CAMOMILLA = {
|
72
86
|
# "PROJECT_TITLE": "",
|
@@ -78,8 +92,14 @@ API_NESTING_DEPTH = pointed_getter(django_settings, "CAMOMILLA.API.NESTING_DEPTH
|
|
78
92
|
# "THUMBNAIL": {"FOLDER": "", "WIDTH": 50, "HEIGHT": 50}
|
79
93
|
# },
|
80
94
|
# "RENDER": {
|
95
|
+
# "TEMPLATE_CONTEXT_FILES": [],
|
96
|
+
# "AUTO_CREATE_HOMEPAGE": True,
|
81
97
|
# "ARTICLE": {"DEFAULT_TEMPLATE": "", "INJECT_CONTEXT": None },
|
82
98
|
# "PAGE": {"DEFAULT_TEMPLATE": "", "INJECT_CONTEXT": None }
|
83
99
|
# },
|
84
|
-
# "
|
100
|
+
# "STRUCTURED_FIELD": {
|
101
|
+
# "CACHE_ENABLED": True
|
102
|
+
# }
|
103
|
+
# "API": {"NESTING_DEPTH": 10 },
|
104
|
+
# "DEBUG": False
|
85
105
|
# }
|
camomilla/storages/optimize.py
CHANGED
camomilla/structured/__init__.py
CHANGED
@@ -1,40 +1,47 @@
|
|
1
|
-
|
1
|
+
import json
|
2
2
|
from typing import Any
|
3
3
|
|
4
4
|
from django.db.models import JSONField
|
5
5
|
from django.db.models.query_utils import DeferredAttribute
|
6
|
+
from django_jsonform.models.fields import JSONFormField
|
7
|
+
from pydantic import (
|
8
|
+
TypeAdapter,
|
9
|
+
ValidationInfo,
|
10
|
+
ValidatorFunctionWrapHandler,
|
11
|
+
WrapValidator,
|
12
|
+
)
|
6
13
|
|
7
14
|
from .fields import *
|
8
15
|
from .models import *
|
9
|
-
|
16
|
+
|
17
|
+
|
18
|
+
class StructuredJSONFormField(JSONFormField):
|
19
|
+
def __init__(self, *args, **kwargs):
|
20
|
+
super().__init__(*args, **kwargs)
|
21
|
+
self.encoder = kwargs.get("encoder", None)
|
22
|
+
self.decoder = kwargs.get("decoder", None)
|
23
|
+
|
24
|
+
def prepare_value(self, value):
|
25
|
+
if isinstance(value, list):
|
26
|
+
return json.dumps([v.model_dump() for v in value])
|
27
|
+
return value.model_dump_json()
|
10
28
|
|
11
29
|
|
12
30
|
class StructuredDescriptior(DeferredAttribute):
|
31
|
+
field: "StructuredJSONField"
|
32
|
+
|
13
33
|
def __set__(self, instance, value):
|
14
|
-
if
|
15
|
-
|
16
|
-
|
17
|
-
elif isinstance(value, list):
|
18
|
-
self.field.prefetch_related(value)
|
19
|
-
_stack = []
|
20
|
-
for v in value:
|
21
|
-
if isinstance(v, dict):
|
22
|
-
v = self.field.populate_schema(v)
|
23
|
-
elif not isinstance(v, self.field.schema):
|
24
|
-
raise TypeError(
|
25
|
-
f"{type(v)} is not a valid type for the given schema ({self.field.schema})."
|
26
|
-
)
|
27
|
-
v.validate()
|
28
|
-
_stack.append(v)
|
29
|
-
value = _stack
|
30
|
-
elif isinstance(value, self.field.schema):
|
31
|
-
value.validate()
|
32
|
-
else:
|
33
|
-
raise TypeError(
|
34
|
-
f"{type(value)} is not a valid type for the given schema ({self.field.schema})."
|
35
|
-
)
|
34
|
+
# TODO: check if it's better to validate here or in __get__ function (performance reasons)
|
35
|
+
# if not self.field.check_type(value):
|
36
|
+
# value = self.field.schema.validate_python(value)
|
36
37
|
instance.__dict__[self.field.attname] = value
|
37
38
|
|
39
|
+
def __get__(self, instance, cls=None):
|
40
|
+
value = super().__get__(instance, cls)
|
41
|
+
if not self.field.check_type(value):
|
42
|
+
return self.field.schema.validate_python(value)
|
43
|
+
return value
|
44
|
+
|
38
45
|
|
39
46
|
class StructuredJSONField(JSONField):
|
40
47
|
# TODO: share cache in querysets of models having this same field
|
@@ -42,64 +49,72 @@ class StructuredJSONField(JSONField):
|
|
42
49
|
|
43
50
|
descriptor_class = StructuredDescriptior
|
44
51
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
52
|
+
@property
|
53
|
+
def list_data_validator(self):
|
54
|
+
def list_data_validator(
|
55
|
+
value: Any, handler: ValidatorFunctionWrapHandler, info: ValidationInfo
|
56
|
+
) -> Any:
|
57
|
+
from camomilla.structured.cache import CacheBuilder
|
49
58
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
59
|
+
cache = CacheBuilder.from_model(self.orig_schema)
|
60
|
+
if info.mode == "json" and isinstance(value, str):
|
61
|
+
return self.schema.validate_python(
|
62
|
+
cache.inject_cache(json.loads(value))
|
63
|
+
)
|
64
|
+
return handler(cache.inject_cache(value))
|
56
65
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
elif isinstance(value, dict):
|
67
|
-
value = self.schema.to_db_transform(self.populate_schema(value).to_struct())
|
68
|
-
elif isinstance(value, self.schema):
|
69
|
-
value = self.schema.to_db_transform(value.to_struct())
|
70
|
-
else:
|
71
|
-
raise TypeError(f"{type(value)} is not a valid type for the given schema.")
|
72
|
-
return super().get_prep_value(value)
|
73
|
-
|
74
|
-
def from_db_value(self, value, expression, connection):
|
75
|
-
return self.schema.from_db_transform(
|
76
|
-
super().from_db_value(value, expression, connection)
|
66
|
+
return list_data_validator
|
67
|
+
|
68
|
+
def __init__(self, schema: type[BaseModel], *args: Any, **kwargs: Any) -> None:
|
69
|
+
self.orig_schema = schema
|
70
|
+
self.schema = schema
|
71
|
+
default = kwargs.get("default", dict)
|
72
|
+
self.file_handler = kwargs.pop("file_handler", "")
|
73
|
+
self.many = kwargs.pop(
|
74
|
+
"many", isinstance(default() if callable(default) else default, list)
|
77
75
|
)
|
76
|
+
if self.many:
|
77
|
+
self.schema = TypeAdapter(
|
78
|
+
Annotated[
|
79
|
+
list[self.schema],
|
80
|
+
Field(default_factory=list),
|
81
|
+
WrapValidator(self.list_data_validator),
|
82
|
+
]
|
83
|
+
)
|
84
|
+
return super().__init__(*args, **kwargs)
|
78
85
|
|
79
|
-
def
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
if isinstance(struct, list):
|
84
|
-
relateds = defaultdict(set)
|
85
|
-
for inner_struct in struct:
|
86
|
-
if isinstance(inner_struct, dict):
|
87
|
-
child_relateds = self.schema.get_all_relateds(inner_struct)
|
88
|
-
for model, pks in child_relateds.items():
|
89
|
-
relateds[model].update(pks)
|
90
|
-
return relateds
|
91
|
-
return self.schema.get_all_relateds(struct)
|
92
|
-
|
93
|
-
def prefetch_related(self, struct):
|
94
|
-
relateds = self.get_all_relateds(struct)
|
95
|
-
for model, pks in relateds.items():
|
96
|
-
self._cache.prefetched_data[model] = (
|
97
|
-
{obj.pk: obj for obj in build_model_cache(model, pks)}
|
98
|
-
if len(pks) > 0
|
99
|
-
else {}
|
86
|
+
def check_type(self, value: Any):
|
87
|
+
if self.many:
|
88
|
+
return isinstance(value, list) and all(
|
89
|
+
isinstance(v, self.orig_schema) for v in value
|
100
90
|
)
|
91
|
+
return isinstance(value, self.orig_schema)
|
92
|
+
|
93
|
+
def get_prep_value(
|
94
|
+
self, value: Union[list[type[BaseModel]], type[BaseModel]]
|
95
|
+
) -> str:
|
96
|
+
if isinstance(value, list) and self.many:
|
97
|
+
return self.schema.dump_json(value, exclude_unset=True).decode()
|
98
|
+
return value.model_dump_json(exclude_unset=True)
|
99
|
+
|
100
|
+
def from_db_value(self, value: Any, expression: Any, connection: Any) -> Any:
|
101
|
+
data = super().from_db_value(value, expression, connection)
|
102
|
+
if isinstance(data, str):
|
103
|
+
return json.loads(data)
|
104
|
+
return data
|
101
105
|
|
102
106
|
def deconstruct(self):
|
103
107
|
name, path, args, kwargs = super().deconstruct()
|
104
108
|
kwargs["schema"] = self.schema
|
105
109
|
return name, path, args, kwargs
|
110
|
+
|
111
|
+
def formfield(self, **kwargs):
|
112
|
+
return super().formfield(
|
113
|
+
**{
|
114
|
+
"form_class": StructuredJSONFormField,
|
115
|
+
"schema": self.schema.json_schema(),
|
116
|
+
"model_name": self.model.__name__,
|
117
|
+
"file_handler": self.file_handler,
|
118
|
+
**kwargs,
|
119
|
+
}
|
120
|
+
)
|