django-camomilla-cms 6.0.0b14__py2.py3-none-any.whl → 6.0.0b16__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.
Files changed (51) hide show
  1. camomilla/__init__.py +1 -1
  2. camomilla/dynamic_pages_urls.py +2 -1
  3. camomilla/fields/__init__.py +1 -3
  4. camomilla/managers/pages.py +22 -33
  5. camomilla/model_api.py +9 -4
  6. camomilla/models/media.py +1 -1
  7. camomilla/models/menu.py +23 -11
  8. camomilla/models/page.py +76 -38
  9. camomilla/openapi/schema.py +4 -0
  10. camomilla/serializers/base/__init__.py +3 -1
  11. camomilla/serializers/fields/__init__.py +2 -2
  12. camomilla/serializers/fields/json.py +2 -2
  13. camomilla/serializers/fields/related.py +5 -1
  14. camomilla/serializers/mixins/__init__.py +56 -18
  15. camomilla/serializers/mixins/filter_fields.py +56 -0
  16. camomilla/serializers/utils.py +3 -1
  17. camomilla/serializers/validators.py +9 -5
  18. camomilla/settings.py +0 -4
  19. camomilla/storages/default.py +6 -0
  20. camomilla/storages/optimize.py +2 -2
  21. camomilla/storages/overwrite.py +2 -2
  22. camomilla/templates/defaults/parts/menu.html +1 -1
  23. camomilla/theme/__init__.py +1 -1
  24. camomilla/theme/admin.py +1 -1
  25. camomilla/translation.py +1 -1
  26. camomilla/utils/query_parser.py +148 -0
  27. camomilla/utils/setters.py +37 -0
  28. camomilla/views/base/__init__.py +2 -2
  29. camomilla/views/menus.py +0 -3
  30. camomilla/views/mixins/__init__.py +9 -2
  31. camomilla/views/mixins/pagination.py +4 -13
  32. {django_camomilla_cms-6.0.0b14.dist-info → django_camomilla_cms-6.0.0b16.dist-info}/METADATA +2 -3
  33. {django_camomilla_cms-6.0.0b14.dist-info → django_camomilla_cms-6.0.0b16.dist-info}/RECORD +46 -40
  34. {django_camomilla_cms-6.0.0b14.dist-info → django_camomilla_cms-6.0.0b16.dist-info}/WHEEL +1 -1
  35. tests/fixtures/__init__.py +17 -0
  36. tests/test_api.py +2 -11
  37. tests/test_camomilla_filters.py +7 -13
  38. tests/test_media.py +80 -0
  39. tests/test_model_api.py +68 -0
  40. tests/test_model_api_permissions.py +39 -0
  41. tests/test_query_parser.py +59 -0
  42. tests/test_utils.py +64 -64
  43. tests/utils/__init__.py +0 -0
  44. tests/utils/api.py +29 -0
  45. camomilla/structured/__init__.py +0 -125
  46. camomilla/structured/cache.py +0 -202
  47. camomilla/structured/fields.py +0 -150
  48. camomilla/structured/models.py +0 -47
  49. camomilla/structured/utils.py +0 -114
  50. {django_camomilla_cms-6.0.0b14.dist-info → django_camomilla_cms-6.0.0b16.dist-info}/LICENSE +0 -0
  51. {django_camomilla_cms-6.0.0b14.dist-info → django_camomilla_cms-6.0.0b16.dist-info}/top_level.txt +0 -0
tests/test_utils.py CHANGED
@@ -13,74 +13,74 @@ from django.utils.translation import activate, get_language
13
13
  from requests import RequestException
14
14
 
15
15
 
16
- class UtilsTestCase(TestCase):
17
- def setUp(self):
18
- pass
16
+ # class UtilsTestCase(TestCase):
17
+ # def setUp(self):
18
+ # pass
19
19
 
20
- def test_get_host_url(self):
21
- """Our beloved get_host_url utility"""
22
- request_factory = RequestFactory()
23
- request = request_factory.get("/path")
24
- request.META["HTTP_HOST"] = "localhost"
25
- host_url = get_host_url(request)
26
- self.assertEqual(host_url, "http://localhost")
27
- host_url = get_host_url(None)
28
- self.assertEqual(host_url, None)
20
+ # def test_get_host_url(self):
21
+ # """Our beloved get_host_url utility"""
22
+ # request_factory = RequestFactory()
23
+ # request = request_factory.get("/path")
24
+ # request.META["HTTP_HOST"] = "localhost"
25
+ # host_url = get_host_url(request)
26
+ # self.assertEqual(host_url, "http://localhost")
27
+ # host_url = get_host_url(None)
28
+ # self.assertEqual(host_url, None)
29
29
 
