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/utils/translation.py
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
import re
|
2
|
-
from typing import Any, Sequence, Iterator
|
2
|
+
from typing import Any, Sequence, Iterator, Union, List
|
3
3
|
|
4
4
|
from django.db.models import Model, Q
|
5
5
|
from django.utils.translation.trans_real import activate, get_language
|
6
6
|
from modeltranslation.settings import AVAILABLE_LANGUAGES, DEFAULT_LANGUAGE
|
7
7
|
from modeltranslation.utils import build_localized_fieldname
|
8
8
|
from camomilla.settings import BASE_URL
|
9
|
+
from django.http import QueryDict
|
9
10
|
|
10
11
|
|
11
12
|
def activate_languages(languages: Sequence[str] = AVAILABLE_LANGUAGES) -> Iterator[str]:
|
@@ -34,11 +35,9 @@ def get_nofallbacks(instance: Model, attr: str, *args, **kwargs) -> Any:
|
|
34
35
|
|
35
36
|
def url_lang_decompose(url):
|
36
37
|
if BASE_URL and url.startswith(BASE_URL):
|
37
|
-
url = url[len(BASE_URL):]
|
38
|
+
url = url[len(BASE_URL) :]
|
38
39
|
data = {"url": url, "permalink": url, "language": DEFAULT_LANGUAGE}
|
39
|
-
result = re.match(
|
40
|
-
f"^\/?({'|'.join(AVAILABLE_LANGUAGES)})?\/(.*)", url
|
41
|
-
) # noqa: W605
|
40
|
+
result = re.match(rf"^/?({'|'.join(AVAILABLE_LANGUAGES)})?/(.*)", url) # noqa: W605
|
42
41
|
groups = result and result.groups()
|
43
42
|
if groups and len(groups) == 2:
|
44
43
|
data["language"] = groups[0]
|
@@ -70,3 +69,46 @@ def is_translatable(model: Model) -> bool:
|
|
70
69
|
from modeltranslation.translator import translator
|
71
70
|
|
72
71
|
return model in translator.get_registered_models()
|
72
|
+
|
73
|
+
|
74
|
+
def plain_to_nest(data, fields, accessor="translations"):
|
75
|
+
"""
|
76
|
+
This function transforms a plain dictionary with translations fields (es. {"title_en": "Hello"})
|
77
|
+
into a dictionary with nested translations fields (es. {"translations": {"en": {"title": "Hello"}}}).
|
78
|
+
"""
|
79
|
+
trans_data = {}
|
80
|
+
for lang in AVAILABLE_LANGUAGES:
|
81
|
+
lang_data = {}
|
82
|
+
for field in fields:
|
83
|
+
trans_field_name = build_localized_fieldname(field, lang)
|
84
|
+
if trans_field_name in data:
|
85
|
+
lang_data[field] = data.pop(trans_field_name)
|
86
|
+
if lang_data.keys():
|
87
|
+
trans_data[lang] = lang_data
|
88
|
+
if trans_data.keys():
|
89
|
+
data[accessor] = trans_data
|
90
|
+
return data
|
91
|
+
|
92
|
+
|
93
|
+
def nest_to_plain(
|
94
|
+
data: Union[dict, QueryDict], fields: List[str], accessor="translations"
|
95
|
+
):
|
96
|
+
"""
|
97
|
+
This function is the inverse of plain_to_nest.
|
98
|
+
It transforms a dictionary with nested translations fields (es. {"translations": {"en": {"title": "Hello"}}})
|
99
|
+
into a plain dictionary with translations fields (es. {"title_en": "Hello"}).
|
100
|
+
"""
|
101
|
+
if isinstance(data, QueryDict):
|
102
|
+
data = data.dict()
|
103
|
+
translations = data.pop(accessor, {})
|
104
|
+
for lang in AVAILABLE_LANGUAGES:
|
105
|
+
nest_trans = translations.pop(lang, {})
|
106
|
+
for k in fields:
|
107
|
+
data.pop(k, None) # this removes all trans field without lang
|
108
|
+
if k in nest_trans:
|
109
|
+
# this saves on the default field the default language value
|
110
|
+
if lang == DEFAULT_LANGUAGE:
|
111
|
+
data[k] = nest_trans[k]
|
112
|
+
key = build_localized_fieldname(k, lang)
|
113
|
+
data[key] = data.get(key, nest_trans[k])
|
114
|
+
return data
|
camomilla/views/base/__init__.py
CHANGED
@@ -1,8 +1,38 @@
|
|
1
|
-
from ..mixins import
|
1
|
+
from ..mixins import (
|
2
|
+
OptimViewMixin,
|
3
|
+
PaginateStackMixin,
|
4
|
+
OrderingMixin,
|
5
|
+
CamomillaBasePermissionMixin,
|
6
|
+
)
|
2
7
|
from rest_framework import viewsets
|
8
|
+
from rest_framework.metadata import SimpleMetadata
|
9
|
+
from structured.contrib.restframework import StructuredJSONField
|
3
10
|
|
4
11
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
12
|
+
base_viewset_classes = [
|
13
|
+
CamomillaBasePermissionMixin,
|
14
|
+
OptimViewMixin,
|
15
|
+
OrderingMixin,
|
16
|
+
PaginateStackMixin,
|
17
|
+
viewsets.ModelViewSet,
|
18
|
+
]
|
19
|
+
|
20
|
+
|
21
|
+
class BaseViewMetadata(SimpleMetadata):
|
22
|
+
|
23
|
+
def get_field_info(self, field):
|
24
|
+
field_info = super().get_field_info(field)
|
25
|
+
if isinstance(field, StructuredJSONField):
|
26
|
+
field_info["schema"] = field.schema.json_schema()
|
27
|
+
field_info["type"] = "structured-json"
|
28
|
+
return field_info
|
29
|
+
|
30
|
+
def get_serializer_info(self, serializer):
|
31
|
+
info = super().get_serializer_info(serializer)
|
32
|
+
if hasattr(serializer, "plain_to_nest"):
|
33
|
+
info.update(serializer.plain_to_nest(info))
|
34
|
+
return info
|
35
|
+
|
36
|
+
|
37
|
+
class BaseModelViewset(*base_viewset_classes):
|
38
|
+
metadata_class = BaseViewMetadata
|
camomilla/views/medias.py
CHANGED
camomilla/views/menus.py
CHANGED
@@ -13,8 +13,6 @@ from camomilla.serializers.page import BasicUrlNodeSerializer
|
|
13
13
|
from camomilla.views.base import BaseModelViewset
|
14
14
|
from camomilla.views.decorators import active_lang
|
15
15
|
|
16
|
-
from django.utils.translation import get_language
|
17
|
-
|
18
16
|
|
19
17
|
class MenuViewSet(BaseModelViewset):
|
20
18
|
queryset = Menu.objects.all()
|
@@ -1,69 +1,17 @@
|
|
1
|
-
from
|
2
|
-
from
|
3
|
-
from
|
4
|
-
from .
|
5
|
-
from .
|
6
|
-
from
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
self.language_fallbacks = True
|
19
|
-
if (
|
20
|
-
len(self.active_language.split("-")) == 2
|
21
|
-
and self.active_language.split("-")[0] == "nofallbacks"
|
22
|
-
):
|
23
|
-
self.language_fallbacks = False
|
24
|
-
self.active_language = self.active_language.split("-")[1]
|
25
|
-
translation.activate(self.active_language)
|
26
|
-
return self.active_language
|
27
|
-
|
28
|
-
def initialize_request(self, request, *args, **kwargs):
|
29
|
-
self._get_user_language(request)
|
30
|
-
return super().initialize_request(request, *args, **kwargs)
|
31
|
-
|
32
|
-
def get_queryset(self):
|
33
|
-
return self.model.objects.all()
|
34
|
-
|
35
|
-
|
36
|
-
class OptimViewMixin:
|
37
|
-
def get_serializer_class(self):
|
38
|
-
if hasattr(self, "action_serializers"):
|
39
|
-
if self.action in self.action_serializers:
|
40
|
-
return self.action_serializers[self.action]
|
41
|
-
return super().get_serializer_class()
|
42
|
-
|
43
|
-
def get_serializer_context(self):
|
44
|
-
return {"request": self.request, "action": self.action}
|
45
|
-
|
46
|
-
def get_queryset(self):
|
47
|
-
queryset = super().get_queryset()
|
48
|
-
serializer = self.get_serializer_class()
|
49
|
-
if hasattr(serializer, "setup_eager_loading"):
|
50
|
-
queryset = self.get_serializer_class().setup_eager_loading(queryset)
|
51
|
-
return queryset
|
52
|
-
|
53
|
-
|
54
|
-
class BulkDeleteMixin(object):
|
55
|
-
@action(
|
56
|
-
detail=False, methods=["post"], permission_classes=(CamomillaBasePermissions,)
|
57
|
-
)
|
58
|
-
def bulk_delete(self, request):
|
59
|
-
try:
|
60
|
-
self.model.objects.filter(pk__in=request.data).delete()
|
61
|
-
return Response(
|
62
|
-
{"detail": "Eliminazione multipla andata a buon fine"},
|
63
|
-
status=status.HTTP_200_OK,
|
64
|
-
)
|
65
|
-
except Exception:
|
66
|
-
return Response(
|
67
|
-
{"detail": "Eliminazione multipla non riuscita"},
|
68
|
-
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
69
|
-
)
|
1
|
+
from .bulk_actions import BulkDeleteMixin
|
2
|
+
from .language import GetUserLanguageMixin
|
3
|
+
from .optimize import OptimViewMixin
|
4
|
+
from .ordering import OrderingMixin
|
5
|
+
from .pagination import PaginateStackMixin, TrigramSearchMixin
|
6
|
+
from .permissions import CamomillaBasePermissionMixin
|
7
|
+
|
8
|
+
|
9
|
+
__all__ = [
|
10
|
+
"BulkDeleteMixin",
|
11
|
+
"GetUserLanguageMixin",
|
12
|
+
"OptimViewMixin",
|
13
|
+
"OrderingMixin",
|
14
|
+
"PaginateStackMixin",
|
15
|
+
"TrigramSearchMixin",
|
16
|
+
"CamomillaBasePermissionMixin",
|
17
|
+
]
|
@@ -0,0 +1,22 @@
|
|
1
|
+
from rest_framework import status
|
2
|
+
from rest_framework.decorators import action
|
3
|
+
from rest_framework.response import Response
|
4
|
+
from ...permissions import CamomillaBasePermissions
|
5
|
+
|
6
|
+
|
7
|
+
class BulkDeleteMixin(object):
|
8
|
+
@action(
|
9
|
+
detail=False, methods=["post"], permission_classes=(CamomillaBasePermissions,)
|
10
|
+
)
|
11
|
+
def bulk_delete(self, request):
|
12
|
+
try:
|
13
|
+
self.model.objects.filter(pk__in=request.data).delete()
|
14
|
+
return Response(
|
15
|
+
{"detail": "Eliminazione multipla andata a buon fine"},
|
16
|
+
status=status.HTTP_200_OK,
|
17
|
+
)
|
18
|
+
except Exception:
|
19
|
+
return Response(
|
20
|
+
{"detail": "Eliminazione multipla non riuscita"},
|
21
|
+
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
22
|
+
)
|
@@ -0,0 +1,33 @@
|
|
1
|
+
from django.utils import translation
|
2
|
+
from camomilla import settings
|
3
|
+
|
4
|
+
|
5
|
+
class GetUserLanguageMixin:
|
6
|
+
def _get_user_language(self, request):
|
7
|
+
active_language_from_request = translation.get_language_from_request(request)
|
8
|
+
active_language = (
|
9
|
+
active_language_from_request
|
10
|
+
if active_language_from_request
|
11
|
+
else settings.DEFAULT_LANGUAGE
|
12
|
+
)
|
13
|
+
active_language = request.GET.get("language_code", active_language)
|
14
|
+
active_language = request.GET.get("language", active_language)
|
15
|
+
self.active_language = active_language
|
16
|
+
self.language_fallbacks = True
|
17
|
+
if (
|
18
|
+
len(self.active_language.split("-")) == 2
|
19
|
+
and self.active_language.split("-")[0] == "nofallbacks"
|
20
|
+
):
|
21
|
+
self.language_fallbacks = False
|
22
|
+
self.active_language = self.active_language.split("-")[1]
|
23
|
+
translation.activate(self.active_language)
|
24
|
+
return self.active_language
|
25
|
+
|
26
|
+
def initialize_request(self, request, *args, **kwargs):
|
27
|
+
self._get_user_language(request)
|
28
|
+
return super().initialize_request(request, *args, **kwargs)
|
29
|
+
|
30
|
+
def get_queryset(self):
|
31
|
+
if hasattr(super(), "get_queryset"):
|
32
|
+
return super().get_queryset()
|
33
|
+
return self.model.objects.all()
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class OptimViewMixin:
|
2
|
+
def get_serializer_class(self):
|
3
|
+
if hasattr(self, "action_serializers"):
|
4
|
+
if self.action in self.action_serializers:
|
5
|
+
return self.action_serializers[self.action]
|
6
|
+
return super().get_serializer_class()
|
7
|
+
|
8
|
+
def get_serializer_context(self):
|
9
|
+
return {"request": self.request, "action": self.action}
|
10
|
+
|
11
|
+
def get_queryset(self):
|
12
|
+
queryset = super().get_queryset()
|
13
|
+
serializer = self.get_serializer_class()
|
14
|
+
if hasattr(serializer, "optimize_qs"):
|
15
|
+
queryset = serializer.optimize_qs(
|
16
|
+
queryset, context=self.get_serializer_context()
|
17
|
+
)
|
18
|
+
return queryset
|
@@ -2,6 +2,8 @@ from rest_framework.response import Response
|
|
2
2
|
from django.db.models import Q
|
3
3
|
from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
|
4
4
|
from django.contrib.postgres.search import SearchVector, SearchQuery, TrigramSimilarity
|
5
|
+
from camomilla.utils.query_parser import ConditionParser
|
6
|
+
from django.conf import settings
|
5
7
|
|
6
8
|
|
7
9
|
class TrigramSearchMixin:
|
@@ -32,19 +34,6 @@ class PaginateStackMixin:
|
|
32
34
|
list_handler, "shared_model", getattr(list_handler, "model", None)
|
33
35
|
)
|
34
36
|
|
35
|
-
def parse_qs_value(self, string: str):
|
36
|
-
if string and string.startswith("[") and string.endswith("]"):
|
37
|
-
string = [self.parse_qs_value(substr) for substr in string[1:-1].split(",")]
|
38
|
-
elif string and string.lower() in ["true", "false"]:
|
39
|
-
string = string.lower() == "true"
|
40
|
-
elif string and string.isdigit():
|
41
|
-
string = int(string)
|
42
|
-
return string
|
43
|
-
|
44
|
-
def parse_filter(self, filter):
|
45
|
-
filter_name, value = filter.split("=")
|
46
|
-
return filter_name, self.parse_qs_value(value)
|
47
|
-
|
48
37
|
def handle_pagination(self, list_handler=None, items_per_page=None):
|
49
38
|
list_handler = list_handler if list_handler is not None else self.get_queryset()
|
50
39
|
items_per_page = int(
|
@@ -84,8 +73,7 @@ class PaginateStackMixin:
|
|
84
73
|
filters = dict(self.request.GET).get("fltr", [])
|
85
74
|
for filter in filters:
|
86
75
|
try:
|
87
|
-
|
88
|
-
list_handler = list_handler.filter(**{filter_name: value})
|
76
|
+
list_handler = list_handler.filter(ConditionParser(filter).db_query)
|
89
77
|
except Exception:
|
90
78
|
pass
|
91
79
|
return list_handler
|
@@ -95,9 +83,15 @@ class PaginateStackMixin:
|
|
95
83
|
search_string = self.request.GET.get("search", None)
|
96
84
|
search_fields = search_fields or getattr(self, "search_fields", [])
|
97
85
|
if search_string and len(search_fields) > 0:
|
98
|
-
|
99
|
-
|
100
|
-
|
86
|
+
if "sqlite" in settings.DATABASES["default"]["ENGINE"]:
|
87
|
+
filter_statement = Q()
|
88
|
+
for field in search_fields:
|
89
|
+
filter_statement |= Q(**{field + '__icontains': search_string})
|
90
|
+
return list_handler.filter(filter_statement)
|
91
|
+
else:
|
92
|
+
return list_handler.annotate(
|
93
|
+
search=SearchVector(*search_fields),
|
94
|
+
).filter(search=SearchQuery(search_string))
|
101
95
|
|
102
96
|
return list_handler
|
103
97
|
|
camomilla/views/pages.py
CHANGED
@@ -1,13 +1,14 @@
|
|
1
1
|
from camomilla.models import Page
|
2
|
-
from camomilla.models.page import UrlNode
|
2
|
+
from camomilla.models.page import UrlNode, UrlRedirect
|
3
3
|
from camomilla.permissions import CamomillaBasePermissions
|
4
4
|
from camomilla.serializers import PageSerializer
|
5
5
|
from camomilla.serializers.page import UrlNodeSerializer
|
6
6
|
from camomilla.views.base import BaseModelViewset
|
7
7
|
from camomilla.views.decorators import active_lang
|
8
8
|
from camomilla.views.mixins import BulkDeleteMixin, GetUserLanguageMixin
|
9
|
-
from rest_framework.decorators import api_view
|
9
|
+
from rest_framework.decorators import api_view, permission_classes
|
10
10
|
from rest_framework.response import Response
|
11
|
+
from rest_framework import permissions
|
11
12
|
from django.shortcuts import get_object_or_404
|
12
13
|
|
13
14
|
|
@@ -20,6 +21,15 @@ class PageViewSet(GetUserLanguageMixin, BulkDeleteMixin, BaseModelViewset):
|
|
20
21
|
|
21
22
|
@active_lang()
|
22
23
|
@api_view(["GET"])
|
24
|
+
@permission_classes(
|
25
|
+
[
|
26
|
+
permissions.AllowAny,
|
27
|
+
]
|
28
|
+
)
|
23
29
|
def fetch_page(request, permalink=""):
|
30
|
+
redirect = UrlRedirect.find_redirect_from_url(f"/{permalink}")
|
31
|
+
if redirect:
|
32
|
+
redirect = redirect.redirect()
|
33
|
+
return Response({"redirect": redirect.url, "status": redirect.status_code})
|
24
34
|
node = get_object_or_404(UrlNode, permalink=f"/{permalink}")
|
25
35
|
return Response(UrlNodeSerializer(node, context={"request": request}).data)
|
{django_camomilla_cms-6.0.0b15.dist-info → django_camomilla_cms-6.0.0b17.dist-info}/METADATA
RENAMED
@@ -1,30 +1,33 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: django-camomilla-cms
|
3
|
-
Version: 6.0.
|
3
|
+
Version: 6.0.0b17
|
4
4
|
Summary: Django powered cms
|
5
5
|
Author-email: Lotrèk <dimmitutto@lotrek.it>
|
6
6
|
License: MIT
|
7
|
-
Project-URL: Homepage, https://github.com/
|
7
|
+
Project-URL: Homepage, https://github.com/camomillacms/camomilla-core
|
8
8
|
Keywords: cms,django,api cms
|
9
9
|
Classifier: Environment :: Web Environment
|
10
10
|
Classifier: Framework :: Django
|
11
11
|
Classifier: Intended Audience :: Developers
|
12
12
|
Classifier: Programming Language :: Python
|
13
13
|
Classifier: Programming Language :: Python :: 3
|
14
|
-
Requires-Python:
|
14
|
+
Requires-Python: <=3.13,>=3.8
|
15
15
|
Description-Content-Type: text/markdown
|
16
16
|
License-File: LICENSE
|
17
|
-
Requires-Dist: django-modeltranslation
|
18
|
-
Requires-Dist: djsuperadmin
|
19
|
-
Requires-Dist: djangorestframework
|
20
|
-
Requires-Dist: django-
|
21
|
-
Requires-Dist: Pillow
|
22
|
-
Requires-Dist: django-
|
23
|
-
Requires-Dist: django-
|
24
|
-
Requires-Dist:
|
25
|
-
Requires-Dist:
|
26
|
-
|
27
|
-
|
17
|
+
Requires-Dist: django-modeltranslation<=0.18.12,>=0.18.7
|
18
|
+
Requires-Dist: djsuperadmin<1.0.0,>=0.9
|
19
|
+
Requires-Dist: djangorestframework<=3.14.0,>=3.10.0
|
20
|
+
Requires-Dist: django-structured-json-field>=0.4.1
|
21
|
+
Requires-Dist: Pillow>=10.0.0
|
22
|
+
Requires-Dist: django-admin-interface<1.0.0,>=0.26.0
|
23
|
+
Requires-Dist: django-ckeditor<7.0.0,>=5.7.1
|
24
|
+
Requires-Dist: django-tinymce<5.0.0,>=4.1.0
|
25
|
+
Requires-Dist: python-magic<0.5,>=0.4
|
26
|
+
Requires-Dist: Django<6,>=3.2
|
27
|
+
Requires-Dist: django_jsonform>=2.23
|
28
|
+
Dynamic: license-file
|
29
|
+
|
30
|
+
# camomilla django cms [](https://pypi.org/project/django-camomilla-cms)   [](./LICENSE)
|
28
31
|
|
29
32
|
## Install
|
30
33
|
|
@@ -35,7 +38,7 @@ $ pip install django-camomilla-cms
|
|
35
38
|
## Setup
|
36
39
|
```shell
|
37
40
|
$ mkdir -p camomilla_migrations
|
38
|
-
$ touch camomilla_migrations
|
41
|
+
$ touch camomilla_migrations/__init__.py
|
39
42
|
$ python manage.py makemigrations camomilla
|
40
43
|
$ python manage.py migrate camomilla
|
41
44
|
```
|
@@ -70,3 +73,7 @@ INSTALLED_APPS = [
|
|
70
73
|
|
71
74
|
pip install -r requirements-dev.txt
|
72
75
|
make test
|
76
|
+
|
77
|
+
## Run format with black
|
78
|
+
|
79
|
+
black camomilla
|
@@ -0,0 +1,132 @@
|
|
1
|
+
camomilla/__init__.py,sha256=xH1i4zwYfJScQn-ySoem00poi295diBqrVChxqcmkW8,251
|
2
|
+
camomilla/apps.py,sha256=eUwb9ynyiRAc5OXgt7ZsAdhsCOnPCpNdIFYMheNeN-o,532
|
3
|
+
camomilla/authentication.py,sha256=jz6tQT4PPEu-_JLox1LZrOy7EiWBb9MWaObK63MJGus,855
|
4
|
+
camomilla/context_processors.py,sha256=cGowjDZ-oDGYn1j2Pj5QDGCqnzXAOdOwp5dmzin_FTc,165
|
5
|
+
camomilla/defaults.py,sha256=VNQ_sbxu09AyFGNpUUYypIAyhlBhEORD36BBNj7e73I,1220
|
6
|
+
camomilla/dynamic_pages_urls.py,sha256=wd52ktpY_LH24jTW77vII7XZ25p_Kz5MSjes8s_94-A,1278
|
7
|
+
camomilla/exceptions.py,sha256=gLniAsK_pmsNNKGMv5Z384LXVbM8oeHcOwz4F91u1LY,111
|
8
|
+
camomilla/model_api.py,sha256=-7l3fc2eN1itCMzkWA8nFaQXMmz0vs7IlGlShF-gSuo,2487
|
9
|
+
camomilla/parsers.py,sha256=fL8XGCGPxJIZNZkPdGtnPSbDP-6-yzGOCVMuLPjkx9Y,1975
|
10
|
+
camomilla/permissions.py,sha256=9NlBO4JMmg36vXCUjPNyq6uZxhkdrnXyIbJVLtWhGWE,1813
|
11
|
+
camomilla/redirects.py,sha256=ilcyHidb5Iw3jTrXMnPntr50kkl_WB3QOB0VNkIxP7A,263
|
12
|
+
camomilla/settings.py,sha256=nY-a1PRhbQ_edvNG5WyndPLWxwsRb_h4eFAjmOHvKYM,3599
|
13
|
+
camomilla/sitemap.py,sha256=U2t5TwhB_-sEscmQZ69PZ5st3bIap8NRxzWEvCgB130,786
|
14
|
+
camomilla/translation.py,sha256=_QyfTlKG6hQ_ClRfxzeJ-3oI3Nu5peJN9xFkO9Ib3As,1316
|
15
|
+
camomilla/urls.py,sha256=XgaeFoG2eXlJQve3KmFKlD-74CMLW1ziaY1mq-lrAiA,2095
|
16
|
+
camomilla/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
17
|
+
camomilla/contrib/modeltranslation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
18
|
+
camomilla/contrib/modeltranslation/hvad_migration.py,sha256=3j_q_Q85eF4iHbU4LG1Zr3LOmfmGmFiVSL-C8KvPsJQ,5409
|
19
|
+
camomilla/fields/__init__.py,sha256=gKrJwHvUA3q_wu-OVV0hrRZmFkT4znMHrmZtpriDumw,323
|
20
|
+
camomilla/fields/json.py,sha256=tWEDn6kwTP6pNB53djxuVPu2d57m9cIDc4ccCEfUbDQ,1938
|
21
|
+
camomilla/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
22
|
+
camomilla/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
23
|
+
camomilla/management/commands/regenerate_thumbnails.py,sha256=pKToASR8p8TJezGpFfuylsAHtriNueJ7xqJJxq55adY,496
|
24
|
+
camomilla/managers/__init__.py,sha256=Zwp28E1RKafIl0FqcUi1YNHxF19IsMIvhlhS-Njg9Mw,60
|
25
|
+
camomilla/managers/pages.py,sha256=DYV1i5vPgxn1YzJlY3kSlsVCRgsiojLxjs2O0LylxEI,3925
|
26
|
+
camomilla/models/__init__.py,sha256=y7Q34AGhQ1weJUKWb6XjiyUbwRnJeylOBGMErU0wqYg,147
|
27
|
+
camomilla/models/article.py,sha256=LgkZgRsubtDV6NwBz8E2bIgKD6H3I-1QLAxEan5TYYs,1139
|
28
|
+
camomilla/models/content.py,sha256=mIgtifb_WMIt58we5u6qWZemHvuDN1zZaBeCyzHL78A,956
|
29
|
+
camomilla/models/media.py,sha256=pD-qldiHDOOHgux4lsivQLBcOJJrRx3a4Bg8ODNx7r0,6852
|
30
|
+
camomilla/models/menu.py,sha256=hUszPcn1prWCDhk4RPvbITmyhsB2CjFkaerx9t1GWnc,3766
|
31
|
+
camomilla/models/page.py,sha256=lkZPdPkl8Yxpz_NfYrpr7Myp8hB3bEAGtdJwKBR7_cg,19385
|
32
|
+
camomilla/models/mixins/__init__.py,sha256=c2NixqvrIX4E9WGRqQbylXlqBWDXEqN9mzs_dpB0hFQ,1248
|
33
|
+
camomilla/openapi/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
34
|
+
camomilla/openapi/schema.py,sha256=C22dhKjaJ2DTK4KWFjyMJXiwe8NLy7ZTW5d-I1dqZ7g,2546
|
35
|
+
camomilla/serializers/__init__.py,sha256=8v1GsJ_YZ6T72VnKBb5l-8K93oaLf4PIsMt-yFtK-Gk,176
|
36
|
+
camomilla/serializers/article.py,sha256=pYVcS0KztzjzSqgruElQMMEZcqTzmQUqXrdv_Sx5Az4,401
|
37
|
+
camomilla/serializers/content_type.py,sha256=qB2wkmkvQI6LHxfSI6auEh6M9cJRFBaHnpmkBCCzeYo,557
|
38
|
+
camomilla/serializers/media.py,sha256=H4JVpRVxXVmn_BiqrjihKXpfLm9fLmHDFIICRDGJU4s,1940
|
39
|
+
camomilla/serializers/menu.py,sha256=TdoyXs40PqxNevnRbBbYOOX9rUv9zQGiHFNduspaZnw,552
|
40
|
+
camomilla/serializers/page.py,sha256=NNjEypVYu_9iKqdHV_-61ea37gxiHlDP5gsloV_i6yg,1834
|
41
|
+
camomilla/serializers/user.py,sha256=CzrHiVRvYYWNE4eNpCNKtJB7DjVqHHwIcP4NUBXMHSo,3706
|
42
|
+
camomilla/serializers/utils.py,sha256=XRL4CNwQDBNpX8xT7365Dw2Cyx8Kvh18GaadgjS9awk,968
|
43
|
+
camomilla/serializers/validators.py,sha256=X2uBlh348nJjUWHPtiu9XKCD7Etsdg0811a4xHLAUzU,2103
|
44
|
+
camomilla/serializers/base/__init__.py,sha256=maaL3y6tvc5Ph9y07KVcMvZVYTkzh_3aBiBnGCoi1EA,799
|
45
|
+
camomilla/serializers/fields/__init__.py,sha256=0I_E9oMtlC0H48LjStMQZiZ-Ycoy49nWK9GvA5JWxN8,145
|
46
|
+
camomilla/serializers/fields/file.py,sha256=yjKMho2ti9TIAzo6nwyLnNPJ6GVUumL2wxhegvYqI2o,800
|
47
|
+
camomilla/serializers/fields/related.py,sha256=qQQeUxIZSNqnVRHsXocLGmBNHjAvrlr0eDY9K2uCjWk,5069
|
48
|
+
camomilla/serializers/mixins/__init__.py,sha256=gMyFpSWHLtLTlKucP1Xk-GtBNX48CwwyY6_wEN3L9vA,640
|
49
|
+
camomilla/serializers/mixins/fields.py,sha256=h-YBHBITLGHsrXRgSYP3NBoUzJqmQ_5HhhftoFFNsKs,711
|
50
|
+
camomilla/serializers/mixins/filter_fields.py,sha256=sqnCG1hi_qfISFJvwgh37BLyQoNDifR0pU7zmJn_MW8,2236
|
51
|
+
camomilla/serializers/mixins/json.py,sha256=Iq5S7mUg5_Y4bsm6XN3ECJR07NGjCAQ21_wdxj6RgyY,1193
|
52
|
+
camomilla/serializers/mixins/language.py,sha256=VukEvPzTpKQfwB-z_RtoNIJ43N3OEgfjLpHvn9KuZDU,1205
|
53
|
+
camomilla/serializers/mixins/nesting.py,sha256=gCEU2UE_Y8e4VRnvT0AExFgwTfJm_jnSqa6l2SwZ3Mg,1432
|
54
|
+
camomilla/serializers/mixins/optimize.py,sha256=zAtbtk6kfGq9FnapqI8tVYOuMKd1IkHbAV6LffL61Z4,3845
|
55
|
+
camomilla/serializers/mixins/ordering.py,sha256=rXQOz47_U4IsMT6IBhySghcmJWMZgpPWHovDcZQG88k,1172
|
56
|
+
camomilla/serializers/mixins/page.py,sha256=Ida7dY9MQTv747_dpPGpo6u3iL5GX87z4zeVct-C36s,2132
|
57
|
+
camomilla/serializers/mixins/translation.py,sha256=Om2UT2EB4Xvp8SrIEvraY0kJXR9H54AsYBr9DKjsay8,4323
|
58
|
+
camomilla/storages/__init__.py,sha256=ytGgX59Ar8vFfYz7eV8z-j5yO_5FqxdZM25iyLnJuSA,131
|
59
|
+
camomilla/storages/default.py,sha256=GNzvV_JZpXMcfTkyXjw5CfK8EIBi3o-NXYBO0KAxD5M,351
|
60
|
+
camomilla/storages/optimize.py,sha256=VGSXZigzZC8LnPTqyTOpPA2Ba9EJB_KC5bcACoRs4GA,2762
|
61
|
+
camomilla/storages/overwrite.py,sha256=jvW3zHvXNzH9dIjeZmmfXo_O3K1ZQmLQzmlSKAOE8ZA,360
|
62
|
+
camomilla/templates/admin/camomilla/page/change_form.html,sha256=ig7rRUtylDZMINBQuVPpZLmeB4sOTV_VtqnTgzAyxEo,251
|
63
|
+
camomilla/templates/defaults/base.html,sha256=pklt7Pif3g9d7gwgRxCQj7gniJaHD14ZqZID_xIlC0A,6638
|
64
|
+
camomilla/templates/defaults/articles/default.html,sha256=1f89jBvNtTa1mPAbC91yy8CzeAjTWO3hhQsTuQW5OKg,239
|
65
|
+
camomilla/templates/defaults/pages/default.html,sha256=bP81Qb6M56I-fBJMywWwEu_cnERtWIX28UkGrUSRU6M,144
|
66
|
+
camomilla/templates/defaults/parts/langswitch.html,sha256=AkaQzb2KNjRYCMLUn_jE31V36rwBIwp4MneirWPiBcI,3424
|
67
|
+
camomilla/templates/defaults/parts/menu.html,sha256=ReE-FfmfCNuNkJI33QqIfmMgLSBl3FTkWAhEa59aD3A,381
|
68
|
+
camomilla/templates/defaults/widgets/media_select_multiple.html,sha256=k2XYou8KkPuFLnPMkPJAFJ-zGJj2Xvu6R3ZmiKa3g7Q,3727
|
69
|
+
camomilla/templates_context/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
70
|
+
camomilla/templates_context/autodiscover.py,sha256=td7SCsqg3iNPnv1HDEDEpwWgWLjy5Zmc8Nbze1_J46I,1907
|
71
|
+
camomilla/templates_context/rendering.py,sha256=GfTR45_gC7WT7zTKPVXkBDwe22uF63A-DfZUW31woAU,3194
|
72
|
+
camomilla/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
73
|
+
camomilla/templatetags/camomilla_filters.py,sha256=35x0-cWrRHeLhqypSLlJzEFY_fQDcRHiwZQpFgIsspE,692
|
74
|
+
camomilla/templatetags/menus.py,sha256=7fc4f9DDqtqG6wNb5_Q0km-fq0mqvGnbpR21qO1TJUw,960
|
75
|
+
camomilla/theme/__init__.py,sha256=3ahSYJ-HBLHrW2VZ45mAM6mlZvpqDKPffxhE0EkD9as,30
|
76
|
+
camomilla/theme/apps.py,sha256=3nCSZ6d4tkx2aMNNnQQoqX8PGrrMNf4VxCxBV_JXNrA,1040
|
77
|
+
camomilla/theme/admin/__init__.py,sha256=TALAZaE-gWshSeGc6yy7VahdX5UfeCeoOE9Q5kJCEpM,2270
|
78
|
+
camomilla/theme/admin/pages.py,sha256=y3rL1nwZlytyD-YR_qqLiBAmjCAjkBY3v56V6JdhBvY,1908
|
79
|
+
camomilla/theme/admin/translations.py,sha256=iAjGM1A1aYrsz1FpeybROk6rn3Ddl_oUCwgU5oD8nSw,308
|
80
|
+
camomilla/theme/static/admin/css/responsive.css,sha256=yGq6qXrr8xEVsXTnprIBgkX-sMGZrNf0Kkh-xDxf6yE,157
|
81
|
+
camomilla/theme/static/admin/img/favicon.ico,sha256=qpKv_2MaGILvyihnD1Vq9Yk-ZXGkxWTW26ciMeBFMYU,15406
|
82
|
+
camomilla/theme/static/admin/img/logo.svg,sha256=WBAORIV_LzEOdcpq7iU6d0Nu3omgk2gn05f6LnPjfWg,3238
|
83
|
+
camomilla/theme/templates/admin/base.html,sha256=veKZ6KlnUYILeY4I2MYI18BbZMDec9MgzVzUqCHnjDc,254
|
84
|
+
camomilla/theme/templates/rosetta/base.html,sha256=s9Ijf1nZx8um30R5Gk3g-w-RwFGv2QGViHufyLLiiDY,16731
|
85
|
+
camomilla/utils/__init__.py,sha256=Ui7nzSh45UMMFtCGF1xFHKyJMNWflG_Nx72HAJHJHFM,100
|
86
|
+
camomilla/utils/getters.py,sha256=6j18grFAZ8BC70SriycFDTQFxTnudGn0uKGA83_Rclk,798
|
87
|
+
camomilla/utils/normalization.py,sha256=RDCZtjwpEEwjvfUjQl2bEWFKw7NxTzkXco72VeO2M9w,255
|
88
|
+
camomilla/utils/query_parser.py,sha256=TUScPzPVVJzaKdqy5NqtMOft3H5Bx6liXTVPM1yjH24,6303
|
89
|
+
camomilla/utils/seo.py,sha256=8p_a_TGgohenpJb094tT4mMxbn2xzW0qDILuTnjNocM,3324
|
90
|
+
camomilla/utils/setters.py,sha256=LV57SM65rL1_ZQkVzk9al_Q13lndVywXLkqgfIvgS0Y,915
|
91
|
+
camomilla/utils/templates.py,sha256=Lv4-5019cnM30HmdZnYWiU5gxry-eFZVAhwOofGQRDs,598
|
92
|
+
camomilla/utils/translation.py,sha256=w5tvTInDLegWBb1TnDWo09ckKY3K6hajuNNsngZIxPQ,4205
|
93
|
+
camomilla/views/__init__.py,sha256=94QuOnnbfMMb17mruO2ydUt286-8zBmDxEPWrJv5Wog,178
|
94
|
+
camomilla/views/articles.py,sha256=qGxebOA5iTbGGe9PfbH40YBoDPKktH8FJongg6rh2R8,571
|
95
|
+
camomilla/views/contents.py,sha256=JxvnmgeK8JEmCMLzVG8pVq2DwvmjXtgnIdsDnn74tA4,1205
|
96
|
+
camomilla/views/decorators.py,sha256=hR--nTGQn2mMKDrWn-0Ildzbsvp11OfoWAtedKEzmiA,982
|
97
|
+
camomilla/views/languages.py,sha256=Rt_X7s3dbDBv4dxsQ9fnav_u0TAzzo8fGKBBx3esDsg,441
|
98
|
+
camomilla/views/medias.py,sha256=XYa-NTLLQmSSynpfrFT3av-K_r59aRns3dTGfYMj-0Q,3002
|
99
|
+
camomilla/views/menus.py,sha256=Kpygnf3tMKJ30gcblUES2NW83A37Vy75ecSGSvExGKM,3301
|
100
|
+
camomilla/views/pages.py,sha256=UL74_u-18QdAkjVl74AVWZbRarEdIPrANTzdcM4iqmE,1338
|
101
|
+
camomilla/views/tags.py,sha256=XcYRlcBFSPPY32lt7POb6fWPJL_8HsTo5JcHcAOiOKw,479
|
102
|
+
camomilla/views/users.py,sha256=_fvsKOEtep4SJLvMva2_q-HdLQT_1KlFNt4wcl3xCJk,3130
|
103
|
+
camomilla/views/base/__init__.py,sha256=t-7tqY_ep4Xi8YgB1sXDgNWQ5oh2YEUlfQWU5pltwJ0,1063
|
104
|
+
camomilla/views/mixins/__init__.py,sha256=Znv3fLYVy6lgu03Q_D8fTen4zMxI6VSRaLPDU8Cp7Ws,473
|
105
|
+
camomilla/views/mixins/bulk_actions.py,sha256=i0duWW6wey9m7I_V8-gPcHsbJyPEfSdMdj4h2i-CbPw,787
|
106
|
+
camomilla/views/mixins/language.py,sha256=hfnYznlVMrMLBdJ_f8dChJWENg7Kpt9m1yqavrdLm7E,1299
|
107
|
+
camomilla/views/mixins/optimize.py,sha256=iRPNkoeIIlJugk7DjJhDPaqeX7Opi7TxnUoMDnxJxUk,686
|
108
|
+
camomilla/views/mixins/ordering.py,sha256=mh7fqPyVCVJh84Nl2pYFQouzGxa-ANF3Wqv0pCb7OVU,4779
|
109
|
+
camomilla/views/mixins/pagination.py,sha256=NWerBdMyBt4Kswig4fbANqGTzsll8SJdE6a8_UIoueU,5772
|
110
|
+
camomilla/views/mixins/permissions.py,sha256=TPmR3Hoa3BjeJu9rCE_7lpLOAupue4WI42C21HTo6X4,200
|
111
|
+
django_camomilla_cms-6.0.0b17.dist-info/licenses/LICENSE,sha256=kVS7zDrNkav2hLLXbOJwVdonY2ToApTK3khyJagGQoQ,1063
|
112
|
+
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
113
|
+
tests/test_api.py,sha256=t03EFDezGgm4UJl8RIVvnTUkAGTB6ptm0G2lHBQ7ljc,1833
|
114
|
+
tests/test_camomilla_filters.py,sha256=5LlR3tctGu6qxVmOrY52AGh_ACvEzdAvkwH2v7medpo,1536
|
115
|
+
tests/test_media.py,sha256=IG-cigWxDnsq54DRVaPO2O0lcsnutfKRsqPTTR8W1E8,4183
|
116
|
+
tests/test_menu.py,sha256=HyEdDzTDsLtNzsTNGjjZ6SCrgTXRU3FkKviUOacnYRg,3582
|
117
|
+
tests/test_model_api.py,sha256=Ne8YlXTH2cqP5gzOc8UKjJuh0t-NaKHh5Ol9krpVHQg,3768
|
118
|
+
tests/test_model_api_permissions.py,sha256=7CSb4-yIOfycAL_vXvh1dE2whx7k0gNkWl9LO0yzy4I,1801
|
119
|
+
tests/test_model_api_register.py,sha256=9pqf7fvtniw63ZD4P2JItfDC0brD55vIrqjZ4phm_qs,14016
|
120
|
+
tests/test_models.py,sha256=WJs8lxWZWn1l7X3a_QFVc8fF5LHTsI8bc3uhQe6-o-Q,684
|
121
|
+
tests/test_pages.py,sha256=qIVdfmbtx7GKHyNWHhirR58gGr9zjfrrzXXkvYlLusc,11469
|
122
|
+
tests/test_query_parser.py,sha256=R9l0L2QDEDcm2b6IFUhyf7wMXLzL9RySLkzKTWRtBkE,2097
|
123
|
+
tests/test_templates_context.py,sha256=D72ufRqCGjInGGXHSNVhlJ1HcWG0zMqrAiTuiaU057k,4694
|
124
|
+
tests/test_utils.py,sha256=o_FG7XOxLePOBfwBr4sk09gej0onWNw9t2-gSjGmgNg,3741
|
125
|
+
tests/fixtures/__init__.py,sha256=NGj22kLV65v56IpOrOVqSkPhJePTXD4QjuuZhZSMwfQ,460
|
126
|
+
tests/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
127
|
+
tests/utils/api.py,sha256=TYcDXeILHtBwzwG0acwPFmiqMZnlF9VnLB0Ydhg55vA,865
|
128
|
+
tests/utils/media.py,sha256=ChsoHqwWmVYHE7teFsV9swTqBEFD0zcvQSBuKsK9G_s,298
|
129
|
+
django_camomilla_cms-6.0.0b17.dist-info/METADATA,sha256=oiU3jK3ZCjQurWBdq2vaUZgBr2OMim4RLEjSPtIVm28,2565
|
130
|
+
django_camomilla_cms-6.0.0b17.dist-info/WHEEL,sha256=oSJJyWjO7Z2XSScFQUpXG1HL-N0sFMqqeKVVbZTPkWc,109
|
131
|
+
django_camomilla_cms-6.0.0b17.dist-info/top_level.txt,sha256=G9VIGBmMMqC7JEckoTgXKmC6T2BR75QRkqRnngw1_lo,16
|
132
|
+
django_camomilla_cms-6.0.0b17.dist-info/RECORD,,
|
@@ -0,0 +1,17 @@
|
|
1
|
+
import json
|
2
|
+
import os
|
3
|
+
from django.core.files.uploadedfile import SimpleUploadedFile
|
4
|
+
|
5
|
+
|
6
|
+
def load_json_fixture(filename):
|
7
|
+
with open(os.path.join(os.path.dirname(__file__), 'json', filename), "r") as f:
|
8
|
+
return json.load(f)
|
9
|
+
|
10
|
+
|
11
|
+
def load_asset(filename):
|
12
|
+
with open(os.path.join(os.path.dirname(__file__), 'assets', filename), "rb") as f:
|
13
|
+
up_file = SimpleUploadedFile(
|
14
|
+
filename,
|
15
|
+
f.read()
|
16
|
+
)
|
17
|
+
return up_file
|