django-camomilla-cms 5.8.5__py2.py3-none-any.whl → 6.0.0__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.
Files changed (143) hide show
  1. camomilla/__init__.py +8 -2
  2. camomilla/apps.py +9 -1
  3. camomilla/context_processors.py +6 -0
  4. camomilla/contrib/modeltranslation/__init__.py +0 -0
  5. camomilla/contrib/modeltranslation/hvad_migration.py +126 -0
  6. camomilla/dynamic_pages_urls.py +33 -0
  7. camomilla/fields/__init__.py +13 -0
  8. camomilla/{fields.py → fields/json.py} +15 -18
  9. camomilla/management/commands/regenerate_thumbnails.py +0 -1
  10. camomilla/managers/__init__.py +3 -0
  11. camomilla/managers/pages.py +116 -0
  12. camomilla/model_api.py +86 -0
  13. camomilla/models/__init__.py +5 -6
  14. camomilla/models/article.py +26 -44
  15. camomilla/models/content.py +8 -15
  16. camomilla/models/media.py +70 -97
  17. camomilla/models/menu.py +106 -0
  18. camomilla/models/mixins/__init__.py +10 -48
  19. camomilla/models/page.py +521 -20
  20. camomilla/openapi/__init__.py +0 -0
  21. camomilla/openapi/schema.py +67 -0
  22. camomilla/parsers.py +0 -1
  23. camomilla/redirects.py +10 -0
  24. camomilla/serializers/__init__.py +2 -0
  25. camomilla/serializers/article.py +5 -10
  26. camomilla/serializers/base/__init__.py +21 -17
  27. camomilla/serializers/content_type.py +17 -0
  28. camomilla/serializers/fields/__init__.py +6 -20
  29. camomilla/serializers/fields/file.py +5 -0
  30. camomilla/serializers/fields/related.py +24 -4
  31. camomilla/serializers/media.py +6 -8
  32. camomilla/serializers/menu.py +17 -0
  33. camomilla/serializers/mixins/__init__.py +23 -187
  34. camomilla/serializers/mixins/fields.py +20 -0
  35. camomilla/serializers/mixins/filter_fields.py +57 -0
  36. camomilla/serializers/mixins/json.py +34 -0
  37. camomilla/serializers/mixins/language.py +32 -0
  38. camomilla/serializers/mixins/nesting.py +35 -0
  39. camomilla/serializers/mixins/optimize.py +91 -0
  40. camomilla/serializers/mixins/ordering.py +34 -0
  41. camomilla/serializers/mixins/page.py +58 -0
  42. camomilla/serializers/mixins/translation.py +103 -0
  43. camomilla/serializers/page.py +53 -4
  44. camomilla/serializers/user.py +5 -4
  45. camomilla/serializers/utils.py +38 -0
  46. camomilla/serializers/validators.py +51 -0
  47. camomilla/settings.py +118 -0
  48. camomilla/sitemap.py +30 -0
  49. camomilla/storages/__init__.py +4 -0
  50. camomilla/storages/default.py +12 -0
  51. camomilla/storages/optimize.py +71 -0
  52. camomilla/{storages.py → storages/overwrite.py} +2 -2
  53. camomilla/templates/admin/camomilla/page/change_form.html +10 -0
  54. camomilla/templates/defaults/articles/default.html +7 -0
  55. camomilla/templates/defaults/base.html +170 -0
  56. camomilla/templates/defaults/pages/default.html +3 -0
  57. camomilla/templates/defaults/parts/langswitch.html +83 -0
  58. camomilla/templates/defaults/parts/menu.html +15 -0
  59. camomilla/templates_context/__init__.py +0 -0
  60. camomilla/templates_context/autodiscover.py +51 -0
  61. camomilla/templates_context/rendering.py +89 -0
  62. camomilla/templatetags/camomilla_filters.py +6 -5
  63. camomilla/templatetags/menus.py +37 -0
  64. camomilla/templatetags/model_extras.py +77 -0
  65. camomilla/theme/__init__.py +1 -1
  66. camomilla/theme/admin/__init__.py +99 -0
  67. camomilla/theme/admin/pages.py +46 -0
  68. camomilla/theme/admin/translations.py +13 -0
  69. camomilla/theme/apps.py +38 -0
  70. camomilla/theme/static/admin/css/responsive.css +5 -1021
  71. camomilla/theme/static/admin/img/favicon.ico +0 -0
  72. camomilla/theme/static/admin/img/logo.svg +31 -0
  73. camomilla/theme/templates/admin/base.html +7 -0
  74. camomilla/theme/templates/rosetta/base.html +196 -0
  75. camomilla/translation.py +61 -0
  76. camomilla/urls.py +38 -17
  77. camomilla/utils/__init__.py +4 -0
  78. camomilla/utils/getters.py +27 -0
  79. camomilla/utils/normalization.py +7 -0
  80. camomilla/utils/query_parser.py +167 -0
  81. camomilla/{utils.py → utils/seo.py} +13 -15
  82. camomilla/utils/setters.py +37 -0
  83. camomilla/utils/templates.py +32 -0
  84. camomilla/utils/translation.py +114 -0
  85. camomilla/views/__init__.py +1 -1
  86. camomilla/views/articles.py +5 -7
  87. camomilla/views/base/__init__.py +35 -5
  88. camomilla/views/contents.py +6 -11
  89. camomilla/views/decorators.py +26 -0
  90. camomilla/views/medias.py +24 -19
  91. camomilla/views/menus.py +81 -0
  92. camomilla/views/mixins/__init__.py +17 -73
  93. camomilla/views/mixins/bulk_actions.py +22 -0
  94. camomilla/views/mixins/language.py +33 -0
  95. camomilla/views/mixins/optimize.py +18 -0
  96. camomilla/views/mixins/ordering.py +2 -2
  97. camomilla/views/mixins/pagination.py +12 -18
  98. camomilla/views/mixins/permissions.py +6 -0
  99. camomilla/views/pages.py +28 -6
  100. camomilla/views/tags.py +5 -6
  101. camomilla/views/users.py +7 -12
  102. django_camomilla_cms-6.0.0.dist-info/METADATA +123 -0
  103. django_camomilla_cms-6.0.0.dist-info/RECORD +133 -0
  104. {django_camomilla_cms-5.8.5.dist-info → django_camomilla_cms-6.0.0.dist-info}/WHEEL +1 -1
  105. tests/fixtures/__init__.py +14 -0
  106. tests/test_api.py +22 -39
  107. tests/test_camomilla_filters.py +11 -13
  108. tests/test_media.py +152 -0
  109. tests/test_menu.py +112 -0
  110. tests/test_model_api.py +113 -0
  111. tests/test_model_api_permissions.py +44 -0
  112. tests/test_model_api_register.py +355 -0
  113. tests/test_pages.py +351 -0
  114. tests/test_query_parser.py +58 -0
  115. tests/test_templates_context.py +149 -0
  116. tests/test_utils.py +64 -64
  117. tests/utils/__init__.py +0 -0
  118. tests/utils/api.py +28 -0
  119. tests/utils/media.py +10 -0
  120. camomilla/admin.py +0 -98
  121. camomilla/migrations/0001_initial.py +0 -577
  122. camomilla/migrations/0002_auto_20200214_1127.py +0 -33
  123. camomilla/migrations/0003_auto_20210130_1610.py +0 -30
  124. camomilla/migrations/0004_auto_20210511_0937.py +0 -25
  125. camomilla/migrations/0005_media_image_props.py +0 -19
  126. camomilla/migrations/0006_auto_20220103_1845.py +0 -35
  127. camomilla/migrations/0007_auto_20220211_1622.py +0 -18
  128. camomilla/migrations/0008_auto_20220309_1616.py +0 -60
  129. camomilla/migrations/0009_article__hvad_query_category__hvad_query_and_more.py +0 -165
  130. camomilla/migrations/0010_auto_20220802_1406.py +0 -83
  131. camomilla/migrations/0011_auto_20220902_1000.py +0 -15
  132. camomilla/models/category.py +0 -25
  133. camomilla/models/tag.py +0 -19
  134. camomilla/theme/static/admin/img/logo.png +0 -0
  135. camomilla/theme/templates/admin/base_site.html +0 -18
  136. camomilla/views/categories.py +0 -13
  137. django_camomilla_cms-5.8.5.dist-info/METADATA +0 -62
  138. django_camomilla_cms-5.8.5.dist-info/RECORD +0 -76
  139. tests/urls.py +0 -21
  140. /camomilla/{migrations → contrib}/__init__.py +0 -0
  141. /camomilla/templates/{camomilla → defaults}/widgets/media_select_multiple.html +0 -0
  142. {django_camomilla_cms-5.8.5.dist-info → django_camomilla_cms-6.0.0.dist-info/licenses}/LICENSE +0 -0
  143. {django_camomilla_cms-5.8.5.dist-info → django_camomilla_cms-6.0.0.dist-info}/top_level.txt +0 -0
