django-camomilla-cms 6.0.0b14__py2.py3-none-any.whl → 6.0.0b15__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.
@@ -1,35 +1,35 @@
1
- camomilla/__init__.py,sha256=QPq2GvO5-zT08-jIOFOjDeg33wtHP-Q3zFfpvPWr-YM,251
1
+ camomilla/__init__.py,sha256=NkhtGSnemCX19_YBz80aKvqoqwSnTGEtwPHHnB-JjcI,251
2
2
  camomilla/apps.py,sha256=eUwb9ynyiRAc5OXgt7ZsAdhsCOnPCpNdIFYMheNeN-o,532
3
3
  camomilla/authentication.py,sha256=jz6tQT4PPEu-_JLox1LZrOy7EiWBb9MWaObK63MJGus,855
4
4
  camomilla/context_processors.py,sha256=cGowjDZ-oDGYn1j2Pj5QDGCqnzXAOdOwp5dmzin_FTc,165
5
5
  camomilla/defaults.py,sha256=VNQ_sbxu09AyFGNpUUYypIAyhlBhEORD36BBNj7e73I,1220
6
- camomilla/dynamic_pages_urls.py,sha256=HBI8gCe0Vd9mE74QrIVP57K4ThWopEW4r7jZnFHulTk,1008
6
+ camomilla/dynamic_pages_urls.py,sha256=14H47KlSxmINoJyrsul0KR7Qvk6-1uoXrD1ReV_W4h8,1056
7
7
  camomilla/exceptions.py,sha256=gLniAsK_pmsNNKGMv5Z384LXVbM8oeHcOwz4F91u1LY,111
8
8
  camomilla/model_api.py,sha256=VC3tNQucS3-KVtdZNWkkbkJn8vtl_cBKYTcC3QU6PpU,2253
9
9
  camomilla/parsers.py,sha256=fL8XGCGPxJIZNZkPdGtnPSbDP-6-yzGOCVMuLPjkx9Y,1975
10
10
  camomilla/permissions.py,sha256=9NlBO4JMmg36vXCUjPNyq6uZxhkdrnXyIbJVLtWhGWE,1813
11
- camomilla/settings.py,sha256=Xxs6_fcx-0b-5yKZL7XqYwan48dxIzQmm2lxuijvld0,3428
11
+ camomilla/settings.py,sha256=GDJXDcEmyobGrP4MmEH3MAt1KBK74gXmmRR8kFuJzp0,3306
12
12
  camomilla/sitemap.py,sha256=U2t5TwhB_-sEscmQZ69PZ5st3bIap8NRxzWEvCgB130,786
13
- camomilla/translation.py,sha256=IDoC48xhH49lFAbOq19kOskgjECv4kRCm7bEI80jOL4,1260
13
+ camomilla/translation.py,sha256=-Y0Toy1D__wxMovipWVIuNx3TrfhDGffrduWYFUmYA4,1269
14
14
  camomilla/urls.py,sha256=YFUYcPuvnNSTnfJa_JByRWAPLgc4_R4LgqCMrmkbFpE,2217
15
15
  camomilla/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
16
  camomilla/contrib/modeltranslation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
17
  camomilla/contrib/modeltranslation/hvad_migration.py,sha256=UanLVSlYYd_ykMC4STtYkkUMJqlFQQYhbWkoV47alM0,5403
18
18
  camomilla/contrib/rest_framework/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
19
  camomilla/contrib/rest_framework/serializer.py,sha256=G6SKes-8RkJKDupCNN0B2B1BQuYqS0y820oGYotxPcY,5924
20
- camomilla/fields/__init__.py,sha256=VTCKK0PsJxpcgWrcqPYtWaSFhOOO2ZL56UIdla2IxF8,400
20
+ camomilla/fields/__init__.py,sha256=gKrJwHvUA3q_wu-OVV0hrRZmFkT4znMHrmZtpriDumw,323
21
21
  camomilla/fields/json.py,sha256=tWEDn6kwTP6pNB53djxuVPu2d57m9cIDc4ccCEfUbDQ,1938