30
- def test_get_complete_url(self):
31
- """Our beloved get_complete_url utility"""
32
- request_factory = RequestFactory()
33
- request = request_factory.get("/path")
34
- request.META["HTTP_HOST"] = "localhost"
35
- complete_url = get_complete_url(request, "path")
36
- self.assertEqual(complete_url, "http://localhost/path")
37
- complete_url = get_complete_url(request, "path", "it")
38
- self.assertEqual(complete_url, "http://localhost/path")
39
- complete_url = get_complete_url(request, "path", "fr")
40
- self.assertEqual(complete_url, "http://localhost/fr/path")
30
+ # def test_get_complete_url(self):
31
+ # """Our beloved get_complete_url utility"""
32
+ # request_factory = RequestFactory()
33
+ # request = request_factory.get("/path")
34
+ # request.META["HTTP_HOST"] = "localhost"
35
+ # complete_url = get_complete_url(request, "path")
36
+ # self.assertEqual(complete_url, "http://localhost/path")
37
+ # complete_url = get_complete_url(request, "path", "it")
38
+ # self.assertEqual(complete_url, "http://localhost/path")
39
+ # complete_url = get_complete_url(request, "path", "fr")
40
+ # self.assertEqual(complete_url, "http://localhost/fr/path")
41
41
 
42
- def test_get_page_with_default_seo(self):
43
- """Our beloved get_seo utility with auto attributes"""
44
- request_factory = RequestFactory()
45
- request = request_factory.get("/path")
46
- request.META["HTTP_HOST"] = "localhost"
47
- page = Page.get(request, identifier="home")
48
- self.assertEqual(page.og_url, "http://localhost/path")
49
- self.assertEqual(page.canonical, "http://localhost/path")
42
+ # def test_get_page_with_default_seo(self):
43
+ # """Our beloved get_seo utility with auto attributes"""
44
+ # request_factory = RequestFactory()
45
+ # request = request_factory.get("/path")
46
+ # request.META["HTTP_HOST"] = "localhost"
47
+ # page = Page.get(request, identifier="home")
48
+ # self.assertEqual(page.og_url, "http://localhost/path")
49
+ # self.assertEqual(page.canonical, "http://localhost/path")
50
50
 
51
- def test_get_article_with_default_seo(self):
52
- """Our beloved get_seo utility with auto attributes"""
53
- request_factory = RequestFactory()
54
- request = request_factory.get("/path")
55
- request.META["HTTP_HOST"] = "localhost"
56
- Article.objects.create(permalink="main")
57
- article = Article.get(request, permalink="main")
58
- self.assertEqual(article.og_url, "http://localhost/path")
59
- self.assertEqual(article.canonical, "http://localhost/path")
51
+ # def test_get_article_with_default_seo(self):
52
+ # """Our beloved get_seo utility with auto attributes"""
53
+ # request_factory = RequestFactory()
54
+ # request = request_factory.get("/path")
55
+ # request.META["HTTP_HOST"] = "localhost"
56
+ # Article.objects.create(permalink="main")
57
+ # article = Article.get(request, permalink="main")
58
+ # self.assertEqual(article.og_url, "http://localhost/path")
59
+ # self.assertEqual(article.canonical, "http://localhost/path")
60
60
 
61
- def test_compile_seo_overwrite(self):
62
- """Our beloved get_seo utility with auto attributes"""
63
- request_factory = RequestFactory()
64
- request = request_factory.get("/path")
65
- request.META["HTTP_HOST"] = "localhost"
66
- article = Article.objects.create(permalink="main")
67
- article.canonical = "/myarticle"
68
- article.og_url = "/myarticle"
69
- article.save()
70
- article = Article.get(request, permalink="main")
71
- self.assertEqual(article.og_url, "http://localhost/myarticle")
72
- self.assertEqual(article.canonical, "http://localhost/myarticle")
61
+ # def test_compile_seo_overwrite(self):
62
+ # """Our beloved get_seo utility with auto attributes"""
63
+ # request_factory = RequestFactory()
64
+ # request = request_factory.get("/path")
65
+ # request.META["HTTP_HOST"] = "localhost"
66
+ # article = Article.objects.create(permalink="main")
67
+ # article.canonical = "/myarticle"
68
+ # article.og_url = "/myarticle"
69
+ # article.save()
70
+ # article = Article.get(request, permalink="main")
71
+ # self.assertEqual(article.og_url, "http://localhost/myarticle")
72
+ # self.assertEqual(article.canonical, "http://localhost/myarticle")
73
73
 