@@ -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
+ )
@@ -0,0 +1,32 @@
1
+ from pathlib import Path
2
+ from typing import Sequence
3
+
4
+ from django import template as django_template
5
+ from os.path import relpath
6
+ from camomilla.settings import REGISTERED_TEMPLATES_APPS
7
+
8
+
9
+ def get_all_templates_files() -> Sequence[str]:
10
+ files = []
11
+
12
+ for engine in django_template.loader.engines.all():
13
+
14
+ if REGISTERED_TEMPLATES_APPS:
15
+ dirs = [
16
+ d
17
+ for d in engine.template_dirs
18
+ if any(app in str(d) for app in REGISTERED_TEMPLATES_APPS)
19
+ ]
20
+ else:
21
+ # Exclude pip installed site package template dirs
22
+ dirs = [
23
+ d
24
+ for d in engine.template_dirs
25
+ if "site-packages" not in str(d) or "camomilla" in str(d)
26
+ ]
27
+
28
+ for d in dirs:
29
+ base = Path(d)
30
+ files.extend(relpath(f, d) for f in base.rglob("*.html"))
31
+
32
+ return files
@@ -0,0 +1,114 @@
1
+ import re
2
+ from typing import Any, Sequence, Iterator, Union, List
3
+
4
+ from django.db.models import Model, Q
5
+ from django.utils.translation.trans_real import activate, get_language
6
+ from modeltranslation.settings import AVAILABLE_LANGUAGES, DEFAULT_LANGUAGE
7
+ from modeltranslation.utils import build_localized_fieldname
8
+ from camomilla.settings import BASE_URL
9
+ from django.http import QueryDict
10
+
11
+
12
+ def activate_languages(languages: Sequence[str] = AVAILABLE_LANGUAGES) -> Iterator[str]:
13
+ old = get_language()
14
+ for language in languages:
15
+ activate(language)
16
+ yield language
17
+ activate(old)
18
+
19
+
20
+ def set_nofallbacks(instance: Model, attr: str, value: Any, **kwargs) -> None:
21
+ language = kwargs.pop("language", get_language())
22
+ local_fieldname = build_localized_fieldname(attr, language)
23
+ if hasattr(instance, local_fieldname):
24
+ attr = local_fieldname
25
+ return setattr(instance, attr, value)
26
+
27
+
28
+ def get_nofallbacks(instance: Model, attr: str, *args, **kwargs) -> Any:
29
+ language = kwargs.pop("language", get_language())
30
+ local_fieldname = build_localized_fieldname(attr, language)
31
+ if hasattr(instance, local_fieldname):
32
+ attr = local_fieldname
33
+ return getattr(instance, attr, *args, **kwargs)
34
+
35
+
36
+ def url_lang_decompose(url):
37
+ if BASE_URL and url.startswith(BASE_URL):
38
+ url = url[len(BASE_URL) :]
39
+ data = {"url": url, "permalink": url, "language": DEFAULT_LANGUAGE}
40
+ result = re.match(rf"^/?({'|'.join(AVAILABLE_LANGUAGES)})?/(.*)", url) # noqa: W605
41
+ groups = result and result.groups()
42
+ if groups and len(groups) == 2:
43
+ data["language"] = groups[0]
44
+ data["permalink"] = "/%s" % groups[1]
45
+ return data
46
+
47
+
48
+ def get_field_translations(instance: Model, field_name: str, *args, **kwargs):
49
+ return {
50
+ lang: get_nofallbacks(instance, field_name, language=lang, *args, **kwargs)
51
+ for lang in AVAILABLE_LANGUAGES
52
+ }
53
+
54
+
55
+ def lang_fallback_query(**kwargs):
56
+ current_lang = get_language()
57
+ query = Q()
58
+ for lang in AVAILABLE_LANGUAGES:
59
+ query |= Q(**{f"{key}_{lang}": value for key, value in kwargs.items()})
60
+ if current_lang:
61
+ query = query & Q(
62
+ **{f"{key}_{current_lang}__isnull": True for key in kwargs.keys()}
63
+ )
64
+ query |= Q(**{f"{key}_{current_lang}": value for key, value in kwargs.items()})
65
+ return query
66
+
67
+
68
+ def is_translatable(model: Model) -> bool:
69
+ from modeltranslation.translator import translator
70
+
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
@@ -1,8 +1,8 @@
1
1
  from .articles import *
