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
@@ -0,0 +1,193 @@
|
|
1
|
+
from collections import defaultdict
|
2
|
+
from inspect import isclass
|
3
|
+
from typing import Any, Dict, Sequence
|
4
|
+
from typing_extensions import get_args, get_origin
|
5
|
+
from camomilla.settings import STRUCTURED_FIELD_CACHE_ENABLED
|
6
|
+
from camomilla.structured.fields import ForeignKey, QuerySet
|
7
|
+
from camomilla.structured.models import BaseModel
|
8
|
+
from camomilla.structured.utils import _LazyType, get_type, pointed_setter
|
9
|
+
|
10
|
+
from camomilla.utils.getters import pointed_getter
|
11
|
+
|
12
|
+
# TODO:
|
13
|
+
# ::: Actually this is a first draft.
|
14
|
+
# The system should be more type safe.
|
15
|
+
# It should handle initialization made with model instance not only dicts
|
16
|
+
# Neet to check if there are problems with different formats for pks (model instaces or string or dicts)
|
17
|
+
|
18
|
+
|
19
|
+
class ValueWithCache:
|
20
|
+
def __init__(self, cache, model, value) -> None:
|
21
|
+
self.cache = cache
|
22
|
+
self.value = value
|
23
|
+
self.model = model
|
24
|
+
|
25
|
+
def retrieve(self):
|
26
|
+
cache = self.cache.get(self.model)
|
27
|
+
if isinstance(self.value, Sequence):
|
28
|
+
qs = self.model._default_manager.filter(pk__in=self.value)
|
29
|
+
setattr(
|
30
|
+
qs,
|
31
|
+
"_result_cache",
|
32
|
+
[cache[i] for i in self.value if i in cache] if cache else [],
|
33
|
+
)
|
34
|
+
return qs
|
35
|
+
else:
|
36
|
+
val = cache.get(self.value, None)
|
37
|
+
if val is None:
|
38
|
+
return self.model._default_manager.get(pk=self.value)
|
39
|
+
else:
|
40
|
+
return val
|
41
|
+
|
42
|
+
|
43
|
+
class RelInfo:
|
44
|
+
FKField: str = "fk"
|
45
|
+
QSField: str = "qs"
|
46
|
+
RIField: str = "rel"
|
47
|
+
RLField: str = "rel_l"
|
48
|
+
|
49
|
+
def __init__(self, model, type, field) -> None:
|
50
|
+
self.model = model
|
51
|
+
self.type = type
|
52
|
+
self.field = field
|
53
|
+
|
54
|
+
|
55
|
+
class CacheBuilder:
|
56
|
+
def __init__(self, related_fields: Dict[str, RelInfo]) -> None:
|
57
|
+
self.__related_fields__ = related_fields
|
58
|
+
|
59
|
+
@classmethod
|
60
|
+
def from_model(cls, model: BaseModel) -> 'CacheBuilder':
|
61
|
+
return cls(related_fields=cls.inspect_related_fields(model))
|
62
|
+
|
63
|
+
@classmethod
|
64
|
+
def inspect_related_fields(cls, model: type[BaseModel]) -> Dict[str, RelInfo]:
|
65
|
+
related = {}
|
66
|
+
for field_name, field in model.model_fields.items():
|
67
|
+
annotation = field.annotation
|
68
|
+
origin = get_origin(annotation)
|
69
|
+
args = get_args(annotation)
|
70
|
+
|
71
|
+
if isclass(origin) and issubclass(origin, ForeignKey):
|
72
|
+
related[field_name] = RelInfo(
|
73
|
+
get_type(annotation), RelInfo.FKField, field
|
74
|
+
)
|
75
|
+
elif isclass(origin) and issubclass(origin, QuerySet):
|
76
|
+
related[field_name] = RelInfo(
|
77
|
+
get_type(annotation), RelInfo.QSField, field
|
78
|
+
)
|
79
|
+
|
80
|
+
elif isclass(origin) and issubclass(origin, Sequence):
|
81
|
+
lazy_types = [
|
82
|
+
_LazyType(arg).evaluate(model)
|
83
|
+
for arg in args
|
84
|
+
if isinstance(arg, str)
|
85
|
+
]
|
86
|
+
subclass = next(
|
87
|
+
(
|
88
|
+
c
|
89
|
+
for c in list(args) + lazy_types
|
90
|
+
if isclass(c) and issubclass(c, BaseModel)
|
91
|
+
),
|
92
|
+
None,
|
93
|
+
)
|
94
|
+
if isclass(subclass) and issubclass(subclass, BaseModel):
|
95
|
+
related[field_name] = RelInfo(subclass, RelInfo.RLField, field)
|
96
|
+
|
97
|
+
elif isclass(annotation) and issubclass(annotation, BaseModel):
|
98
|
+
related[field_name] = RelInfo(annotation, RelInfo.RIField, field)
|
99
|
+
return related
|
100
|
+
|
101
|
+
def get_all_fk_data(self, data):
|
102
|
+
if isinstance(data, Sequence):
|
103
|
+
fk_data = defaultdict(list)
|
104
|
+
for index in range(len(data)):
|
105
|
+
child_fk_data = self.get_all_fk_data(data[index])
|
106
|
+
for model, touples in child_fk_data.items():
|
107
|
+
fk_data[model] += [(f"{index}.{t[0]}", t[1]) for t in touples]
|
108
|
+
return fk_data
|
109
|
+
return self.get_fk_data(data)
|
110
|
+
|
111
|
+
def get_fk_data(self, data):
|
112
|
+
fk_data = defaultdict(list)
|
113
|
+
if not data:
|
114
|
+
return fk_data
|
115
|
+
for field_name, info in self.__related_fields__.items():
|
116
|
+
if info.type == RelInfo.FKField:
|
117
|
+
value = pointed_getter(data, field_name, None)
|
118
|
+
if value:
|
119
|
+
if isinstance(value, ValueWithCache): # needed to break recursive cache builds
|
120
|
+
fk_data = {}
|
121
|
+
break
|
122
|
+
fk_data[info.model].append(
|
123
|
+
(
|
124
|
+
field_name,
|
125
|
+
pointed_getter(value, info.model._meta.pk.attname, value),
|
126
|
+
)
|
127
|
+
)
|
128
|
+
elif info.type == RelInfo.QSField:
|
129
|
+
value = pointed_getter(data, field_name, [])
|
130
|
+
if isinstance(value, list):
|
131
|
+
if any(True for v in value if isinstance(v, ValueWithCache)): # needed to break recursive cache builds
|
132
|
+
fk_data = {}
|
133
|
+
break
|
134
|
+
fk_data[info.model].append(
|
135
|
+
(
|
136
|
+
field_name,
|
137
|
+
[
|
138
|
+
pointed_getter(i, info.model._meta.pk.attname, i)
|
139
|
+
for i in value
|
140
|
+
if i
|
141
|
+
],
|
142
|
+
)
|
143
|
+
)
|
144
|
+
|
145
|
+
elif info.type == RelInfo.RLField:
|
146
|
+
values = pointed_getter(data, field_name, [])
|
147
|
+
if isinstance(values, list):
|
148
|
+
for index in range(len(values)):
|
149
|
+
for model, touples in (
|
150
|
+
self.from_model(info.model)
|
151
|
+
.get_all_fk_data(values[index])
|
152
|
+
.items()
|
153
|
+
):
|
154
|
+
fk_data[model] += [
|
155
|
+
(f"{field_name}.{index}.{t[0]}", t[1]) for t in touples
|
156
|
+
]
|
157
|
+
elif info.type == RelInfo.RIField:
|
158
|
+
value = pointed_getter(data, field_name, None)
|
159
|
+
child_fk_data = self.from_model(info.model).get_all_fk_data(value)
|
160
|
+
for model, touples in child_fk_data.items():
|
161
|
+
fk_data[model] += [(f"{field_name}.{t[0]}", t[1]) for t in touples]
|
162
|
+
return fk_data
|
163
|
+
|
164
|
+
def inject_cache(self, data: Any) -> Any:
|
165
|
+
if not STRUCTURED_FIELD_CACHE_ENABLED:
|
166
|
+
return data
|
167
|
+
fk_data = self.get_all_fk_data(data)
|
168
|
+
plainset = defaultdict(set)
|
169
|
+
for model, touples in fk_data.items():
|
170
|
+
for t in touples:
|
171
|
+
if isinstance(t[1], Sequence):
|
172
|
+
plainset[model].update(t[1])
|
173
|
+
else:
|
174
|
+
plainset[model].add(t[1])
|
175
|
+
cache = {}
|
176
|
+
for model, values in plainset.items():
|
177
|
+
models = []
|
178
|
+
pks = []
|
179
|
+
for value in values:
|
180
|
+
if isinstance(value, model):
|
181
|
+
models.append(value)
|
182
|
+
else:
|
183
|
+
pks.append(value)
|
184
|
+
models_pks = [m.pk for m in models]
|
185
|
+
pks = [pk for pk in pks if pk not in models_pks]
|
186
|
+
if len(pks):
|
187
|
+
models += list(model.objects.filter(pk__in=pks))
|
188
|
+
cache[model] = {obj.pk: obj for obj in models}
|
189
|
+
|
190
|
+
for model, touples in fk_data.items():
|
191
|
+
for t in touples:
|
192
|
+
pointed_setter(data, t[0], ValueWithCache(cache, model, t[1]))
|
193
|
+
return data
|
camomilla/structured/fields.py
CHANGED
@@ -1,292 +1,149 @@
|
|
1
|
-
import
|
2
|
-
import decimal
|
1
|
+
from typing import Any, Callable, Dict, Generic, TypeVar, Union
|
3
2
|
|
4
|
-
from django.
|
5
|
-
from
|
6
|
-
from
|
7
|
-
from django.utils.duration import duration_string
|
8
|
-
from jsonmodels import fields
|
9
|
-
from jsonmodels.validators import ValidationError
|
10
|
-
from camomilla.utils.getters import pointed_getter
|
3
|
+
from django.db import models as django_models
|
4
|
+
from pydantic import SerializationInfo
|
5
|
+
from pydantic_core import core_schema as cs
|
11
6
|
|
7
|
+
from .utils import get_type
|
8
|
+
from devtools import debug
|
12
9
|
|
13
|
-
__all__ = [
|
14
|
-
"CharField",
|
15
|
-
"DecimalField",
|
16
|
-
"IntegerField",
|
17
|
-
"FloatField",
|
18
|
-
"BooleanField",
|
19
|
-
"DateTimeField",
|
20
|
-
"DateField",
|
21
|
-
"TimeField",
|
22
|
-
"DurationField",
|
23
|
-
"EmailField",
|
24
|
-
"SlugField",
|
25
|
-
"URLField",
|
26
|
-
"ListField",
|
27
|
-
"DictField",
|
28
|
-
"EmbeddedField",
|
29
|
-
"ForeignKey",
|
30
|
-
"ForeignKeyList",
|
31
|
-
]
|
32
10
|
|
11
|
+
T = TypeVar("T", bound=django_models.Model)
|
33
12
|
|
34
|
-
class Field(fields.BaseField):
|
35
|
-
additional_kwargs = {}
|
36
|
-
additional_validators = []
|
37
|
-
__validators = []
|
38
|
-
parent = None
|
39
|
-
|
40
|
-
def __init__(self, *args, **kwargs):
|
41
|
-
for key, value in self.additional_kwargs.items():
|
42
|
-
setattr(self, key, kwargs.pop(key, value))
|
43
|
-
super(Field, self).__init__(*args, **kwargs)
|
44
|
-
|
45
|
-
@property
|
46
|
-
def validators(self):
|
47
|
-
validators = self.__validators
|
48
|
-
for validator in self.additional_validators:
|
49
|
-
if isinstance(validator, dict):
|
50
|
-
validator_class = validator["validator"]
|
51
|
-
args = [
|
52
|
-
getattr(self, arg)
|
53
|
-
for arg in validator.get("args", [])
|
54
|
-
if getattr(self, arg, fields.NotSet) != fields.NotSet
|
55
|
-
]
|
56
|
-
kwargs = {
|
57
|
-
key: getattr(self, value)
|
58
|
-
for key, value in validator.get("kwargs", {}).items()
|
59
|
-
if getattr(self, value, fields.NotSet) != fields.NotSet
|
60
|
-
}
|
61
|
-
if len(args) or len(kwargs.keys()):
|
62
|
-
validators.append(validator_class(*args, **kwargs))
|
63
|
-
else:
|
64
|
-
validators.append(validator)
|
65
|
-
return validators
|
66
|
-
|
67
|
-
@validators.setter
|
68
|
-
def validators(self, value):
|
69
|
-
self.__validators = value
|
70
|
-
|
71
|
-
def bind(self, parent):
|
72
|
-
self.parent = parent
|
73
|
-
|
74
|
-
@classmethod
|
75
|
-
def to_db_transform(cls, data):
|
76
|
-
return data
|
77
13
|
|
14
|
+
class ForeignKey(django_models.Model, Generic[T]):
|
78
15
|
@classmethod
|
79
|
-
def
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
additional_kwargs = {
|
96
|
-
"max_digits": fields.NotSet,
|
97
|
-
"decimal_places": fields.NotSet,
|
98
|
-
}
|
99
|
-
additional_validators = [
|
100
|
-
{
|
101
|
-
"validator": validators.DecimalValidator,
|
102
|
-
"args": ["max_digits", "decimal_places"],
|
103
|
-
}
|
104
|
-
]
|
105
|
-
|
106
|
-
|
107
|
-
class IntegerField(fields.IntField, Field):
|
108
|
-
additional_kwargs = {
|
109
|
-
"min_value": fields.NotSet,
|
110
|
-
"max_value": fields.NotSet,
|
111
|
-
}
|
112
|
-
additional_validators = [
|
113
|
-
{"validator": validators.MinValueValidator, "args": ["min_value"]},
|
114
|
-
{"validator": validators.MaxValueValidator, "args": ["max_value"]},
|
115
|
-
]
|
116
|
-
|
117
|
-
|
118
|
-
class FloatField(IntegerField):
|
119
|
-
types = (float, int)
|
120
|
-
|
121
|
-
|
122
|
-
class BooleanField(fields.BoolField, Field):
|
123
|
-
pass
|
124
|
-
|
125
|
-
|
126
|
-
class DateTimeField(fields.DateTimeField, Field):
|
127
|
-
pass
|
128
|
-
|
129
|
-
|
130
|
-
class DateField(fields.DateField, Field):
|
131
|
-
pass
|
132
|
-
|
133
|
-
|
134
|
-
class TimeField(fields.TimeField, Field):
|
135
|
-
pass
|
136
|
-
|
137
|
-
|
138
|
-
class DurationField(fields.StringField, Field):
|
139
|
-
types = (datetime.timedelta,)
|
140
|
-
|
141
|
-
def to_struct(self, value):
|
142
|
-
return value and duration_string(value)
|
143
|
-
|
144
|
-
def parse_value(self, value):
|
145
|
-
if value is None:
|
146
|
-
return value
|
147
|
-
if isinstance(value, datetime.timedelta):
|
148
|
-
return value
|
149
|
-
try:
|
150
|
-
parsed = parse_duration(value)
|
151
|
-
except ValueError:
|
152
|
-
pass
|
153
|
-
else:
|
154
|
-
if parsed is not None:
|
155
|
-
return parsed
|
156
|
-
|
157
|
-
raise fields.ValidationError(
|
158
|
-
"“%(value)s” value has an invalid format. It must be in '[DD] [[HH:]MM:]ss[.uuuuuu] format.'".format(
|
159
|
-
value=value
|
160
|
-
)
|
16
|
+
def __get_pydantic_core_schema__(
|
17
|
+
cls, source: Any, handler: Callable[[Any], cs.CoreSchema]
|
18
|
+
) -> cs.CoreSchema:
|
19
|
+
from camomilla.structured.cache import ValueWithCache
|
20
|
+
|
21
|
+
model_class = get_type(source)
|
22
|
+
|
23
|
+
def validate_from_pk(pk: Union[int, str]) -> model_class:
|
24
|
+
return model_class._default_manager.get(pk=pk)
|
25
|
+
|
26
|
+
int_str_union = cs.union_schema([cs.str_schema(), cs.int_schema()])
|
27
|
+
from_pk_schema = cs.chain_schema(
|
28
|
+
[
|
29
|
+
int_str_union,
|
30
|
+
cs.no_info_plain_validator_function(validate_from_pk),
|
31
|
+
]
|
161
32
|
)
|
33
|
+
pk_attname = model_class._meta.pk.attname
|
162
34
|
|
35
|
+
def validate_from_dict(data: Dict[str, Union[str, int]]) -> model_class:
|
36
|
+
return validate_from_pk(data[pk_attname])
|
163
37
|
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
38
|
+
from_dict_schema = cs.chain_schema(
|
39
|
+
[
|
40
|
+
cs.typed_dict_schema({pk_attname: cs.typed_dict_field(int_str_union)}),
|
41
|
+
cs.no_info_plain_validator_function(validate_from_dict),
|
42
|
+
]
|
43
|
+
)
|
171
44
|
|
172
|
-
|
173
|
-
|
45
|
+
from_cache_schema = cs.chain_schema(
|
46
|
+
[
|
47
|
+
cs.is_instance_schema(ValueWithCache),
|
48
|
+
cs.no_info_plain_validator_function(lambda v: v.retrieve()),
|
49
|
+
]
|
50
|
+
)
|
51
|
+
|
52
|
+
def serialize_data(instance, info):
|
53
|
+
from camomilla.serializers.utils import build_standard_model_serializer
|
54
|
+
if info.mode == "python":
|
55
|
+
serializer = build_standard_model_serializer(model_class, depth=1)
|
56
|
+
return serializer(instance=instance).data
|
57
|
+
return instance.pk
|
58
|
+
|
59
|
+
return cs.json_or_python_schema(
|
60
|
+
json_schema=cs.union_schema(
|
61
|
+
[from_cache_schema, from_pk_schema, from_dict_schema]
|
62
|
+
),
|
63
|
+
python_schema=cs.union_schema(
|
64
|
+
[
|
65
|
+
cs.is_instance_schema(model_class),
|
66
|
+
from_cache_schema,
|
67
|
+
from_pk_schema,
|
68
|
+
from_dict_schema,
|
69
|
+
]
|
70
|
+
),
|
71
|
+
serialization=cs.plain_serializer_function_ser_schema(
|
72
|
+
serialize_data, info_arg=True
|
73
|
+
),
|
74
|
+
)
|
174
75
|
|
175
76
|
|
176
|
-
class
|
177
|
-
|
178
|
-
def
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
return value
|
191
|
-
else:
|
192
|
-
main_type = self._get_main_type()
|
193
|
-
from camomilla.structured.models import Model
|
194
|
-
if issubclass(main_type, Model):
|
195
|
-
instance = main_type()
|
196
|
-
relations = instance.prepopulate(**value)
|
197
|
-
instance.bind(parent)
|
198
|
-
instance.populate(**relations)
|
199
|
-
return instance
|
200
|
-
return main_type(**value)
|
201
|
-
|
202
|
-
def _get_main_type(self):
|
203
|
-
if len(self.items_types) != 1:
|
204
|
-
raise ValidationError(
|
205
|
-
'Cannot decide which type to choose from "{items_types}".'.format(
|
206
|
-
items_types=", ".join([t.__name__ for t in self.items_types])
|
207
|
-
)
|
77
|
+
class QuerySet(Generic[T]):
|
78
|
+
@classmethod
|
79
|
+
def __get_pydantic_core_schema__(
|
80
|
+
cls, source: Any, handler: Callable[[Any], cs.CoreSchema]
|
81
|
+
) -> cs.CoreSchema:
|
82
|
+
from camomilla.structured.cache import ValueWithCache
|
83
|
+
|
84
|
+
model_class = get_type(source)
|
85
|
+
|
86
|
+
def validate_from_pk_list(
|
87
|
+
values: list[Union[int, str]]
|
88
|
+
) -> django_models.QuerySet:
|
89
|
+
preserved = django_models.Case(
|
90
|
+
*[django_models.When(pk=pk, then=pos) for pos, pk in enumerate(values)]
|
208
91
|
)
|
209
|
-
|
210
|
-
|
211
|
-
class DictField(fields.DictField, Field):
|
212
|
-
pass
|
213
|
-
|
214
|
-
|
215
|
-
class EmbeddedField(fields.EmbeddedField, Field):
|
216
|
-
def parse_value(self, value):
|
217
|
-
if not isinstance(value, dict):
|
218
|
-
return value
|
219
|
-
embed_instance = self._get_embed_type()()
|
220
|
-
relations = embed_instance.prepopulate(**value)
|
221
|
-
embed_instance.bind(self.parent)
|
222
|
-
embed_instance.populate(**relations)
|
223
|
-
return embed_instance
|
224
|
-
|
225
|
-
|
226
|
-
class ForeignKey(Field):
|
227
|
-
types = (models.Model, int, str)
|
228
|
-
|
229
|
-
def __init__(self, to, *args, **kwargs):
|
230
|
-
self.model = to
|
231
|
-
self.queryset = kwargs.pop("queryset", None)
|
232
|
-
super(ForeignKey, self).__init__(*args, **kwargs)
|
233
|
-
|
234
|
-
def get_queryset(self):
|
235
|
-
return self.queryset or self.model.objects.all()
|
236
|
-
|
237
|
-
def to_struct(self, value):
|
238
|
-
return getattr(value, "pk", value)
|
239
|
-
|
240
|
-
def parse_value(self, value):
|
241
|
-
try:
|
242
|
-
if isinstance(value, bool):
|
243
|
-
raise TypeError
|
244
|
-
if isinstance(value, self.model):
|
245
|
-
return value
|
246
|
-
cache = pointed_getter(self, "parent.get_prefetched_data", lambda: None)()
|
247
|
-
if cache is not None:
|
248
|
-
return cache.get(self.model, {}).get(value)
|
249
|
-
return value and self.get_queryset().get(pk=value)
|
250
|
-
except (TypeError, ValueError):
|
251
|
-
raise fields.ValidationError(
|
252
|
-
"Incorrect type. Expected pk value, received {data_type}.".format(
|
253
|
-
data_type=type(value).__name__
|
254
|
-
)
|
92
|
+
return model_class._default_manager.filter(pk__in=values).order_by(
|
93
|
+
preserved
|
255
94
|
)
|
256
95
|
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
96
|
+
int_str_union = cs.union_schema([cs.str_schema(), cs.int_schema()])
|
97
|
+
from_pk_list_schema = cs.chain_schema(
|
98
|
+
[
|
99
|
+
cs.list_schema(int_str_union),
|
100
|
+
cs.no_info_plain_validator_function(validate_from_pk_list),
|
101
|
+
]
|
102
|
+
)
|
103
|
+
pk_attname = model_class._meta.pk.attname
|
104
|
+
|
105
|
+
def validate_from_dict(
|
106
|
+
values: list[Dict[str, Union[str, int]]]
|
107
|
+
) -> django_models.QuerySet:
|
108
|
+
return validate_from_pk_list([data[pk_attname] for data in values])
|
109
|
+
|
110
|
+
from_dict_list_schema = cs.chain_schema(
|
111
|
+
[
|
112
|
+
cs.list_schema(
|
113
|
+
cs.typed_dict_schema(
|
114
|
+
{pk_attname: cs.typed_dict_field(int_str_union)}
|
115
|
+
)
|
116
|
+
),
|
117
|
+
cs.no_info_plain_validator_function(validate_from_dict),
|
118
|
+
]
|
119
|
+
)
|
120
|
+
from_cache_schema = cs.chain_schema(
|
121
|
+
[
|
122
|
+
cs.is_instance_schema(ValueWithCache),
|
123
|
+
cs.no_info_plain_validator_function(lambda v: v.retrieve()),
|
124
|
+
]
|
125
|
+
)
|
126
|
+
|
127
|
+
def serialize_data(qs: django_models.QuerySet, info: SerializationInfo):
|
128
|
+
from camomilla.serializers.utils import build_standard_model_serializer
|
129
|
+
if info.mode == "python":
|
130
|
+
serializer = build_standard_model_serializer(model_class, depth=1)
|
131
|
+
return serializer(instance=qs, many=True).data
|
132
|
+
return [x.pk for x in qs]
|
133
|
+
|
134
|
+
return cs.json_or_python_schema(
|
135
|
+
json_schema=cs.union_schema(
|
136
|
+
[from_cache_schema, from_pk_list_schema, from_dict_list_schema]
|
137
|
+
),
|
138
|
+
python_schema=cs.union_schema(
|
139
|
+
[
|
140
|
+
cs.is_instance_schema(django_models.QuerySet),
|
141
|
+
from_cache_schema,
|
142
|
+
from_pk_list_schema,
|
143
|
+
from_dict_list_schema,
|
144
|
+
]
|
145
|
+
),
|
146
|
+
serialization=cs.plain_serializer_function_ser_schema(
|
147
|
+
serialize_data, info_arg=True
|
148
|
+
),
|
285
149
|
)
|
286
|
-
return self.get_inner_queryset().filter(pk__in=values).order_by(preserved)
|
287
|
-
|
288
|
-
def get_inner_queryset(self):
|
289
|
-
return self.inner_queryset or self.inner_model._default_manager.all()
|
290
|
-
|
291
|
-
def to_struct(self, value):
|
292
|
-
return [getattr(v, "pk", v) for v in value]
|