django-camomilla-cms 6.0.0b13__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.
- camomilla/__init__.py +1 -1
- camomilla/dynamic_pages_urls.py +2 -1
- camomilla/fields/__init__.py +1 -3
- camomilla/managers/pages.py +23 -23
- camomilla/models/menu.py +21 -10
- camomilla/models/page.py +75 -38
- camomilla/serializers/fields/__init__.py +2 -2
- camomilla/serializers/fields/json.py +3 -2
- camomilla/serializers/mixins/__init__.py +5 -12
- camomilla/serializers/validators.py +9 -5
- camomilla/settings.py +0 -4
- camomilla/theme/__init__.py +1 -1
- camomilla/theme/admin.py +1 -1
- camomilla/translation.py +1 -1
- camomilla/utils/setters.py +37 -0
- camomilla/views/menus.py +0 -1
- {django_camomilla_cms-6.0.0b13.dist-info → django_camomilla_cms-6.0.0b15.dist-info}/METADATA +2 -3
- {django_camomilla_cms-6.0.0b13.dist-info → django_camomilla_cms-6.0.0b15.dist-info}/RECORD +21 -25
- {django_camomilla_cms-6.0.0b13.dist-info → django_camomilla_cms-6.0.0b15.dist-info}/WHEEL +1 -1
- camomilla/structured/__init__.py +0 -125
- camomilla/structured/cache.py +0 -202
- camomilla/structured/fields.py +0 -150
- camomilla/structured/models.py +0 -47
- camomilla/structured/utils.py +0 -114
- {django_camomilla_cms-6.0.0b13.dist-info → django_camomilla_cms-6.0.0b15.dist-info}/LICENSE +0 -0
- {django_camomilla_cms-6.0.0b13.dist-info → django_camomilla_cms-6.0.0b15.dist-info}/top_level.txt +0 -0
camomilla/__init__.py
CHANGED
camomilla/dynamic_pages_urls.py
CHANGED
@@ -7,7 +7,8 @@ from .models import Page
|
|
7
7
|
|
8
8
|
|
9
9
|
def fetch(request, *args, **kwargs):
|
10
|
-
|
10
|
+
can_preview = request.user.is_staff or settings.DEBUG
|
11
|
+
preview = can_preview and request.GET.get("preview", False)
|
11
12
|
append_slash = getattr(django_settings, "APPEND_SLASH", True)
|
12
13
|
if append_slash and not request.path.endswith("/"):
|
13
14
|
return redirect(request.path + "/")
|
camomilla/fields/__init__.py
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
from django.db import models
|
2
2
|
|
3
|
-
from camomilla.structured import StructuredJSONField
|
4
|
-
|
5
3
|
from .json import ArrayField, JSONField
|
6
4
|
|
7
5
|
ORDERING_ACCEPTED_FIELDS = (
|
@@ -12,4 +10,4 @@ ORDERING_ACCEPTED_FIELDS = (
|
|
12
10
|
models.SmallIntegerField,
|
13
11
|
)
|
14
12
|
|
15
|
-
__all__ = ["
|
13
|
+
__all__ = ["JSONField", "ArrayField", "ORDERING_ACCEPTED_FIELDS"]
|
camomilla/managers/pages.py
CHANGED
@@ -1,31 +1,31 @@
|
|
1
|
-
from typing import Any, Tuple
|
2
1
|
from django.db.models.query import QuerySet
|
3
|
-
from django.
|
2
|
+
from django.core.exceptions import ObjectDoesNotExist
|
4
3
|
from django.apps import apps
|
5
4
|
|
6
5
|
URL_NODE_RELATED_NAME = "%(app_label)s_%(class)s"
|
7
6
|
|
8
7
|
|
9
8
|
class PageQuerySet(QuerySet):
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
9
|
+
|
10
|
+
__UrlNodeModel = None
|
11
|
+
|
12
|
+
@property
|
13
|
+
def UrlNodeModel(self):
|
14
|
+
if not self.__UrlNodeModel:
|
15
|
+
self.__UrlNodeModel = apps.get_model("camomilla", "UrlNode")
|
16
|
+
return self.__UrlNodeModel
|
17
|
+
|
18
|
+
def get_permalink_kwargs(self, kwargs):
|
19
|
+
return list(set(kwargs.keys()).intersection(set(self.UrlNodeModel.LANG_PERMALINK_FIELDS + ["permalink"])))
|
20
|
+
|
21
|
+
def get(self, *args, **kwargs):
|
22
|
+
permalink_args = self.get_permalink_kwargs(kwargs)
|
23
|
+
if len(permalink_args):
|
24
|
+
try:
|
25
|
+
node = self.UrlNodeModel.objects.get(**{arg:kwargs.pop(arg) for arg in permalink_args})
|
26
|
+
kwargs["url_node"] = node
|
27
|
+
except ObjectDoesNotExist:
|
28
|
+
raise self.model.DoesNotExist(
|
29
|
+
"%s matching query does not exist." % self.model._meta.object_name
|
17
30
|
)
|
18
|
-
|
19
|
-
page = self.get(**kwargs)
|
20
|
-
if page.pk != url_node.page.pk:
|
21
|
-
raise self.model.MultipleObjectsReturned(
|
22
|
-
"got more than one %s object for the same permalink: %s"
|
23
|
-
% (
|
24
|
-
self.model._meta.object_name,
|
25
|
-
kwargs["permalink"],
|
26
|
-
)
|
27
|
-
)
|
28
|
-
return url_node.page, False
|
29
|
-
kwargs["url_node"] = url_node
|
30
|
-
return super().get_or_create(defaults, **kwargs)
|
31
|
-
return super().get_or_create(defaults, **kwargs)
|
31
|
+
return super(PageQuerySet, self).get(*args, **kwargs)
|
camomilla/models/menu.py
CHANGED
@@ -12,9 +12,12 @@ from pydantic import (
|
|
12
12
|
computed_field,
|
13
13
|
model_serializer,
|
14
14
|
)
|
15
|
-
from
|
16
|
-
from
|
15
|
+
from structured.pydantic.models import BaseModel
|
16
|
+
from structured.fields import StructuredJSONField
|
17
|
+
from structured.pydantic.fields import QuerySet
|
18
|
+
from camomilla.models.page import UrlNode, AbstractPage
|
17
19
|
from typing import Optional, Union, Callable, List
|
20
|
+
from django.db.models.base import Model as DjangoModel
|
18
21
|
|
19
22
|
|
20
23
|
class LinkTypes(str, Enum):
|
@@ -22,21 +25,29 @@ class LinkTypes(str, Enum):
|
|
22
25
|
static = "ST"
|
23
26
|
|
24
27
|
|
25
|
-
class MenuNodeLink(
|
28
|
+
class MenuNodeLink(BaseModel):
|
26
29
|
link_type: LinkTypes = LinkTypes.static
|
27
30
|
static: str = None
|
28
|
-
content_type:
|
29
|
-
|
31
|
+
content_type: ContentType = None
|
32
|
+
page: AbstractPage = None
|
30
33
|
url_node: UrlNode = None
|
31
34
|
|
32
35
|
@model_serializer(mode="wrap", when_used="json")
|
33
36
|
def update_relational(self, handler: Callable, info: SerializationInfo):
|
34
37
|
if self.link_type == LinkTypes.relational:
|
35
|
-
if self.content_type and self.
|
36
|
-
|
38
|
+
if self.content_type and self.page:
|
39
|
+
if isinstance(self.page, DjangoModel) and not self.page._meta.abstract:
|
40
|
+
self.content_type = ContentType.objects.get_for_model(self.page.__class__)
|
41
|
+
ctype_id = getattr(self.content_type, "pk", self.content_type)
|
42
|
+
page_id = getattr(self.page, "pk", self.page)
|
43
|
+
c_type = ContentType.objects.filter(pk=ctype_id).first()
|
37
44
|
model = c_type and c_type.model_class()
|
38
|
-
page = model and model.objects.filter(pk=
|
45
|
+
page = model and model.objects.filter(pk=page_id).first()
|
39
46
|
self.url_node = page and page.url_node
|
47
|
+
elif self.url_node:
|
48
|
+
url_node_id = getattr(self.url_node, "pk", self.url_node)
|
49
|
+
self.page = UrlNode.objects.filter(pk=url_node_id).first().page
|
50
|
+
self.content_type = ContentType.objects.get_for_model(self.page.__class__)
|
40
51
|
return handler(self)
|
41
52
|
|
42
53
|
def get_url(self, request=None):
|
@@ -51,7 +62,7 @@ class MenuNodeLink(structured.BaseModel):
|
|
51
62
|
return self.get_url()
|
52
63
|
|
53
64
|
|
54
|
-
class MenuNode(
|
65
|
+
class MenuNode(BaseModel):
|
55
66
|
id: str = Field(default_factory=uuid4)
|
56
67
|
meta: dict = {}
|
57
68
|
nodes: List["MenuNode"] = []
|
@@ -63,7 +74,7 @@ class Menu(models.Model):
|
|
63
74
|
key = models.CharField(max_length=200, unique=True, editable=False)
|
64
75
|
available_classes = models.JSONField(default=dict, editable=False)
|
65
76
|
enabled = models.BooleanField(default=True)
|
66
|
-
nodes =
|
77
|
+
nodes = StructuredJSONField(default=list, schema=MenuNode)
|
67
78
|
|
68
79
|
class Meta:
|
69
80
|
verbose_name = _("menu")
|
camomilla/models/page.py
CHANGED
@@ -2,7 +2,6 @@ from typing import Sequence, Tuple
|
|
2
2
|
from uuid import uuid4
|
3
3
|
|
4
4
|
from django.core.exceptions import ObjectDoesNotExist
|
5
|
-
from django.core.validators import RegexValidator
|
6
5
|
|
7
6
|
from django.db import ProgrammingError, OperationalError, models, transaction
|
8
7
|
from django.db.models.signals import post_delete
|
@@ -13,6 +12,7 @@ from django.utils import timezone
|
|
13
12
|
from django.utils.functional import lazy
|
14
13
|
from django.utils.text import slugify
|
15
14
|
from django.utils.translation import gettext_lazy as _
|
15
|
+
from django.utils.translation import get_language
|
16
16
|
|
17
17
|
from camomilla.managers.pages import PageQuerySet
|
18
18
|
from camomilla.models.mixins import MetaMixin, SeoMixin
|
@@ -29,8 +29,13 @@ from camomilla.utils.getters import pointed_getter
|
|
29
29
|
from camomilla import settings
|
30
30
|
from camomilla.templates_context.rendering import ctx_registry
|
31
31
|
from django.conf import settings as django_settings
|
32
|
+
from modeltranslation.settings import AVAILABLE_LANGUAGES
|
33
|
+
from modeltranslation.utils import build_localized_fieldname
|
32
34
|
|
33
35
|
|
36
|
+
class UrlPathValidator():
|
37
|
+
pass
|
38
|
+
|
34
39
|
def GET_TEMPLATE_CHOICES():
|
35
40
|
return [(t, t) for t in get_all_templates_files()]
|
36
41
|
|
@@ -111,6 +116,14 @@ class UrlNodeManager(models.Manager):
|
|
111
116
|
|
112
117
|
|
113
118
|
class UrlNode(models.Model):
|
119
|
+
|
120
|
+
LANG_PERMALINK_FIELDS = [
|
121
|
+
build_localized_fieldname("permalink", lang)
|
122
|
+
for lang in AVAILABLE_LANGUAGES
|
123
|
+
if settings.ENABLE_TRANSLATIONS
|
124
|
+
]
|
125
|
+
|
126
|
+
|
114
127
|
permalink = models.CharField(max_length=400, unique=True, null=True)
|
115
128
|
related_name = models.CharField(max_length=200)
|
116
129
|
objects = UrlNodeManager()
|
@@ -140,6 +153,23 @@ class UrlNode(models.Model):
|
|
140
153
|
if self.routerlink == "/":
|
141
154
|
return ""
|
142
155
|
return self.routerlink
|
156
|
+
|
157
|
+
@staticmethod
|
158
|
+
def sanitize_permalink(permalink):
|
159
|
+
if isinstance(permalink, str):
|
160
|
+
p_parts = permalink.split("/")
|
161
|
+
permalink = "/".join([slugify(p, allow_unicode=True).strip() for p in p_parts])
|
162
|
+
if not permalink.startswith("/"):
|
163
|
+
permalink = f"/{permalink}"
|
164
|
+
return permalink
|
165
|
+
|
166
|
+
def save(self, *args, **kwargs) -> None:
|
167
|
+
for lang_p_field in UrlNode.LANG_PERMALINK_FIELDS:
|
168
|
+
setattr(self, lang_p_field, UrlNode.sanitize_permalink(getattr(self, lang_p_field)))
|
169
|
+
super().save(*args, **kwargs)
|
170
|
+
|
171
|
+
def __str__(self) -> str:
|
172
|
+
return self.permalink
|
143
173
|
|
144
174
|
|
145
175
|
PAGE_CHILD_RELATED_NAME = "%(app_label)s_%(class)s_child_pages"
|
@@ -154,11 +184,28 @@ PAGE_STATUS = (
|
|
154
184
|
|
155
185
|
|
156
186
|
class PageBase(models.base.ModelBase):
|
187
|
+
"""
|
188
|
+
This models comes to implement a language based permalink logic
|
189
|
+
"""
|
190
|
+
def perm_prop_factory(permalink_field):
|
191
|
+
def getter(_self):
|
192
|
+
return getattr(_self, f"__{permalink_field}", getattr(_self.url_node or object(), permalink_field, None))
|
193
|
+
def setter(_self, value:str):
|
194
|
+
setattr(_self, f"__{permalink_field}", value)
|
195
|
+
return getter, setter
|
196
|
+
|
157
197
|
def __new__(cls, name, bases, attrs, **kwargs):
|
158
198
|
attr_meta = attrs.pop("PageMeta", None)
|
159
199
|
new_class = super().__new__(cls, name, bases, attrs, **kwargs)
|
160
200
|
page_meta = attr_meta or getattr(new_class, "PageMeta", None)
|
161
201
|
base_page_meta = getattr(new_class, "_page_meta", None)
|
202
|
+
for lang_p_field in UrlNode.LANG_PERMALINK_FIELDS:
|
203
|
+
computed_prop = property(*cls.perm_prop_factory(lang_p_field))
|
204
|
+
setattr(new_class, lang_p_field, computed_prop)
|
205
|
+
setattr(new_class, "permalink", property(
|
206
|
+
lambda _self: getattr(_self, build_localized_fieldname("permalink", get_language()), None),
|
207
|
+
lambda _self, value: setattr(_self, f"__{build_localized_fieldname('permalink', get_language())}", value)
|
208
|
+
))
|
162
209
|
if page_meta:
|
163
210
|
for name, value in getattr(base_page_meta, "__dict__", {}).items():
|
164
211
|
if name not in page_meta.__dict__:
|
@@ -167,16 +214,6 @@ class PageBase(models.base.ModelBase):
|
|
167
214
|
return new_class
|
168
215
|
|
169
216
|
|
170
|
-
class UrlPathValidator(RegexValidator):
|
171
|
-
|
172
|
-
regex = r"^[a-zA-Z0-9_\-\/]+[^\/]$"
|
173
|
-
message = _(
|
174
|
-
"Enter a valid 'slug' consisting of lowercase letters, numbers, "
|
175
|
-
"underscores, hyphens and slashes."
|
176
|
-
)
|
177
|
-
flags = 0
|
178
|
-
|
179
|
-
|
180
217
|
class AbstractPage(SeoMixin, MetaMixin, models.Model, metaclass=PageBase):
|
181
218
|
date_created = models.DateTimeField(auto_now_add=True)
|
182
219
|
date_updated_at = models.DateTimeField(auto_now=True)
|
@@ -188,9 +225,7 @@ class AbstractPage(SeoMixin, MetaMixin, models.Model, metaclass=PageBase):
|
|
188
225
|
editable=False,
|
189
226
|
)
|
190
227
|
breadcrumbs_title = models.CharField(max_length=128, null=True, blank=True)
|
191
|
-
|
192
|
-
max_length=150, null=True, blank=True, validators=[UrlPathValidator()]
|
193
|
-
)
|
228
|
+
autopermalink = models.BooleanField(default=True)
|
194
229
|
status = models.CharField(
|
195
230
|
max_length=3,
|
196
231
|
choices=PAGE_STATUS,
|
@@ -211,6 +246,19 @@ class AbstractPage(SeoMixin, MetaMixin, models.Model, metaclass=PageBase):
|
|
211
246
|
)
|
212
247
|
|
213
248
|
objects = PageQuerySet.as_manager()
|
249
|
+
|
250
|
+
__cached_db_instance: "AbstractPage" = None
|
251
|
+
|
252
|
+
@property
|
253
|
+
def db_instance(self):
|
254
|
+
if self.__cached_db_instance is None:
|
255
|
+
self.__cached_db_instance = self.get_db_instance()
|
256
|
+
return self.__cached_db_instance
|
257
|
+
|
258
|
+
def get_db_instance(self):
|
259
|
+
if self.pk:
|
260
|
+
return self.__class__.objects.get(pk=self.pk)
|
261
|
+
return None
|
214
262
|
|
215
263
|
def __init__(self, *args, **kwargs):
|
216
264
|
super(AbstractPage, self).__init__(*args, **kwargs)
|
@@ -240,10 +288,6 @@ class AbstractPage(SeoMixin, MetaMixin, models.Model, metaclass=PageBase):
|
|
240
288
|
def model_info(self) -> dict:
|
241
289
|
return {"app_label": self._meta.app_label, "class": self._meta.model_name}
|
242
290
|
|
243
|
-
@property
|
244
|
-
def permalink(self) -> str:
|
245
|
-
return self.url_node and self.url_node.permalink
|
246
|
-
|
247
291
|
@property
|
248
292
|
def routerlink(self) -> str:
|
249
293
|
return self.url_node and self.url_node.routerlink
|
@@ -252,7 +296,7 @@ class AbstractPage(SeoMixin, MetaMixin, models.Model, metaclass=PageBase):
|
|
252
296
|
def breadcrumbs(self) -> Sequence[dict]:
|
253
297
|
breadcrumb = {
|
254
298
|
"permalink": self.permalink,
|
255
|
-
"title": self.breadcrumbs_title or self.title or
|
299
|
+
"title": self.breadcrumbs_title or self.title or "",
|
256
300
|
}
|
257
301
|
if self.parent:
|
258
302
|
return self.parent.breadcrumbs + [breadcrumb]
|
@@ -291,8 +335,10 @@ class AbstractPage(SeoMixin, MetaMixin, models.Model, metaclass=PageBase):
|
|
291
335
|
def _update_url_node(self, force: bool = False) -> UrlNode:
|
292
336
|
self.url_node = self._get_or_create_url_node()
|
293
337
|
for __ in activate_languages():
|
294
|
-
old_permalink = self.permalink
|
295
|
-
new_permalink = self.
|
338
|
+
old_permalink = self.db_instance and self.db_instance.permalink
|
339
|
+
new_permalink = self.permalink
|
340
|
+
if self.autopermalink:
|
341
|
+
new_permalink = self.generate_permalink()
|
296
342
|
force = force or old_permalink != new_permalink
|
297
343
|
set_nofallbacks(self.url_node, "permalink", new_permalink)
|
298
344
|
if force:
|
@@ -301,22 +347,10 @@ class AbstractPage(SeoMixin, MetaMixin, models.Model, metaclass=PageBase):
|
|
301
347
|
return self.url_node
|
302
348
|
|
303
349
|
def generate_permalink(self, safe: bool = True) -> str:
|
304
|
-
|
305
|
-
if slug is None and not self.permalink:
|
306
|
-
translations = get_field_translations(self, "slug").values()
|
307
|
-
fallback_slug = next((t for t in translations if t is not None), None)
|
308
|
-
slug = (
|
309
|
-
slugify(self.title or uuid4(), allow_unicode=True)
|
310
|
-
if fallback_slug is None
|
311
|
-
else fallback_slug
|
312
|
-
)
|
313
|
-
set_nofallbacks(self, "slug", slug)
|
314
|
-
slug_parts = (slug or "").split("/")
|
315
|
-
for i, part in enumerate(slug_parts):
|
316
|
-
slug_parts[i] = slugify(part, allow_unicode=True)
|
317
|
-
permalink = "/%s" % "/".join(slug_parts)
|
350
|
+
permalink = f"/{slugify(self.title or '', allow_unicode=True)}"
|
318
351
|
if self.parent:
|
319
|
-
permalink = f"{self.parent.permalink}{permalink}"
|
352
|
+
permalink = f"/{self.parent.permalink}{permalink}"
|
353
|
+
set_nofallbacks(self, "permalink", permalink)
|
320
354
|
qs = UrlNode.objects.exclude(pk=getattr(self.url_node or object, "pk", None))
|
321
355
|
if safe and qs.filter(permalink=permalink).exists():
|
322
356
|
permalink = "/".join(
|
@@ -333,7 +367,10 @@ class AbstractPage(SeoMixin, MetaMixin, models.Model, metaclass=PageBase):
|
|
333
367
|
def save(self, *args, **kwargs) -> None:
|
334
368
|
with transaction.atomic():
|
335
369
|
self._update_url_node()
|
336
|
-
|
370
|
+
super().save(*args, **kwargs)
|
371
|
+
self.__cached_db_instance = None
|
372
|
+
for lang_p_field in UrlNode.LANG_PERMALINK_FIELDS:
|
373
|
+
hasattr(self, f"__{lang_p_field}") and delattr(self, f"__{lang_p_field}")
|
337
374
|
|
338
375
|
@classmethod
|
339
376
|
def get(cls, request, *args, **kwargs) -> "AbstractPage":
|
@@ -386,7 +423,7 @@ class AbstractPage(SeoMixin, MetaMixin, models.Model, metaclass=PageBase):
|
|
386
423
|
node = UrlNode.objects.get(permalink="/")
|
387
424
|
return node.page, False
|
388
425
|
except UrlNode.DoesNotExist:
|
389
|
-
return cls.get_or_create(None,
|
426
|
+
return cls.get_or_create(None, permalink="/")
|
390
427
|
|
391
428
|
@classmethod
|
392
429
|
def get_or_404(cls, request, *args, **kwargs) -> "AbstractPage":
|
@@ -1,7 +1,7 @@
|
|
1
1
|
from django.db import models
|
2
2
|
from rest_framework import serializers
|
3
3
|
|
4
|
-
from
|
4
|
+
from structured.fields import StructuredJSONField as ModelStructuredJSONField
|
5
5
|
|
6
6
|
from .json import StructuredJSONField
|
7
7
|
from .file import FileField, ImageField
|
@@ -16,6 +16,6 @@ class FieldsOverrideMixin:
|
|
16
16
|
**serializers.ModelSerializer.serializer_field_mapping,
|
17
17
|
models.FileField: FileField,
|
18
18
|
models.ImageField: ImageField,
|
19
|
-
|
19
|
+
ModelStructuredJSONField: StructuredJSONField,
|
20
20
|
}
|
21
21
|
serializer_related_field = RelatedField
|
@@ -3,10 +3,11 @@ from rest_framework import serializers
|
|
3
3
|
from rest_framework.utils import model_meta
|
4
4
|
from typing import TYPE_CHECKING, Any, Union, Dict, List
|
5
5
|
|
6
|
-
from camomilla.
|
6
|
+
from camomilla.utils.setters import pointed_setter
|
7
7
|
|
8
8
|
if TYPE_CHECKING:
|
9
|
-
from
|
9
|
+
from structured.pydantic.models import BaseModel
|
10
|
+
|
10
11
|
|
11
12
|
|
12
13
|
class StructuredJSONField(serializers.JSONField):
|
@@ -3,11 +3,10 @@ from django.conf import settings as django_settings
|
|
3
3
|
from django.db.models.aggregates import Max
|
4
4
|
from django.db.models.functions import Coalesce
|
5
5
|
from django.utils import translation
|
6
|
-
from modeltranslation.settings import AVAILABLE_LANGUAGES
|
7
|
-
from modeltranslation.utils import build_localized_fieldname
|
8
6
|
from rest_framework import serializers
|
9
7
|
from rest_framework.utils import model_meta
|
10
8
|
|
9
|
+
from camomilla.models import UrlNode
|
11
10
|
from camomilla.fields import ORDERING_ACCEPTED_FIELDS
|
12
11
|
from camomilla.serializers.fields.related import RelatedField
|
13
12
|
from camomilla.serializers.utils import build_standard_model_serializer
|
@@ -169,12 +168,6 @@ class AbstractPageMixin(serializers.ModelSerializer):
|
|
169
168
|
def get_breadcrumbs(self, instance: "AbstractPage"):
|
170
169
|
return instance.breadcrumbs
|
171
170
|
|
172
|
-
LANG_PERMALINK_FIELDS = [
|
173
|
-
build_localized_fieldname("permalink", lang)
|
174
|
-
for lang in AVAILABLE_LANGUAGES
|
175
|
-
if settings.ENABLE_TRANSLATIONS
|
176
|
-
]
|
177
|
-
|
178
171
|
@property
|
179
172
|
def translation_fields(self):
|
180
173
|
return super().translation_fields + ["permalink"]
|
@@ -186,15 +179,15 @@ class AbstractPageMixin(serializers.ModelSerializer):
|
|
186
179
|
return super().get_default_field_names(*args)
|
187
180
|
return (
|
188
181
|
[f for f in super().get_default_field_names(*args) if f != "url_node"]
|
189
|
-
+
|
182
|
+
+ UrlNode.LANG_PERMALINK_FIELDS
|
190
183
|
+ ["permalink"]
|
191
184
|
)
|
192
185
|
|
193
186
|
def build_field(self, field_name, info, model_class, nested_depth):
|
194
|
-
if field_name in
|
187
|
+
if field_name in UrlNode.LANG_PERMALINK_FIELDS + ["permalink"]:
|
195
188
|
return serializers.CharField, {
|
196
|
-
"
|
197
|
-
"
|
189
|
+
"required": False,
|
190
|
+
"allow_blank": True,
|
198
191
|
}
|
199
192
|
return super().build_field(field_name, info, model_class, nested_depth)
|
200
193
|
|
@@ -8,7 +8,7 @@ from camomilla.utils.translation import get_nofallbacks, set_nofallbacks
|
|
8
8
|
|
9
9
|
|
10
10
|
class UniquePermalinkValidator:
|
11
|
-
message = _("
|
11
|
+
message = _("There is an other page with same permalink.")
|
12
12
|
|
13
13
|
requires_context = True
|
14
14
|
|
@@ -29,13 +29,17 @@ class UniquePermalinkValidator:
|
|
29
29
|
instance, parent_page_field, None
|
30
30
|
)
|
31
31
|
for language in activate_languages():
|
32
|
-
|
33
|
-
|
32
|
+
autopermalink_f = build_localized_fieldname("autopermalink", language)
|
33
|
+
f_name = build_localized_fieldname("permalink", language)
|
34
|
+
permalink = value.get(f_name, instance and get_nofallbacks(instance, "permalink"))
|
35
|
+
permalink = UrlNode.sanitize_permalink(permalink)
|
36
|
+
autopermalink = value.get(autopermalink_f, instance and get_nofallbacks(instance, "autopermalink"))
|
37
|
+
if autopermalink:
|
38
|
+
continue
|
34
39
|
fake_instance = serializer.Meta.model()
|
35
|
-
set_nofallbacks(fake_instance, "
|
40
|
+
set_nofallbacks(fake_instance, "permalink", permalink)
|
36
41
|
if parent_page:
|
37
42
|
set_nofallbacks(fake_instance, parent_page_field, parent_page)
|
38
|
-
permalink = fake_instance.generate_permalink(safe=False)
|
39
43
|
qs = UrlNode.objects.exclude(**exclude_kwargs)
|
40
44
|
if qs.filter(permalink=permalink).exists():
|
41
45
|
errors[f_name] = self.message
|
camomilla/settings.py
CHANGED
@@ -79,10 +79,6 @@ TEMPLATE_CONTEXT_FILES = pointed_getter(
|
|
79
79
|
django_settings, "CAMOMILLA.RENDER.TEMPLATE_CONTEXT_FILES", []
|
80
80
|
)
|
81
81
|
|
82
|
-
STRUCTURED_FIELD_CACHE_ENABLED = pointed_getter(
|
83
|
-
django_settings, "CAMOMILLA.STRUCTURED_FIELD.CACHE_ENABLED", True
|
84
|
-
)
|
85
|
-
|
86
82
|
DEBUG = pointed_getter(django_settings, "CAMOMILLA.DEBUG", django_settings.DEBUG)
|
87
83
|
|
88
84
|
# camomilla settings example
|
camomilla/theme/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = "6.0.0-beta.
|
1
|
+
__version__ = "6.0.0-beta.15"
|
camomilla/theme/admin.py
CHANGED
camomilla/translation.py
CHANGED
@@ -16,7 +16,7 @@ class SeoMixinTranslationOptions(TranslationOptions):
|
|
16
16
|
|
17
17
|
|
18
18
|
class AbstractPageTranslationOptions(SeoMixinTranslationOptions):
|
19
|
-
fields = ("breadcrumbs_title", "
|
19
|
+
fields = ("breadcrumbs_title", "autopermalink", "status", "indexable", "template_data")
|
20
20
|
|
21
21
|
|
22
22
|
@register(Article)
|
@@ -0,0 +1,37 @@
|
|
1
|
+
from typing import Sequence
|
2
|
+
from .getters import pointed_getter
|
3
|
+
|
4
|
+
|
5
|
+
def set_key(data, key, val):
|
6
|
+
if isinstance(data, Sequence):
|
7
|
+
key = int(key)
|
8
|
+
if key < len(data):
|
9
|
+
data[key] = val
|
10
|
+
else:
|
11
|
+
data.append(val)
|
12
|
+
return data
|
13
|
+
elif isinstance(data, dict):
|
14
|
+
data[key] = val
|
15
|
+
else:
|
16
|
+
setattr(data, key, val)
|
17
|
+
return data
|
18
|
+
|
19
|
+
|
20
|
+
def get_key(data, key, default):
|
21
|
+
if isinstance(data, Sequence):
|
22
|
+
try:
|
23
|
+
return data[int(key)]
|
24
|
+
except IndexError:
|
25
|
+
return default
|
26
|
+
return pointed_getter(data, key, default)
|
27
|
+
|
28
|
+
|
29
|
+
def pointed_setter(data, path, value):
|
30
|
+
path = path.split(".")
|
31
|
+
key = path.pop(0)
|
32
|
+
if not len(path):
|
33
|
+
return set_key(data, key, value)
|
34
|
+
default = [] if path[0].isdigit() else {}
|
35
|
+
return set_key(
|
36
|
+
data, key, pointed_setter(get_key(data, key, default), ".".join(path), value)
|
37
|
+
)
|
camomilla/views/menus.py
CHANGED
@@ -80,5 +80,4 @@ class MenuViewSet(BaseModelViewset):
|
|
80
80
|
def search_urlnode(self, request, *args, **kwargs):
|
81
81
|
url_node = request.GET.get("q", "")
|
82
82
|
qs = UrlNode.objects.filter(permalink__icontains=url_node).order_by("permalink")
|
83
|
-
print(get_language())
|
84
83
|
return Response(BasicUrlNodeSerializer(qs, many=True).data)
|
{django_camomilla_cms-6.0.0b13.dist-info → django_camomilla_cms-6.0.0b15.dist-info}/METADATA
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: django-camomilla-cms
|
3
|
-
Version: 6.0.
|
3
|
+
Version: 6.0.0b15
|
4
4
|
Summary: Django powered cms
|
5
5
|
Author-email: Lotrèk <dimmitutto@lotrek.it>
|
6
6
|
License: MIT
|
@@ -20,10 +20,9 @@ Requires-Dist: djangorestframework <4.0.0,>=3.10.0
|
|
20
20
|
Requires-Dist: django-admin-interface <1.0.0,>=0.26.0
|
21
21
|
Requires-Dist: Pillow <10.0.0,>=6.2.0
|
22
22
|
Requires-Dist: django-ckeditor <7.0.0,>=5.7.1
|
23
|
+
Requires-Dist: django-structured-json-field ==0.2.0
|
23
24
|
Requires-Dist: python-magic <0.5,>=0.4
|
24
|
-
Requires-Dist: django-jsonform ~=2.19.0
|
25
25
|
Requires-Dist: Django >=3.2
|
26
|
-
Requires-Dist: pydantic ~=2.2.1
|
27
26
|
|
28
27
|
# camomilla django cms [](https://pypi.org/project/django-camomilla-cms)   [](./LICENSE)
|
29
28
|
|