74
- def test_get_article_with_redirect(self):
75
- """Our beloved get_seo utility with auto attributes"""
76
- request_factory = RequestFactory()
77
- request = request_factory.get("/articles/articolo-1")
78
- request.META["HTTP_HOST"] = "localhost"
74
+ # def test_get_article_with_redirect(self):
75
+ # """Our beloved get_seo utility with auto attributes"""
76
+ # request_factory = RequestFactory()
77
+ # request = request_factory.get("/articles/articolo-1")
78
+ # request.META["HTTP_HOST"] = "localhost"
79
79
 
80
- article = Article.objects.create(permalink="article-1", language_code="en")
81
- article.translate("it")
82
- article.permalink = "articolo-1"
83
- article.save()
84
- self.assertRaises(NeedARedirect, Article.get, request, permalink="article-1")
85
- activate("de")
86
- self.assertRaises(Http404, Article.get, request, permalink="article-1")
80
+ # article = Article.objects.create(permalink="article-1", language_code="en")
81
+ # article.translate("it")
82
+ # article.permalink = "articolo-1"
83
+ # article.save()
84
+ # self.assertRaises(NeedARedirect, Article.get, request, permalink="article-1")
85
+ # activate("de")
86
+ # self.assertRaises(Http404, Article.get, request, permalink="article-1")
File without changes
tests/utils/api.py ADDED
@@ -0,0 +1,29 @@
1
+ from django.contrib.auth.models import User
2
+ from rest_framework.test import APIClient
3
+ from example.website.models import TestModel
4
+
5
+ client = APIClient()
6
+
7
+
8
+ def login_superuser():
9
+ User.objects.create_superuser("admin", "admin@test.com", "adminadmin")
10
+ response = client.post(
11
+ "/api/camomilla/token-auth/", {"username": "admin", "password": "adminadmin"}
12
+ )
13
+ return response.json()["token"]
14
+
15
+
16
+ def login_user():
17
+ User.objects.create_user("user", "user@test.com", "useruser")
18
+ response = client.post(
19
+ "/api/camomilla/token-auth/", {"username": "user", "password": "useruser"}
20
+ )
21
+ return response.json()["token"]
22
+
23
+
24
+ def login_staff():
25
+ User.objects.create_user("staff", "staff@test.com", "staffstaff", is_staff=True)
26
+ response = client.post(
27
+ "/api/camomilla/token-auth/", {"username": "staff", "password": "staffstaff"}
28
+ )
29
+ return response.json()["token"]
@@ -1,125 +0,0 @@
1
- import json
2
- from typing import Any, Union, Type, TYPE_CHECKING, List
3
-
4
- from django.db.models import JSONField
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
- )
13
-
14
- from .fields import *
15
- from .models import *
16
-
17
- if TYPE_CHECKING:
18
- from pydantic import BaseModel
19
- from pydantic.fields import Field
20
- from typing_extensions import Annotated
21
-
22
-
23
- class StructuredJSONFormField(JSONFormField):
24
- def __init__(self, *args, **kwargs):
25
- super().__init__(*args, **kwargs)
26
- self.encoder = kwargs.get("encoder", None)
27
- self.decoder = kwargs.get("decoder", None)
28
-
29
- def prepare_value(self, value):
30
- if isinstance(value, list):
31
- return json.dumps([v.model_dump() for v in value])
32
- return value.model_dump_json()
33
-
34
-
35
- class StructuredDescriptior(DeferredAttribute):
36
- field: "StructuredJSONField"
37
-
38
- def __set__(self, instance, value):
39
- # TODO: check if it's better to validate here or in __get__ function (performance reasons)
40
- # if not self.field.check_type(value):
41
- # value = self.field.schema.validate_python(value)
42
- instance.__dict__[self.field.attname] = value
43
-
44
- def __get__(self, instance, cls=None):
45
- value = super().__get__(instance, cls)
46
- if not self.field.check_type(value):
47
- return self.field.schema.validate_python(value)
48
- return value
49
-
50
-
51
- class StructuredJSONField(JSONField):
52
- # TODO: share cache in querysets of models having this same field
53
- # TODO: write queries for prefetch related for models inside the field
54
-
55
- descriptor_class = StructuredDescriptior
56
-
57
- @property
58
- def list_data_validator(self):
59
- def list_data_validator(
60
- value: Any, handler: ValidatorFunctionWrapHandler, info: ValidationInfo
61
- ) -> Any:
62
- from camomilla.structured.cache import CacheBuilder
63
-
64
- cache = CacheBuilder.from_model(self.orig_schema)
65
- if info.mode == "json" and isinstance(value, str):
66
- return self.schema.validate_python(
67
- cache.inject_cache(json.loads(value))
68
- )
69
- return handler(cache.inject_cache(value))
70
-
71
- return list_data_validator
72
-
73
- def __init__(self, schema: Type[BaseModel], *args: Any, **kwargs: Any) -> None:
74
- self.orig_schema = schema
75
- self.schema = schema
76
- default = kwargs.get("default", dict)
77
- self.file_handler = kwargs.pop("file_handler", "")
78
- self.many = kwargs.pop(
79
- "many", isinstance(default() if callable(default) else default, list)
80
- )
81
- if self.many:
82
- self.schema = TypeAdapter(
83
- Annotated[
84
- List[self.schema],
85
- Field(default_factory=list),
86
- WrapValidator(self.list_data_validator),
87
- ]
88
- )
89
- return super().__init__(*args, **kwargs)
90
-
91
- def check_type(self, value: Any):
92
- if self.many:
93
- return isinstance(value, list) and all(
94
- isinstance(v, self.orig_schema) for v in value
95
- )
96
- return isinstance(value, self.orig_schema)
97
-
98
- def get_prep_value(
99
- self, value: Union[List[Type[BaseModel]], Type[BaseModel]]
100
- ) -> str:
101
- if isinstance(value, list) and self.many:
102
- return self.schema.dump_json(value, exclude_unset=True).decode()
103
- return value.model_dump_json(exclude_unset=True)
104
-
105
- def from_db_value(self, value: Any, expression: Any, connection: Any) -> Any:
106
- data = super().from_db_value(value, expression, connection)
107
- if isinstance(data, str):
108
- return json.loads(data)
109
- return data
110
-
111
- def deconstruct(self):
112
- name, path, args, kwargs = super().deconstruct()
113
- kwargs["schema"] = self.orig_schema
114
- return name, path, args, kwargs
115
-
116
- def formfield(self, **kwargs):
117
- return super().formfield(
118
- **{
119
- "form_class": StructuredJSONFormField,
120
- "schema": self.schema.json_schema(),
121
- "model_name": self.model.__name__,
122
- "file_handler": self.file_handler,
123
- **kwargs,
124
- }
125
- )
@@ -1,202 +0,0 @@
1
- from collections import defaultdict
2
- from inspect import isclass
3
- from typing import Any, Dict, Sequence, Type, Iterable, Union
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
- from django.db.models import Model as DjangoModel
10
- from camomilla.utils.getters import pointed_getter
11
-
12
-
13
- # TODO:
14
- # ::: Actually this is a first draft.
15
- # The system should be more type safe.
16
- # It should handle initialization made with model instance not only dicts
17
- # Neet to check if there are problems with different formats for pks (model instaces or string or dicts)
18
-
19
-
20
- class Cache(dict):
21
- pass
22
-
23
-
24
- class ValueWithCache:
25
- def __init__(self, cache, model, value) -> None:
26
- self.cache: Cache = cache
27
- self.value: Union[Iterable[Union[str, int]], str, int] = value
28
- self.model: Type[DjangoModel] = model
29
-
30
- def retrieve(self):
31
- cache = self.cache.get(self.model)
32
- if hasattr(self.value, '__iter__'):
33
- qs = self.model.objects.filter(pk__in=self.value)
34
- setattr(
35
- qs,
36
- "_result_cache",
37
- [cache[i] for i in self.value if i in cache] if cache else [],
38
- )
39
- return qs
40
- else:
41
- val = cache.get(self.value, None)
42
- if val is None:
43
- return self.model._default_manager.get(pk=self.value)
44
- else:
45
- return val
46
-
47
-
48
- class RelInfo:
49
- FKField: str = "fk"
50
- QSField: str = "qs"
51
- RIField: str = "rel"
52
- RLField: str = "rel_l"
53
-
54
- def __init__(self, model, type, field) -> None:
55
- self.model = model
56
- self.type = type
57
- self.field = field
58
-
59
-
60
- class CacheBuilder:
61
- def __init__(self, related_fields: Dict[str, RelInfo]) -> None:
62
- self.__related_fields__ = related_fields
63
-
64
- @classmethod
65
- def from_model(cls, model: BaseModel) -> "CacheBuilder":
66
- return cls(related_fields=cls.inspect_related_fields(model))
67
-
68
- @classmethod
69
- def inspect_related_fields(cls, model: Type[BaseModel]) -> Dict[str, RelInfo]:
70
- related = {}
71
- for field_name, field in model.model_fields.items():
72
- annotation = field.annotation
73
- origin = get_origin(annotation)
74
- args = get_args(annotation)
75
-
76
- if isclass(origin) and issubclass(origin, ForeignKey):
77
- related[field_name] = RelInfo(
78
- get_type(annotation), RelInfo.FKField, field
79
- )
80
- elif isclass(origin) and issubclass(origin, QuerySet):
81
- related[field_name] = RelInfo(
82
- get_type(annotation), RelInfo.QSField, field
83
- )
84
-
85
- elif isclass(origin) and issubclass(origin, Sequence):
86
- lazy_types = [
87
- _LazyType(arg).evaluate(model)
88
- for arg in args
89
- if isinstance(arg, str)
90
- ]
91
- subclass = next(
92
- (
93
- c
94
- for c in list(args) + lazy_types
95
- if isclass(c) and issubclass(c, BaseModel)
96
- ),
97
- None,
98
- )
99
- if isclass(subclass) and issubclass(subclass, BaseModel):
100
- related[field_name] = RelInfo(subclass, RelInfo.RLField, field)
101
-
102
- elif isclass(annotation) and issubclass(annotation, BaseModel):
103
- related[field_name] = RelInfo(annotation, RelInfo.RIField, field)
104
- return related
105
-
106
- def get_all_fk_data(self, data):
107
- if isinstance(data, Sequence):
108
- fk_data = defaultdict(list)
109
- for index in range(len(data)):
110
- child_fk_data = self.get_all_fk_data(data[index])
111
- for model, touples in child_fk_data.items():
112
- fk_data[model] += [(f"{index}.{t[0]}", t[1]) for t in touples]
113
- return fk_data
114
- return self.get_fk_data(data)
115
-
116
- def get_fk_data(self, data):
117
- fk_data = defaultdict(list)
118
- if not data:
119
- return fk_data
120
- for field_name, info in self.__related_fields__.items():
121
- if info.type == RelInfo.FKField:
122
- value = pointed_getter(data, field_name, None)
123
- if value:
124
- if isinstance(
125
- value, ValueWithCache
126
- ): # needed to break recursive cache builds
127
- fk_data = {}
128
- break
129
- fk_data[info.model].append(
130
- (
131
- field_name,
132
- pointed_getter(value, info.model._meta.pk.attname, value),
133
- )
134
- )
135
- elif info.type == RelInfo.QSField:
136
- value = pointed_getter(data, field_name, [])
137
- if isinstance(value, list):
138
- if any(
139
- True for v in value if isinstance(v, ValueWithCache)
140
- ): # needed to break recursive cache builds
141
- fk_data = {}
142
- break
143
- fk_data[info.model].append(
144
- (
145
- field_name,
146
- [
147
- pointed_getter(i, info.model._meta.pk.attname, i)
148
- for i in value
149
- if i
150
- ],
151
- )
152
- )
153
-
154
- elif info.type == RelInfo.RLField:
155
- values = pointed_getter(data, field_name, [])
156
- if isinstance(values, list):
157
- for index in range(len(values)):
158
- for model, touples in (
159
- self.from_model(info.model)
160
- .get_all_fk_data(values[index])
161
- .items()
162
- ):
163
- fk_data[model] += [
164
- (f"{field_name}.{index}.{t[0]}", t[1]) for t in touples
165
- ]
166
- elif info.type == RelInfo.RIField:
167
- value = pointed_getter(data, field_name, None)
168
- child_fk_data = self.from_model(info.model).get_all_fk_data(value)
169
- for model, touples in child_fk_data.items():
170
- fk_data[model] += [(f"{field_name}.{t[0]}", t[1]) for t in touples]
171
- return fk_data
172
-
173
- def inject_cache(self, data: Any) -> Any:
174
- if not STRUCTURED_FIELD_CACHE_ENABLED:
175
- return data
176
- fk_data = self.get_all_fk_data(data)
177
- plainset = defaultdict(set)
178
- for model, touples in fk_data.items():
179
- for t in touples:
180
- if isinstance(t[1], Sequence):
181
- plainset[model].update(t[1])
182
- else:
183
- plainset[model].add(t[1])
184
- cache = Cache()
185
- for model, values in plainset.items():
186
- models = []
187
- pks = []
188
- for value in values:
189
- if isinstance(value, model):
190
- models.append(value)
191
- else:
192
- pks.append(value)
193
- models_pks = [m.pk for m in models]
194
- pks = [pk for pk in pks if pk not in models_pks]
195
- if len(pks):
196
- models += list(model.objects.filter(pk__in=pks))
197
- cache[model] = {obj.pk: obj for obj in models}
198
-
199
- for model, touples in fk_data.items():
200
- for t in touples:
201
- pointed_setter(data, t[0], ValueWithCache(cache, model, t[1]))
202
- return data
@@ -1,150 +0,0 @@
1
- from typing import Any, Callable, Dict, Generic, TypeVar, Union
2
-
3
- from django.db import models as django_models
4
- from pydantic import SerializationInfo
5
- from pydantic_core import core_schema as cs
6
-
7
- from .utils import get_type
8
-
9
-
10
- T = TypeVar("T", bound=django_models.Model)
11
-
12
-
13
- class ForeignKey(Generic[T]):
14
- @classmethod
15
- def __get_pydantic_core_schema__(
16
- cls, source: Any, handler: Callable[[Any], cs.CoreSchema]
17
- ) -> cs.CoreSchema:
18
- from camomilla.structured.cache import ValueWithCache
19
-
20
- model_class = get_type(source)
21
-
22
- def validate_from_pk(pk: Union[int, str]) -> model_class:
23
- return model_class._default_manager.get(pk=pk)
24
-
25
- int_str_union = cs.union_schema([cs.str_schema(), cs.int_schema()])
26
- from_pk_schema = cs.chain_schema(
27
- [
28
- int_str_union,
29
- cs.no_info_plain_validator_function(validate_from_pk),
30
- ]
31
- )
32
- pk_attname = model_class._meta.pk.attname
33
-
34
- def validate_from_dict(data: Dict[str, Union[str, int]]) -> model_class:
35
- return validate_from_pk(data[pk_attname])
36
-
37
- from_dict_schema = cs.chain_schema(
38
- [
39
- cs.typed_dict_schema({pk_attname: cs.typed_dict_field(int_str_union)}),
40
- cs.no_info_plain_validator_function(validate_from_dict),
41
- ]
42
- )
43
-
44
- from_cache_schema = cs.chain_schema(
45
- [
46
- cs.is_instance_schema(ValueWithCache),
47
- cs.no_info_plain_validator_function(lambda v: v.retrieve()),
48
- ]
49
- )
50
-
51
- def serialize_data(instance, info):
52
- from camomilla.serializers.utils import build_standard_model_serializer
53
-
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
- )
75
-
76
-
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)]
91
- )
92
- return model_class._default_manager.filter(pk__in=values).order_by(
93
- preserved
94
- )
95
-
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
-
130
- if info.mode == "python":
131
- serializer = build_standard_model_serializer(model_class, depth=1)
132
- return serializer(instance=qs, many=True).data
133
- return [x.pk for x in qs]
134
-
135
- return cs.json_or_python_schema(
136
- json_schema=cs.union_schema(
137
- [from_cache_schema, from_pk_list_schema, from_dict_list_schema]
138
- ),
139
- python_schema=cs.union_schema(
140
- [
141
- cs.is_instance_schema(django_models.QuerySet),
142
- from_cache_schema,
143
- from_pk_list_schema,
144
- from_dict_list_schema,
145
- ]
146
- ),
147
- serialization=cs.plain_serializer_function_ser_schema(
148
- serialize_data, info_arg=True
149
- ),
150
- )