22
22
  camomilla/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
23
  camomilla/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
24
  camomilla/management/commands/regenerate_thumbnails.py,sha256=pKToASR8p8TJezGpFfuylsAHtriNueJ7xqJJxq55adY,496
25
25
  camomilla/managers/__init__.py,sha256=Zwp28E1RKafIl0FqcUi1YNHxF19IsMIvhlhS-Njg9Mw,60
26
- camomilla/managers/pages.py,sha256=6L35RPzfJbEntjWfEzw0b9ruhOwoVaITUD-RyxuK5yc,1857
26
+ camomilla/managers/pages.py,sha256=8SXd0iUt2UA59lEn9DzJC41SkeU7Vc_tFe9sJhvfyww,1144
27
27
  camomilla/models/__init__.py,sha256=y7Q34AGhQ1weJUKWb6XjiyUbwRnJeylOBGMErU0wqYg,147
28
28
  camomilla/models/article.py,sha256=LgkZgRsubtDV6NwBz8E2bIgKD6H3I-1QLAxEan5TYYs,1139
29
29
  camomilla/models/content.py,sha256=mIgtifb_WMIt58we5u6qWZemHvuDN1zZaBeCyzHL78A,956
30
30
  camomilla/models/media.py,sha256=7TD_0nhNoi8P-Gmsr0Kag4eGaxMXuTfQOFl53Mb03cQ,6854
31
- camomilla/models/menu.py,sha256=rCq0MfHLGM7-7fDVk--AixQZb6_C1gkWMWffKs6QnK4,2841
32
- camomilla/models/page.py,sha256=X4Mo4cTbAYyWlpVFtjRjyvI_wE2-zcBU1MR8mm26NQI,15671
31
+ camomilla/models/menu.py,sha256=1eUOjBTZQklPgHXrQTpPsRKjT4QNFSoQ8diUVNRymfc,3589
32
+ camomilla/models/page.py,sha256=O4EGWjzk4cHGzmtkJr2nSWG51qLAlqt5B9fkoyEatrs,17287
33
33
  camomilla/models/mixins/__init__.py,sha256=c2NixqvrIX4E9WGRqQbylXlqBWDXEqN9mzs_dpB0hFQ,1248
34
34
  camomilla/openapi/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
35
35
  camomilla/openapi/schema.py,sha256=kYNtIKnLx4SIj893umIE6u-q3HH9hYH9O4zdZCSOe7U,2100
@@ -41,21 +41,16 @@ camomilla/serializers/menu.py,sha256=TdoyXs40PqxNevnRbBbYOOX9rUv9zQGiHFNduspaZnw
41
41
  camomilla/serializers/page.py,sha256=NNjEypVYu_9iKqdHV_-61ea37gxiHlDP5gsloV_i6yg,1834
42
42
  camomilla/serializers/user.py,sha256=CzrHiVRvYYWNE4eNpCNKtJB7DjVqHHwIcP4NUBXMHSo,3706
43
43
  camomilla/serializers/utils.py,sha256=sE6Fe8vgGuEWYTox7_ATC6zwcUZ7t2EqkemuP5tWp00,1001
44
- camomilla/serializers/validators.py,sha256=QtlmGyJiKaKyMBqBZ1W2VAjCoLi1vhPUnLOo7DvZpsM,1775
44
+ camomilla/serializers/validators.py,sha256=EX-EFg6afFr8viPKq-LHeEtRAKPVtO4kIlEymY1j2HA,2043
45
45
  camomilla/serializers/base/__init__.py,sha256=HSDBPXQ3ldX0pixDbNLna5LSUQ_TfFZ4JWKRm6OpWcU,793
46
- camomilla/serializers/fields/__init__.py,sha256=P3WFxrNGNBKTYqXi-CA4e5HLOYWo6Yt6izQcwyUhBPU,642
46
+ camomilla/serializers/fields/__init__.py,sha256=T_ONowldBTPfgbwFmoefY1r3TQiB2sbWQPpFvBSnzqE,681
47
47
  camomilla/serializers/fields/file.py,sha256=yjKMho2ti9TIAzo6nwyLnNPJ6GVUumL2wxhegvYqI2o,800