2
- from .categories import *
3
2
  from .contents import *
4
3
  from .languages import *
5
4
  from .medias import *
6
5
  from .pages import *
7
6
  from .tags import *
8
7
  from .users import *
8
+ from .menus import *
@@ -1,13 +1,11 @@
1
- from .base import BaseModelViewset
2
- from .mixins import GetUserLanguageMixin, BulkDeleteMixin
3
-
4
- from ..models import Article
5
- from ..serializers import ArticleSerializer
6
- from ..permissions import CamomillaBasePermissions
1
+ from camomilla.models import Article
2
+ from camomilla.permissions import CamomillaBasePermissions
3
+ from camomilla.serializers import ArticleSerializer
4
+ from camomilla.views.base import BaseModelViewset
5
+ from camomilla.views.mixins import BulkDeleteMixin, GetUserLanguageMixin
7
6
 
8
7
 
9
8
  class ArticleViewSet(GetUserLanguageMixin, BulkDeleteMixin, BaseModelViewset):
10
-
11
9
  queryset = Article.objects.all()
12
10
  serializer_class = ArticleSerializer
13
11
  permission_classes = (CamomillaBasePermissions,)
@@ -1,8 +1,38 @@
1
- from ..mixins import OptimViewMixin, PaginateStackMixin, OrderingMixin
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
- class BaseModelViewset(
6
- OptimViewMixin, OrderingMixin, PaginateStackMixin, viewsets.ModelViewSet
7
- ):
8
- pass
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
@@ -1,18 +1,16 @@
1
- from .base import BaseModelViewset
2
1
  import json
