django-camomilla-cms 6.0.0b16__py2.py3-none-any.whl → 6.0.0b17__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/contrib/modeltranslation/hvad_migration.py +9 -9
- camomilla/dynamic_pages_urls.py +6 -2
- camomilla/managers/pages.py +87 -2
- camomilla/model_api.py +6 -4
- camomilla/models/menu.py +9 -4
- camomilla/models/page.py +178 -117
- camomilla/openapi/schema.py +15 -10
- camomilla/redirects.py +10 -0
- camomilla/serializers/base/__init__.py +4 -4
- camomilla/serializers/fields/__init__.py +5 -17
- camomilla/serializers/fields/related.py +5 -3
- camomilla/serializers/mixins/__init__.py +23 -240
- camomilla/serializers/mixins/fields.py +20 -0
- camomilla/serializers/mixins/filter_fields.py +9 -8
- camomilla/serializers/mixins/json.py +34 -0
- camomilla/serializers/mixins/language.py +32 -0
- camomilla/serializers/mixins/nesting.py +35 -0
- camomilla/serializers/mixins/optimize.py +91 -0
- camomilla/serializers/mixins/ordering.py +34 -0
- camomilla/serializers/mixins/page.py +58 -0
- camomilla/{contrib/rest_framework/serializer.py → serializers/mixins/translation.py} +16 -56
- camomilla/serializers/utils.py +3 -3
- camomilla/serializers/validators.py +6 -2
- camomilla/settings.py +10 -2
- camomilla/storages/default.py +7 -1
- camomilla/templates/defaults/parts/menu.html +1 -1
- camomilla/templatetags/menus.py +3 -0
- camomilla/theme/__init__.py +1 -1
- camomilla/theme/{admin.py → admin/__init__.py} +22 -20
- camomilla/theme/admin/pages.py +46 -0
- camomilla/theme/admin/translations.py +13 -0
- camomilla/theme/apps.py +1 -5
- camomilla/translation.py +7 -1
- camomilla/urls.py +2 -5
- camomilla/utils/query_parser.py +42 -23
- camomilla/utils/translation.py +47 -5
- camomilla/views/base/__init__.py +35 -5
- camomilla/views/medias.py +1 -1
- camomilla/views/mixins/__init__.py +17 -76
- camomilla/views/mixins/bulk_actions.py +22 -0
- camomilla/views/mixins/language.py +33 -0
- camomilla/views/mixins/optimize.py +18 -0
- camomilla/views/mixins/pagination.py +11 -8
- camomilla/views/mixins/permissions.py +6 -0
- camomilla/views/pages.py +12 -2
- {django_camomilla_cms-6.0.0b16.dist-info → django_camomilla_cms-6.0.0b17.dist-info}/METADATA +23 -16
- {django_camomilla_cms-6.0.0b16.dist-info → django_camomilla_cms-6.0.0b17.dist-info}/RECORD +60 -43
- {django_camomilla_cms-6.0.0b16.dist-info → django_camomilla_cms-6.0.0b17.dist-info}/WHEEL +1 -1
- tests/test_camomilla_filters.py +1 -1
- tests/test_media.py +98 -65
- tests/test_menu.py +97 -0
- tests/test_model_api_register.py +393 -0
- tests/test_pages.py +343 -0
- tests/test_query_parser.py +1 -2
- tests/test_templates_context.py +111 -0
- tests/utils/api.py +0 -1
- tests/utils/media.py +9 -0
- camomilla/contrib/rest_framework/__init__.py +0 -0
- camomilla/serializers/fields/json.py +0 -48
- {django_camomilla_cms-6.0.0b16.dist-info → django_camomilla_cms-6.0.0b17.dist-info/licenses}/LICENSE +0 -0
- {django_camomilla_cms-6.0.0b16.dist-info → django_camomilla_cms-6.0.0b17.dist-info}/top_level.txt +0 -0
camomilla/__init__.py
CHANGED
@@ -56,12 +56,12 @@ class KeepTranslationsMixin:
|
|
56
56
|
)
|
57
57
|
)
|
58
58
|
rows = cursor.fetchall()
|
59
|
-
self._saved_data_from_plain[
|
60
|
-
modelPath
|
61
|
-
|
62
|
-
self._saved_data_from_plain[modelPath][
|
63
|
-
master[0]
|
64
|
-
|
59
|
+
self._saved_data_from_plain[modelPath] = (
|
60
|
+
self._saved_data_from_plain.get(modelPath, {})
|
61
|
+
)
|
62
|
+
self._saved_data_from_plain[modelPath][master[0]] = (
|
63
|
+
self._saved_data_from_plain[modelPath].get(master[0], [])
|
64
|
+
)
|
65
65
|
for row in rows:
|
66
66
|
self._saved_data_from_plain[modelPath][master[0]].append(
|
67
67
|
dict(zip(fields, row))
|
@@ -83,9 +83,9 @@ class KeepTranslationsMixin:
|
|
83
83
|
)
|
84
84
|
)
|
85
85
|
rows = cursor.fetchall()
|
86
|
-
self._saved_data_from_plain[
|
87
|
-
modelPath
|
88
|
-
|
86
|
+
self._saved_data_from_plain[modelPath] = (
|
87
|
+
self._saved_data_from_plain.get(modelPath, [])
|
88
|
+
)
|
89
89
|
for row in rows:
|
90
90
|
row_data = dict(zip(("master_id", *fields), row))
|
91
91
|
row_data.update({"language_code": lang})
|
camomilla/dynamic_pages_urls.py
CHANGED
@@ -3,15 +3,19 @@ from django.urls import path
|
|
3
3
|
|
4
4
|
from camomilla import settings
|
5
5
|
from django.conf import settings as django_settings
|
6
|
-
from .models import Page
|
6
|
+
from .models import Page, UrlRedirect
|
7
7
|
|
8
8
|
|
9
9
|
def fetch(request, *args, **kwargs):
|
10
10
|
can_preview = request.user.is_staff or settings.DEBUG
|
11
11
|
preview = can_preview and request.GET.get("preview", False)
|
12
12
|
append_slash = getattr(django_settings, "APPEND_SLASH", True)
|
13
|
+
redirect_obj = UrlRedirect.find_redirect(request)
|
14
|
+
if redirect_obj:
|
15
|
+
return redirect_obj.redirect()
|
13
16
|
if append_slash and not request.path.endswith("/"):
|
14
|
-
|
17
|
+
q_string = request.META.get("QUERY_STRING", "")
|
18
|
+
return redirect(request.path + "/" + ("?" + q_string if q_string else ""))
|
15
19
|
if "permalink" in kwargs:
|
16
20
|
page = Page.get_or_404(
|
17
21
|
request, bypass_public_check=preview, bypass_type_check=True
|
camomilla/managers/pages.py
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
from django.db.models.query import QuerySet
|
2
2
|
from django.core.exceptions import ObjectDoesNotExist
|
3
3
|
from django.apps import apps
|
4
|
+
from django.db import models
|
5
|
+
from django.utils import timezone
|
6
|
+
from django.db.utils import ProgrammingError, OperationalError
|
7
|
+
from typing import Sequence, Tuple
|
4
8
|
|
5
9
|
URL_NODE_RELATED_NAME = "%(app_label)s_%(class)s"
|
6
10
|
|
@@ -16,16 +20,97 @@ class PageQuerySet(QuerySet):
|
|
16
20
|
return self.__UrlNodeModel
|
17
21
|
|
18
22
|
def get_permalink_kwargs(self, kwargs):
|
19
|
-
return list(
|
23
|
+
return list(
|
24
|
+
set(kwargs.keys()).intersection(
|
25
|
+
set(self.UrlNodeModel.LANG_PERMALINK_FIELDS + ["permalink"])
|
26
|
+
)
|
27
|
+
)
|
20
28
|
|
21
29
|
def get(self, *args, **kwargs):
|
22
30
|
permalink_args = self.get_permalink_kwargs(kwargs)
|
23
31
|
if len(permalink_args):
|
24
32
|
try:
|
25
|
-
node = self.UrlNodeModel.objects.get(
|
33
|
+
node = self.UrlNodeModel.objects.get(
|
34
|
+
**{arg: kwargs.pop(arg) for arg in permalink_args}
|
35
|
+
)
|
26
36
|
kwargs["url_node"] = node
|
27
37
|
except ObjectDoesNotExist:
|
28
38
|
raise self.model.DoesNotExist(
|
29
39
|
"%s matching query does not exist." % self.model._meta.object_name
|
30
40
|
)
|
31
41
|
return super(PageQuerySet, self).get(*args, **kwargs)
|
42
|
+
|
43
|
+
|
44
|
+
class UrlNodeManager(models.Manager):
|
45
|
+
@property
|
46
|
+
def related_names(self):
|
47
|
+
self._related_names = getattr(
|
48
|
+
self,
|
49
|
+
"_related_names",
|
50
|
+
super().get_queryset().values_list("related_name", flat=True).distinct(),
|
51
|
+
)
|
52
|
+
return self._related_names
|
53
|
+
|
54
|
+
def _annotate_fields(
|
55
|
+
self,
|
56
|
+
qs: models.QuerySet,
|
57
|
+
field_names: Sequence[Tuple[str, models.Field, models.Value]],
|
58
|
+
):
|
59
|
+
for field_name, output_field, default in field_names:
|
60
|
+
whens = [
|
61
|
+
models.When(
|
62
|
+
related_name=related_name,
|
63
|
+
then=models.F("__".join([related_name, field_name])),
|
64
|
+
)
|
65
|
+
for related_name in self.related_names
|
66
|
+
]
|
67
|
+
qs = qs.annotate(
|
68
|
+
**{
|
69
|
+
field_name: models.Case(
|
70
|
+
*whens, output_field=output_field, default=default
|
71
|
+
)
|
72
|
+
}
|
73
|
+
)
|
74
|
+
return self._annotate_is_public(qs)
|
75
|
+
|
76
|
+
def _annotate_is_public(self, qs: models.QuerySet):
|
77
|
+
return qs.annotate(
|
78
|
+
is_public=models.Case(
|
79
|
+
models.When(status="PUB", then=True),
|
80
|
+
models.When(
|
81
|
+
status="PLA", publication_date__lte=timezone.now(), then=True
|
82
|
+
),
|
83
|
+
default=False,
|
84
|
+
output_field=models.BooleanField(default=False),
|
85
|
+
)
|
86
|
+
)
|
87
|
+
|
88
|
+
def get_queryset(self):
|
89
|
+
try:
|
90
|
+
return self._annotate_fields(
|
91
|
+
super().get_queryset(),
|
92
|
+
[
|
93
|
+
(
|
94
|
+
"indexable",
|
95
|
+
models.BooleanField(),
|
96
|
+
models.Value(None, models.BooleanField()),
|
97
|
+
),
|
98
|
+
(
|
99
|
+
"status",
|
100
|
+
models.CharField(),
|
101
|
+
models.Value("DRF", models.CharField()),
|
102
|
+
),
|
103
|
+
(
|
104
|
+
"publication_date",
|
105
|
+
models.DateTimeField(),
|
106
|
+
models.Value(timezone.now(), models.DateTimeField()),
|
107
|
+
),
|
108
|
+
(
|
109
|
+
"date_updated_at",
|
110
|
+
models.DateTimeField(),
|
111
|
+
models.Value(timezone.now(), models.DateTimeField()),
|
112
|
+
),
|
113
|
+
],
|
114
|
+
)
|
115
|
+
except (ProgrammingError, OperationalError):
|
116
|
+
return super().get_queryset()
|
camomilla/model_api.py
CHANGED
@@ -47,7 +47,7 @@ def register(
|
|
47
47
|
)
|
48
48
|
},
|
49
49
|
)
|
50
|
-
|
50
|
+
|
51
51
|
def get_queryset(self, *args, **kwargs):
|
52
52
|
qs = super(base_viewset, self).get_queryset(*args, **kwargs)
|
53
53
|
return qs if filters is None else qs.filter(**filters)
|
@@ -66,9 +66,11 @@ def register(
|
|
66
66
|
|
67
67
|
model_path = "".join(
|
68
68
|
[
|
69
|
-
|
70
|
-
|
71
|
-
|
69
|
+
(
|
70
|
+
"-" + character.lower()
|
71
|
+
if character.isupper() and index > 0
|
72
|
+
else character
|
73
|
+
)
|
72
74
|
for index, character in enumerate(model.__name__)
|
73
75
|
]
|
74
76
|
).lstrip("-")
|
camomilla/models/menu.py
CHANGED
@@ -17,7 +17,6 @@ from structured.fields import StructuredJSONField
|
|
17
17
|
from camomilla.models.page import UrlNode, AbstractPage
|
18
18
|
from typing import Optional, Union, Callable, List
|
19
19
|
from django.db.models.base import Model as DjangoModel
|
20
|
-
from django.conf import settings
|
21
20
|
|
22
21
|
|
23
22
|
class LinkTypes(str, Enum):
|
@@ -37,7 +36,9 @@ class MenuNodeLink(BaseModel):
|
|
37
36
|
if self.link_type == LinkTypes.relational:
|
38
37
|
if self.content_type and self.page:
|
39
38
|
if isinstance(self.page, DjangoModel) and not self.page._meta.abstract:
|
40
|
-
self.content_type = ContentType.objects.get_for_model(
|
39
|
+
self.content_type = ContentType.objects.get_for_model(
|
40
|
+
self.page.__class__
|
41
|
+
)
|
41
42
|
ctype_id = getattr(self.content_type, "pk", self.content_type)
|
42
43
|
page_id = getattr(self.page, "pk", self.page)
|
43
44
|
c_type = ContentType.objects.filter(pk=ctype_id).first()
|
@@ -47,7 +48,9 @@ class MenuNodeLink(BaseModel):
|
|
47
48
|
elif self.url_node:
|
48
49
|
url_node_id = getattr(self.url_node, "pk", self.url_node)
|
49
50
|
self.page = UrlNode.objects.filter(pk=url_node_id).first().page
|
50
|
-
self.content_type = ContentType.objects.get_for_model(
|
51
|
+
self.content_type = ContentType.objects.get_for_model(
|
52
|
+
self.page.__class__
|
53
|
+
)
|
51
54
|
return handler(self)
|
52
55
|
|
53
56
|
def get_url(self, request=None):
|
@@ -88,7 +91,9 @@ class Menu(models.Model):
|
|
88
91
|
):
|
89
92
|
if isinstance(context, RequestContext):
|
90
93
|
context = context.flatten()
|
91
|
-
is_preview =
|
94
|
+
is_preview = (
|
95
|
+
False if request is None else bool(request.GET.get("preview", False))
|
96
|
+
)
|
92
97
|
context.update({"menu": self, "is_preview": is_preview})
|
93
98
|
return mark_safe(render_to_string(template_path, context, request))
|
94
99
|
|