48
- camomilla/serializers/fields/json.py,sha256=KHSxklGr1dLS6yd8nkVo_S7BiJKVMJ8Iv8vXyR8aCVQ,1870
48
+ camomilla/serializers/fields/json.py,sha256=LzSVjVXjRR5mbZ94tVq-wdX3_B5yzv0h7pPEFZVOL1s,1874
49
49
  camomilla/serializers/fields/related.py,sha256=Lofgk582KAb0EIK20jtds538O_y-yRHo644ND_VlXrM,4747
50
- camomilla/serializers/mixins/__init__.py,sha256=lvPCwDNZtQwdewIQUVI-D9f2UuwxjVs29TNTozcahzs,7377
50
+ camomilla/serializers/mixins/__init__.py,sha256=prhA91hMrLezm-mIYc3yHJXefM346okZQ8OS8IxxqhI,7114
51
51
  camomilla/storages/__init__.py,sha256=ytGgX59Ar8vFfYz7eV8z-j5yO_5FqxdZM25iyLnJuSA,131
52
52
  camomilla/storages/optimize.py,sha256=YKbhSbTaWNbTFIRTRWdilD3w8b_LjMLOmE6yk2lrbo4,2745
53
53
  camomilla/storages/overwrite.py,sha256=CO_zSZxr321eohYFuzPjDMK7WMFnvvwlIigIPabU_eE,343
54
- camomilla/structured/__init__.py,sha256=mX0mruLSrovzKlIk4WLBw-eFOuxbFVh9ASA5lR5OVF4,4338
55
- camomilla/structured/cache.py,sha256=Fi6F3oQzq8XcbGglKes-U1SskFzTnvowZY82S5eeOCU,7845
56
- camomilla/structured/fields.py,sha256=INjb6G67k4nmeGQibU0WBHUUpRJQbYmcX74sWvBLMUs,5237
57
- camomilla/structured/models.py,sha256=us03rOU2VtdhH4MMHIGkb11Wr6ItxPmB7ZUL1QepWmg,1790
58
- camomilla/structured/utils.py,sha256=nJlRFuYkBOgqxMWEpCM5qGu2m7j9TCBHB8SMmZPbCKM,3447
59
54
  camomilla/templates/admin/camomilla/page/change_form.html,sha256=ig7rRUtylDZMINBQuVPpZLmeB4sOTV_VtqnTgzAyxEo,251
60
55
  camomilla/templates/defaults/base.html,sha256=pklt7Pif3g9d7gwgRxCQj7gniJaHD14ZqZID_xIlC0A,6638
61
56
  camomilla/templates/defaults/articles/default.html,sha256=1f89jBvNtTa1mPAbC91yy8CzeAjTWO3hhQsTuQW5OKg,239
@@ -69,8 +64,8 @@ camomilla/templates_context/rendering.py,sha256=GfTR45_gC7WT7zTKPVXkBDwe22uF63A-
69
64
  camomilla/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
70
65
  camomilla/templatetags/camomilla_filters.py,sha256=35x0-cWrRHeLhqypSLlJzEFY_fQDcRHiwZQpFgIsspE,692
71
66
  camomilla/templatetags/menus.py,sha256=pLFeJ1UAm3flsU9TNo2iQk4otSwZuJPr86Nf408dZ-8,862
72
- camomilla/theme/__init__.py,sha256=UVNasDCHfmeetQsfJsCQ1TN3YgarCjDERgSZX0OwKT8,30
73
- camomilla/theme/admin.py,sha256=R3QAfqaF9mhzwfyb_m2_hqTQ2hBcNvQts1MWUX4dUUM,2462
67
+ camomilla/theme/__init__.py,sha256=0q-mhm8VNgYidC6IDdFUN4eArRIb2Wf1Wf1vymdu_Fg,30
68
+ camomilla/theme/admin.py,sha256=7TgNXOfIy4awtV-KkGEKsmai94qhBiykB0uJWwTZy8U,2461
74
69
  camomilla/theme/apps.py,sha256=ecqG8ntWdOighYOEHQOMVQpa5g_1WEzzpaaGjnOi9uA,1195