3
2
 
4
3
  from django.http import JsonResponse
5
- from django.utils.translation import get_language
6
4
  from rest_framework.decorators import action
7
5
 
8
- from ..models import Content
9
- from ..permissions import CamomillaBasePermissions
10
- from ..serializers import ContentSerializer
11
- from .mixins import BulkDeleteMixin, GetUserLanguageMixin
6
+ from camomilla.models import Content
7
+ from camomilla.permissions import CamomillaBasePermissions
8
+ from camomilla.serializers import ContentSerializer
9
+ from camomilla.views.base import BaseModelViewset
10
+ from camomilla.views.mixins import BulkDeleteMixin, GetUserLanguageMixin
12
11
 
13
12
 
14
13
  class ContentViewSet(GetUserLanguageMixin, BulkDeleteMixin, BaseModelViewset):
15
-
16
14
  queryset = Content.objects.all()
17
15
  serializer_class = ContentSerializer
18
16
  model = Content
@@ -20,13 +18,10 @@ class ContentViewSet(GetUserLanguageMixin, BulkDeleteMixin, BaseModelViewset):
20
18
 
21
19
  @action(detail=True, methods=["get", "patch"])
22
20
  def djsuperadmin(self, request, pk):
23
- # content, _ = Content.objects.get_or_create(pk=pk)
24
- # content.translate(get_language())
25
21
  try:
26
- content = Content.objects.language(get_language()).get(pk=pk)
22
+ content = Content.objects.get(pk=pk)
27
23
  except Content.DoesNotExist:
28
24
  content, _ = Content.objects.get_or_create(pk=pk)
29
- content.translate(get_language())
30
25
  if request.method == "GET":
31
26
  return JsonResponse({"content": content.content})
32
27
  if request.method == "PATCH":
@@ -0,0 +1,26 @@
1
+ import functools
2
+ from django.utils.translation import activate
3
+ from django.conf import settings
4
+
5
+
6
+ def active_lang(*args, **kwargs):
7
+ def decorator(func):
8
+ @functools.wraps(func)
9
+ def wrapped_func(*args, **kwargs):
10
+ if len(args) and hasattr(args[0], "request"):
11
+ request = args[0].request
12
+ else:
13
+ request = args[0] if len(args) else kwargs.get("request", None)
14
+ lang = settings.LANGUAGE_CODE
15
+ if request and hasattr(request, "GET"):
16
+ lang = request.GET.get("lang", request.GET.get("language", lang))
17
+ if request and hasattr(request, "data"):
18
+ lang = request.data.pop("lang", request.data.pop("language", lang))
19
+ if lang and lang in [lng[0] for lng in settings.LANGUAGES]:
20
+ activate(lang)
21
+ request.LANGUAGE_CODE = lang
22
+ return func(*args, **kwargs)
23
+
24
+ return wrapped_func
25
+
26
+ return decorator
camomilla/views/medias.py CHANGED
@@ -1,14 +1,20 @@
1
- from .base import BaseModelViewset
2
- from .mixins import BulkDeleteMixin, GetUserLanguageMixin, TrigramSearchMixin
3
- from ..parsers import MultipartJsonParser
4
- from ..permissions import CamomillaBasePermissions
5
-
6
-
7
- from rest_framework.response import Response
8
1
  from rest_framework.parsers import JSONParser
2
+ from rest_framework.response import Response
9
3
 
10
- from ..models import Media, MediaFolder
11
- from ..serializers import MediaSerializer, MediaFolderSerializer, MediaListSerializer
4
+ from camomilla.models import Media, MediaFolder
5
+ from camomilla.parsers import MultipartJsonParser
6
+ from camomilla.permissions import CamomillaBasePermissions
7
+ from camomilla.serializers import (
8
+ MediaFolderSerializer,
9
+ MediaListSerializer,
10
+ MediaSerializer,
11
+ )
12
+ from camomilla.views.base import BaseModelViewset
13
+ from camomilla.views.mixins import (
14
+ BulkDeleteMixin,
15
+ GetUserLanguageMixin,
16
+ TrigramSearchMixin,
17
+ )
12
18
 
13
19
 
14
20
  class ParseMimeMixin(object):
