django-camomilla-cms 6.0.0b15__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 +93 -8
- camomilla/model_api.py +14 -7
- camomilla/models/media.py +1 -1
- camomilla/models/menu.py +10 -4
- camomilla/models/page.py +189 -127
- camomilla/openapi/schema.py +17 -8
- camomilla/redirects.py +10 -0
- camomilla/serializers/base/__init__.py +6 -4
- camomilla/serializers/fields/__init__.py +5 -17
- camomilla/serializers/fields/related.py +10 -4
- camomilla/serializers/mixins/__init__.py +23 -195
- camomilla/serializers/mixins/fields.py +20 -0
- camomilla/serializers/mixins/filter_fields.py +57 -0
- 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 +5 -3
- camomilla/serializers/validators.py +6 -2
- camomilla/settings.py +10 -2
- camomilla/storages/default.py +12 -0
- camomilla/storages/optimize.py +2 -2
- camomilla/storages/overwrite.py +2 -2
- 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 +167 -0
- camomilla/utils/translation.py +47 -5
- camomilla/views/base/__init__.py +35 -5
- camomilla/views/medias.py +1 -1
- camomilla/views/menus.py +0 -2
- camomilla/views/mixins/__init__.py +17 -69
- 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 +12 -18
- camomilla/views/mixins/permissions.py +6 -0
- camomilla/views/pages.py +12 -2
- {django_camomilla_cms-6.0.0b15.dist-info → django_camomilla_cms-6.0.0b17.dist-info}/METADATA +23 -16
- django_camomilla_cms-6.0.0b17.dist-info/RECORD +132 -0
- {django_camomilla_cms-6.0.0b15.dist-info → django_camomilla_cms-6.0.0b17.dist-info}/WHEEL +1 -1
- tests/fixtures/__init__.py +17 -0
- tests/test_api.py +2 -11
- tests/test_camomilla_filters.py +7 -13
- tests/test_media.py +113 -0
- tests/test_menu.py +97 -0
- tests/test_model_api.py +68 -0
- tests/test_model_api_permissions.py +39 -0
- tests/test_model_api_register.py +393 -0
- tests/test_pages.py +343 -0
- tests/test_query_parser.py +58 -0
- tests/test_templates_context.py +111 -0
- tests/test_utils.py +64 -64
- tests/utils/api.py +28 -0
- tests/utils/media.py +9 -0
- camomilla/serializers/fields/json.py +0 -49
- django_camomilla_cms-6.0.0b15.dist-info/RECORD +0 -105
- {django_camomilla_cms-6.0.0b15.dist-info → django_camomilla_cms-6.0.0b17.dist-info/licenses}/LICENSE +0 -0
- {django_camomilla_cms-6.0.0b15.dist-info → django_camomilla_cms-6.0.0b17.dist-info}/top_level.txt +0 -0
- {camomilla/contrib/rest_framework → tests/utils}/__init__.py +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,31 +1,116 @@
|
|
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
|
|
7
11
|
|
8
12
|
class PageQuerySet(QuerySet):
|
9
|
-
|
13
|
+
|
10
14
|
__UrlNodeModel = None
|
11
|
-
|
15
|
+
|
12
16
|
@property
|
13
17
|
def UrlNodeModel(self):
|
14
18
|
if not self.__UrlNodeModel:
|
15
19
|
self.__UrlNodeModel = apps.get_model("camomilla", "UrlNode")
|
16
|
-
return self.__UrlNodeModel
|
17
|
-
|
20
|
+
return self.__UrlNodeModel
|
21
|
+
|
18
22
|
def get_permalink_kwargs(self, kwargs):
|
19
|
-
return list(
|
20
|
-
|
23
|
+
return list(
|
24
|
+
set(kwargs.keys()).intersection(
|
25
|
+
set(self.UrlNodeModel.LANG_PERMALINK_FIELDS + ["permalink"])
|
26
|
+
)
|
27
|
+
)
|
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
|
-
return super(PageQuerySet, self).get(*args, **kwargs)
|
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
@@ -26,6 +26,7 @@ def register(
|
|
26
26
|
"""
|
27
27
|
|
28
28
|
def inner(model):
|
29
|
+
global urlpatterns
|
29
30
|
base_meta = {
|
30
31
|
"model": model,
|
31
32
|
"fields": "__all__",
|
@@ -47,13 +48,17 @@ def register(
|
|
47
48
|
},
|
48
49
|
)
|
49
50
|
|
51
|
+
def get_queryset(self, *args, **kwargs):
|
52
|
+
qs = super(base_viewset, self).get_queryset(*args, **kwargs)
|
53
|
+
return qs if filters is None else qs.filter(**filters)
|
54
|
+
|
50
55
|
viewset = type(
|
51
56
|
f"{model.__name__}ViewSet",
|
52
57
|
(base_viewset,),
|
53
58
|
{
|
54
|
-
"
|
55
|
-
|
56
|
-
|
59
|
+
"queryset": model.objects.all(),
|
60
|
+
"model": model,
|
61
|
+
"get_queryset": get_queryset,
|
57
62
|
"serializer_class": serializer,
|
58
63
|
**viewset_attrs,
|
59
64
|
},
|
@@ -61,9 +66,11 @@ def register(
|
|
61
66
|
|
62
67
|
model_path = "".join(
|
63
68
|
[
|
64
|
-
|
65
|
-
|
66
|
-
|
69
|
+
(
|
70
|
+
"-" + character.lower()
|
71
|
+
if character.isupper() and index > 0
|
72
|
+
else character
|
73
|
+
)
|
67
74
|
for index, character in enumerate(model.__name__)
|
68
75
|
]
|
69
76
|
).lstrip("-")
|
@@ -73,7 +80,7 @@ def register(
|
|
73
80
|
viewset,
|
74
81
|
f"{model.__name__.lower()}_api",
|
75
82
|
)
|
76
|
-
urlpatterns
|
83
|
+
urlpatterns = [path("", include(router.urls))]
|
77
84
|
return model
|
78
85
|
|
79
86
|
return inner
|
camomilla/models/media.py
CHANGED
@@ -150,7 +150,7 @@ class Media(models.Model):
|
|
150
150
|
img_bytes = self.file.storage.open(self.file.name, "rb")
|
151
151
|
with Image.open(img_bytes) as orig_image:
|
152
152
|
image = orig_image.copy()
|
153
|
-
image.thumbnail((THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT), Image.
|
153
|
+
image.thumbnail((THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT), Image.LANCZOS)
|
154
154
|
|
155
155
|
# Path to save to, name, and extension
|
156
156
|
thumb_name, thumb_extension = os.path.splitext(self.file.name)
|
camomilla/models/menu.py
CHANGED
@@ -14,7 +14,6 @@ from pydantic import (
|
|
14
14
|
)
|
15
15
|
from structured.pydantic.models import BaseModel
|
16
16
|
from structured.fields import StructuredJSONField
|
17
|
-
from structured.pydantic.fields import QuerySet
|
18
17
|
from camomilla.models.page import UrlNode, AbstractPage
|
19
18
|
from typing import Optional, Union, Callable, List
|
20
19
|
from django.db.models.base import Model as DjangoModel
|
@@ -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,10 @@ class Menu(models.Model):
|
|
88
91
|
):
|
89
92
|
if isinstance(context, RequestContext):
|
90
93
|
context = context.flatten()
|
91
|
-
|
94
|
+
is_preview = (
|
95
|
+
False if request is None else bool(request.GET.get("preview", False))
|
96
|
+
)
|
97
|
+
context.update({"menu": self, "is_preview": is_preview})
|
92
98
|
return mark_safe(render_to_string(template_path, context, request))
|
93
99
|
|
94
100
|
class defaultdict(dict):
|