75
70
  camomilla/theme/static/admin/css/responsive.css,sha256=yGq6qXrr8xEVsXTnprIBgkX-sMGZrNf0Kkh-xDxf6yE,157
76
71
  camomilla/theme/static/admin/img/favicon.ico,sha256=qpKv_2MaGILvyihnD1Vq9Yk-ZXGkxWTW26ciMeBFMYU,15406
@@ -81,6 +76,7 @@ camomilla/utils/__init__.py,sha256=Ui7nzSh45UMMFtCGF1xFHKyJMNWflG_Nx72HAJHJHFM,1
81
76
  camomilla/utils/getters.py,sha256=6j18grFAZ8BC70SriycFDTQFxTnudGn0uKGA83_Rclk,798
82
77
  camomilla/utils/normalization.py,sha256=RDCZtjwpEEwjvfUjQl2bEWFKw7NxTzkXco72VeO2M9w,255
83
78
  camomilla/utils/seo.py,sha256=8p_a_TGgohenpJb094tT4mMxbn2xzW0qDILuTnjNocM,3324
79
+ camomilla/utils/setters.py,sha256=LV57SM65rL1_ZQkVzk9al_Q13lndVywXLkqgfIvgS0Y,915
84
80
  camomilla/utils/templates.py,sha256=Lv4-5019cnM30HmdZnYWiU5gxry-eFZVAhwOofGQRDs,598
85
81
  camomilla/utils/translation.py,sha256=HG70ki9MTBVTdAYgi6lqqFQtNm30VTvGSYepXpyb3_Y,2477
86
82
  camomilla/views/__init__.py,sha256=94QuOnnbfMMb17mruO2ydUt286-8zBmDxEPWrJv5Wog,178
@@ -89,7 +85,7 @@ camomilla/views/contents.py,sha256=JxvnmgeK8JEmCMLzVG8pVq2DwvmjXtgnIdsDnn74tA4,1
89
85
  camomilla/views/decorators.py,sha256=hR--nTGQn2mMKDrWn-0Ildzbsvp11OfoWAtedKEzmiA,982
90
86
  camomilla/views/languages.py,sha256=Rt_X7s3dbDBv4dxsQ9fnav_u0TAzzo8fGKBBx3esDsg,441
91
87
  camomilla/views/medias.py,sha256=S4Ak4JI6XCSu3BCZx8KOotX7TBfrIU82GPXgyP3KcZA,3001
92
- camomilla/views/menus.py,sha256=wKxBTaLW-YEJrE0RIoi_1oUpymskbBTqQvJh1ZioN5c,3382
88
+ camomilla/views/menus.py,sha256=o2W6oQNiB__jHw5rbGaitEENxuRk1_mRoLCKyb30qPk,3352
93
89
  camomilla/views/pages.py,sha256=KrBsKT0Z-rkBha8K3P6fVDt4vArQBxsc9rZ28j8XW9w,994
94
90
  camomilla/views/tags.py,sha256=XcYRlcBFSPPY32lt7POb6fWPJL_8HsTo5JcHcAOiOKw,479
95
91
  camomilla/views/users.py,sha256=_fvsKOEtep4SJLvMva2_q-HdLQT_1KlFNt4wcl3xCJk,3130
@@ -102,8 +98,8 @@ tests/test_api.py,sha256=DTXtvNNRwvZvxGKi6LyCzhpyU1OzPWGSbBUfRL3-Nck,2101
102
98
  tests/test_camomilla_filters.py,sha256=_W9CcwsEyewMBvnNYKk4CVqrkXxRYejle3mjiBS7hqk,1848
103
99
  tests/test_models.py,sha256=WJs8lxWZWn1l7X3a_QFVc8fF5LHTsI8bc3uhQe6-o-Q,684
104
100
  tests/test_utils.py,sha256=0tdEDBaBkyjAXpbqP0SpFcfgO56MV2TWZmk9xpjR7J0,3613
105
- django_camomilla_cms-6.0.0b14.dist-info/LICENSE,sha256=kVS7zDrNkav2hLLXbOJwVdonY2ToApTK3khyJagGQoQ,1063
106
- django_camomilla_cms-6.0.0b14.dist-info/METADATA,sha256=Epgi0FSONdA7m9MZnYCTufligreZhHcNT3OD0jOgKo4,2410
107
- django_camomilla_cms-6.0.0b14.dist-info/WHEEL,sha256=DZajD4pwLWue70CAfc7YaxT1wLUciNBvN_TTcvXpltE,110
108
- django_camomilla_cms-6.0.0b14.dist-info/top_level.txt,sha256=G9VIGBmMMqC7JEckoTgXKmC6T2BR75QRkqRnngw1_lo,16
109
- django_camomilla_cms-6.0.0b14.dist-info/RECORD,,
101
+ django_camomilla_cms-6.0.0b15.dist-info/LICENSE,sha256=kVS7zDrNkav2hLLXbOJwVdonY2ToApTK3khyJagGQoQ,1063
102
+ django_camomilla_cms-6.0.0b15.dist-info/METADATA,sha256=_k6mnVPePkm5V8-NNr4LKbft-k1_vte_CM6lEgTB32U,2390
103
+ django_camomilla_cms-6.0.0b15.dist-info/WHEEL,sha256=TJ49d73sNs10F0aze1W_bTW2P_X7-F4YXOlBqoqA-jY,109
104
+ django_camomilla_cms-6.0.0b15.dist-info/top_level.txt,sha256=G9VIGBmMMqC7JEckoTgXKmC6T2BR75QRkqRnngw1_lo,16
105
+ django_camomilla_cms-6.0.0b15.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.43.0)
2
+ Generator: setuptools (75.2.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py2-none-any
5
5
  Tag: py3-none-any
@@ -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
- )
@@ -1,47 +0,0 @@
1
- from inspect import isclass
2
- from typing import Any, Dict, Tuple, get_origin
3
-
4
- import pydantic._internal._model_construction
5
- from django.db.models import Model as DjangoModel
6
- from pydantic import BaseModel as PyDBaseModel
7
- from pydantic import Field, model_validator
8
- from typing_extensions import Annotated
9
-
10
- from .fields import ForeignKey, QuerySet
11
- from .utils import get_type, map_method_aliases
12
-
13
-
14
- class BaseModelMeta(pydantic._internal._model_construction.ModelMetaclass):
15
- def __new__(
16
- mcs, name: str, bases: Tuple[type], namespaces: Dict[str, Any], **kwargs
17
- ):
18
- annotations: dict = namespaces.get("__annotations__", {})
19
- for base in bases:
20
- for base_ in base.__mro__:
21
- if base_ is PyDBaseModel:
22
- break
23
- annotations.update(base_.__annotations__)
24
-
25
- for field in annotations:
26
- annotation = annotations[field]
27
- origin = get_origin(annotation)
28
- if isclass(annotation) and issubclass(annotation, DjangoModel):
29
- annotations[field] = ForeignKey[annotation]
30
- elif isclass(origin) and issubclass(origin, QuerySet):
31
- annotations[field] = Annotated[
32
- annotation,
33
- Field(default_factory=get_type(annotation)._default_manager.none),
34
- ]
35
- namespaces["__annotations__"] = annotations
36
- return map_method_aliases(
37
- super().__new__(mcs, name, bases, namespaces, **kwargs)
38
- )
39
-
40
-
41
- class BaseModel(PyDBaseModel, metaclass=BaseModelMeta):
42
- @model_validator(mode="before")
43
- @classmethod
44
- def build_cache(cls, data: Any) -> Any:
45
- from camomilla.structured.cache import CacheBuilder
46
-
47
- return CacheBuilder.from_model(cls).inject_cache(data)