@@ -29,7 +35,7 @@ class MediaFolderViewSet(
29
35
  serializer_class = MediaFolderSerializer
30
36
  permission_classes = (CamomillaBasePermissions,)
31
37
  items_per_page = 18
32
- search_fields = ["name", "title", "alt_text"]
38
+ search_fields = ["title", "alt_text", "file"]
33
39
 
34
40
  def get_queryset(self):
35
41
  return self.model.objects.all()
@@ -38,24 +44,24 @@ class MediaFolderViewSet(
38
44
  return {**super().get_serializer_context(), "action": "list"}
39
45
 
40
46
  def get_mixed_response(self, request, *args, **kwargs):
41
- updir = kwargs.get("pk", None)
47
+ search = self.request.GET.get("search", None)
48
+ all = self.request.GET.get("all", "false").lower() == "true"
49
+ updir = None if all else kwargs.get("pk", None)
50
+ if not search and all:
51
+ self.search_fields = []
42
52
 
43
53
  parent_folder = MediaFolderSerializer(
44
54
  self.model.objects.filter(pk=updir).first()
45
55
  ).data
46
- folder_queryset = self.model.objects.language(self.active_language).fallbacks().filter(updir__pk=updir)
47
- media_queryset = (
48
- Media.objects.language(self.active_language).fallbacks()
49
- if request.GET.get("search", None)
50
- else Media.objects.all()
51
- ).filter(folder__pk=updir)
56
+ folder_queryset = self.model.objects.filter(updir__pk=updir)
57
+ media_queryset = Media.objects.filter(**({} if all else {"folder__pk": updir}))
52
58
 
53
59
  folder_data = MediaFolderSerializer(
54
60
  folder_queryset, many=True, context={"request": request}
55
61
  ).data
56
62
  media_data = self.format_output(
57
63
  *self.handle_pagination_stack(media_queryset),
58
- SerializerClass=MediaListSerializer
64
+ SerializerClass=MediaListSerializer,
59
65
  )
60
66
  return {
61
67
  "folders": folder_data,
@@ -77,7 +83,6 @@ class MediaViewSet(
77
83
  TrigramSearchMixin,
78
84
  BaseModelViewset,
79
85
  ):
80
-
81
86
  queryset = Media.objects.all()
82
87
  serializer_class = MediaSerializer
83
88
  permission_classes = (CamomillaBasePermissions,)
@@ -0,0 +1,81 @@
1
+ from django.contrib.contenttypes.models import ContentType
2
+ from django.db.models import Q
3
+ from django.http import Http404
4
+ from django.shortcuts import get_object_or_404
5
+ from rest_framework.decorators import action
6
+ from rest_framework.response import Response
7
+
8
+ from camomilla.models import AbstractPage, Menu
9
+ from camomilla.models.page import UrlNode
10
+ from camomilla.permissions import CamomillaBasePermissions
11
+ from camomilla.serializers import ContentTypeSerializer, MenuSerializer
12
+ from camomilla.serializers.page import BasicUrlNodeSerializer
13
+ from camomilla.views.base import BaseModelViewset
14
+ from camomilla.views.decorators import active_lang
15
+
16
+
17
+ class MenuViewSet(BaseModelViewset):
18
+ queryset = Menu.objects.all()
19
+ serializer_class = MenuSerializer
20
+ permission_classes = (CamomillaBasePermissions,)
21
+ model = Menu
22
+
23
+ lookup_field = "key"
24
+
25
+ def get_object(self):
26
+ queryset = self.filter_queryset(self.get_queryset())
27
+ lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
28
+ assert lookup_url_kwarg in self.kwargs, (
29
+ "Expected view %s to be called with a URL keyword argument "
30
+ 'named "%s". Fix your URL conf, or set the `.lookup_field` '
31
+ "attribute on the view correctly."
32
+ % (self.__class__.__name__, lookup_url_kwarg)
33
+ )
34
+ filter = Q(**{self.lookup_field: self.kwargs[lookup_url_kwarg]})
35
+ if (
36
+ isinstance(self.kwargs[lookup_url_kwarg], str)
37
+ and self.kwargs[lookup_url_kwarg].isnumeric()
38
+ or isinstance(self.kwargs[lookup_url_kwarg], int)
39
+ ):
40
+ filter |= Q(pk=self.kwargs[lookup_url_kwarg])
41
+
42
+ obj = get_object_or_404(queryset, filter)
43
+ # May raise a permission denied
44
+ self.check_object_permissions(self.request, obj)
45
+
46
+ return obj
47
+
48
+ @action(detail=False, methods=["get"], url_path="page_types")
49
+ def page_types(self, request, *args, **kwargs):
50
+ return Response(
51
+ ContentTypeSerializer(
52
+ [
53
+ model
54
+ for model in ContentType.objects.all()
55
+ if model.model_class()
56
+ and issubclass(model.model_class(), AbstractPage)
57
+ ],
58
+ many=True,
59
+ ).data
60
+ )
61
+
62
+ @action(detail=False, methods=["get"], url_path=r"page_types/(?P<content_id>\w+)")
63
+ def page_type_instances(self, request, content_id, *args, **kwargs):
64
+ content_type = ContentType.objects.filter(pk=content_id).first()
65
+ if not content_type or not issubclass(content_type.model_class(), AbstractPage):
66
+ raise Http404("No object matches the given query.")
67
+ return Response(
68
+ [
69
+ {"id": obj.pk, "name": str(obj), "url_node_id": obj.url_node.pk}
70
+ for obj in content_type.model_class().objects.exclude(
71
+ url_node__isnull=True
72
+ )
73
+ ]
74
+ )
75
+
76
+ @active_lang()
77
+ @action(detail=False, methods=["get"], url_path="search_urlnode")
78
+ def search_urlnode(self, request, *args, **kwargs):
79
+ url_node = request.GET.get("q", "")
80
+ qs = UrlNode.objects.filter(permalink__icontains=url_node).order_by("permalink")
81
+ return Response(BasicUrlNodeSerializer(qs, many=True).data)
@@ -1,73 +1,17 @@
1
- from rest_framework import status
2
- from rest_framework.decorators import action
3
- from rest_framework.response import Response
4
- from .pagination import *
5
- from .ordering import *
6
- from ...permissions import CamomillaBasePermissions
7
- from django.utils import translation
8
-
9
-
10
- class GetUserLanguageMixin(object):
11
- def _get_user_language(self, request):
12
- self.active_language = request.GET.get(
13
- "language",
14
- request.GET.get(
15
- "language_code", translation.get_language_from_request(request)
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 (
34
- self.model.objects.language(self.active_language).fallbacks().all()
35
- if self.language_fallbacks
36
- else self.model.objects.language(self.active_language).all()
37
- )
38
-
39
-
40
- class OptimViewMixin:
41
- def get_serializer_class(self):
42
- if hasattr(self, "action_serializers"):
43
- if self.action in self.action_serializers:
44
- return self.action_serializers[self.action]
45
- return super().get_serializer_class()
46
-
47
- def get_serializer_context(self):
48
- return {"request": self.request, "action": self.action}
49
-
50
- def get_queryset(self):
51
- queryset = super().get_queryset()
52
- serializer = self.get_serializer_class()
53
- if hasattr(serializer, "setup_eager_loading"):
54
- queryset = self.get_serializer_class().setup_eager_loading(queryset)
55
- return queryset
56
-
57
-
58
- class BulkDeleteMixin(object):
59
- @action(
60
- detail=False, methods=["post"], permission_classes=(CamomillaBasePermissions,)
61
- )
62
- def bulk_delete(self, request):
63
- try:
64
- self.model.objects.filter(pk__in=request.data).delete()
65
- return Response(
66
- {"detail": "Eliminazione multipla andata a buon fine"},
67
- status=status.HTTP_200_OK,
68
- )
69
- except Exception:
70
- return Response(
71
- {"detail": "Eliminazione multipla non riuscita"},
72
- status=status.HTTP_500_INTERNAL_SERVER_ERROR,
73
- )
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
@@ -1,12 +1,12 @@
1
1
  from rest_framework.decorators import action
2
2
  from rest_framework.response import Response
3
- from ...permissions import CamomillaBasePermissions
3
+ from camomilla.permissions import CamomillaBasePermissions
4
4
  from django.core.exceptions import ImproperlyConfigured
5
5
  from django.db import transaction, router
6
6
  from django.db.models.signals import post_save, pre_save
7
7
  from itertools import chain
8
8
  from django.db.models.expressions import F
9
- from ...fields import ORDERING_ACCEPTED_FIELDS
9
+ from camomilla.fields import ORDERING_ACCEPTED_FIELDS
10
10
 
11
11
 
12
12
  class OrderingMixin: