django-camomilla-cms 6.0.0b2__py2.py3-none-any.whl → 6.0.0b4__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 (53) hide show
  1. camomilla/__init__.py +1 -1
  2. camomilla/apps.py +3 -0
  3. camomilla/contrib/modeltranslation/hvad_migration.py +1 -2
  4. camomilla/contrib/rest_framework/serializer.py +31 -1
  5. camomilla/dynamic_pages_urls.py +7 -1
  6. camomilla/fields/json.py +12 -9
  7. camomilla/management/commands/regenerate_thumbnails.py +0 -1
  8. camomilla/model_api.py +6 -4
  9. camomilla/models/__init__.py +5 -5
  10. camomilla/models/article.py +0 -1
  11. camomilla/models/media.py +1 -2
  12. camomilla/models/menu.py +44 -42
  13. camomilla/models/mixins/__init__.py +0 -1
  14. camomilla/models/page.py +66 -32
  15. camomilla/openapi/schema.py +27 -0
  16. camomilla/parsers.py +0 -1
  17. camomilla/serializers/fields/json.py +7 -75
  18. camomilla/serializers/mixins/__init__.py +16 -2
  19. camomilla/serializers/page.py +47 -0
  20. camomilla/serializers/user.py +2 -3
  21. camomilla/serializers/utils.py +22 -17
  22. camomilla/settings.py +21 -1
  23. camomilla/storages/optimize.py +1 -1
  24. camomilla/structured/__init__.py +90 -75
  25. camomilla/structured/cache.py +193 -0
  26. camomilla/structured/fields.py +132 -275
  27. camomilla/structured/models.py +45 -138
  28. camomilla/structured/utils.py +114 -0
  29. camomilla/templatetags/camomilla_filters.py +0 -1
  30. camomilla/theme/__init__.py +1 -1
  31. camomilla/theme/admin.py +96 -0
  32. camomilla/theme/apps.py +12 -1
  33. camomilla/translation.py +4 -2
  34. camomilla/urls.py +13 -6
  35. camomilla/utils/__init__.py +1 -1
  36. camomilla/utils/getters.py +11 -1
  37. camomilla/utils/templates.py +2 -2
  38. camomilla/utils/translation.py +9 -6
  39. camomilla/views/__init__.py +1 -1
  40. camomilla/views/articles.py +0 -1
  41. camomilla/views/contents.py +0 -1
  42. camomilla/views/decorators.py +26 -0
  43. camomilla/views/medias.py +1 -2
  44. camomilla/views/menus.py +45 -1
  45. camomilla/views/pages.py +13 -1
  46. camomilla/views/tags.py +0 -1
  47. camomilla/views/users.py +0 -2
  48. {django_camomilla_cms-6.0.0b2.dist-info → django_camomilla_cms-6.0.0b4.dist-info}/METADATA +4 -3
  49. {django_camomilla_cms-6.0.0b2.dist-info → django_camomilla_cms-6.0.0b4.dist-info}/RECORD +53 -49
  50. tests/test_api.py +1 -0
  51. {django_camomilla_cms-6.0.0b2.dist-info → django_camomilla_cms-6.0.0b4.dist-info}/LICENSE +0 -0
  52. {django_camomilla_cms-6.0.0b2.dist-info → django_camomilla_cms-6.0.0b4.dist-info}/WHEEL +0 -0
  53. {django_camomilla_cms-6.0.0b2.dist-info → django_camomilla_cms-6.0.0b4.dist-info}/top_level.txt +0 -0
@@ -1,140 +1,47 @@
1
- from collections import defaultdict
2
- from jsonmodels.models import Base
3
- from jsonmodels.fields import _LazyType
4
- from jsonmodels.validators import ValidationError
5
- from camomilla.structured.fields import (
6
- ForeignKey,
7
- ForeignKeyList,
8
- EmbeddedField,
9
- ListField,
10
- )
11
- from camomilla.utils.getters import pointed_getter
12
-
13
- __all__ = ["Model"]
14
-
15
-
16
- class _Cache:
17
- instance = None
18
- parent = None
19
- prefetched_data = {}
20
-
21
- def __init__(self, instance):
22
- self.instance = instance
23
- self.prefetched_data = {}
24
-
25
- @property
26
- def is_root(self):
27
- return self.parent == None
28
-
29
- @property
30
- def root_node(self):
31
- root = self.instance
32
- while root._cache.parent is not None:
33
- root = root._cache.parent
34
- return root
35
-
36
- def get_prefetched_data(self):
37
- if self.is_root:
38
- return self.prefetched_data
39
- return self.root_node.get_prefetched_data()
40
-
41
-
42
- class Model(Base):
43
- def __init__(self, **kwargs):
44
- self._cache = _Cache(self)
45
- for _, field in self.iterate_over_fields():
46
- field.bind(self)
47
- super(Model, self).__init__(**kwargs)
48
-
49
- def populate(self, **kwargs):
50
- relations = self.prepopulate(**kwargs)
51
- self.prefetch_related(kwargs)
52
- super(Model, self).populate(**relations)
53
-
54
- def prepopulate(self, **kwargs):
55
- relations = {}
56
- for _, struct_name, field in self.iterate_with_name():
57
- if struct_name in kwargs and isinstance(
58
- field, (ForeignKey, ForeignKeyList, EmbeddedField, ListField)
59
- ):
60
- relations[struct_name] = kwargs.pop(struct_name)
61
- super(Model, self).populate(**kwargs)
62
- return relations
63
-
64
- def bind(self, parent):
65
- self._cache.parent = parent
66
-
67
- @classmethod
68
- def to_db_transform(cls, data):
69
- return data
70
-
1
+ from inspect import isclass
2
+ from typing import Any, Dict, Tuple, get_origin
3
+
4
+ import pydantic._internal._model_construction
5
+ from django.db.models import Model as DjangoModel
6
+ from pydantic import BaseModel as PyDBaseModel
7
+ from pydantic import Field, model_validator
8
+ from typing_extensions import Annotated
9
+
10
+ from .fields import ForeignKey, QuerySet
11
+ from .utils import get_type, map_method_aliases
12
+
13
+
14
+ class BaseModelMeta(pydantic._internal._model_construction.ModelMetaclass):
15
+ def __new__(
16
+ mcs, name: str, bases: Tuple[type], namespaces: Dict[str, Any], **kwargs
17
+ ):
18
+ annotations: dict = namespaces.get("__annotations__", {})
19
+ for base in bases:
20
+ for base_ in base.__mro__:
21
+ if base_ is PyDBaseModel:
22
+ break
23
+ annotations.update(base_.__annotations__)
24
+
25
+ for field in annotations:
26
+ annotation = annotations[field]
27
+ origin = get_origin(annotation)
28
+ if isclass(annotation) and issubclass(annotation, DjangoModel):
29
+ annotations[field] = ForeignKey[annotation]
30
+ elif isclass(origin) and issubclass(origin, QuerySet):
31
+ annotations[field] = Annotated[
32
+ annotation,
33
+ Field(default_factory=get_type(annotation)._default_manager.none),
34
+ ]
35
+ namespaces["__annotations__"] = annotations
36
+ return map_method_aliases(
37
+ super().__new__(mcs, name, bases, namespaces, **kwargs)
38
+ )
39
+
40
+
41
+ class BaseModel(PyDBaseModel, metaclass=BaseModelMeta):
42
+ @model_validator(mode="before")
71
43
  @classmethod
72
- def from_db_transform(cls, data):
73
- return data
74
-
75
- @classmethod
76
- def get_all_relateds(cls, struct):
77
- relateds = defaultdict(set)
78
- for _, struct_name, field in cls.iterate_with_name():
79
- if isinstance(field, ForeignKey):
80
- value = struct.get(struct_name, None)
81
- model = getattr(field, "model", None)
82
- if value:
83
- relateds[model].add(value)
84
- elif isinstance(field, ForeignKeyList):
85
- value = pointed_getter(struct, struct_name, [])
86
- if isinstance(value, list):
87
- model = getattr(field, "inner_model", None)
88
- relateds[model].update([i for i in value if i])
89
- elif isinstance(field, ListField):
90
- values = pointed_getter(struct, struct_name, [])
91
- if isinstance(values, list):
92
- for val in values:
93
- if isinstance(val, Model):
94
- main_type = val.__class__
95
- val = val.to_struct()
96
- else:
97
- main_type = field._get_main_type()
98
- if isinstance(main_type, _LazyType):
99
- main_type = main_type.evaluate(cls)
100
- if issubclass(main_type, Model):
101
- for model, pks in main_type.get_all_relateds(val).items():
102
- relateds[model].update(pks)
103
- elif isinstance(field, EmbeddedField):
104
- value = struct.get(struct_name, None)
105
- if isinstance(value, Model):
106
- value = value.to_struct()
107
- if not isinstance(value, dict):
108
- continue
109
- child_relateds = field._get_embed_type().get_all_relateds(value)
110
- for model, pks in child_relateds.items():
111
- relateds[model].update(pks)
112
- return relateds
113
-
114
- def prefetch_related(self, struct):
115
- if struct.keys() and self._cache.is_root:
116
- relateds = self.get_all_relateds(struct)
117
- for model, pks in relateds.items():
118
- self._cache.prefetched_data[model] = (
119
- {obj.pk: obj for obj in build_model_cache(model, pks)}
120
- if len(pks) > 0
121
- else {}
122
- )
123
-
124
- def get_prefetched_data(self):
125
- return self._cache.get_prefetched_data()
126
-
44
+ def build_cache(cls, data: Any) -> Any:
45
+ from camomilla.structured.cache import CacheBuilder
127
46
 
128
- def build_model_cache(model, values):
129
- models = []
130
- pks = []
131
- for value in values:
132
- if isinstance(value, model):
133
- models.append(value)
134
- else:
135
- pks.append(value)
136
- models_pks = [m.pk for m in models]
137
- pks = [pk for pk in pks if pk not in models_pks]
138
- if len(pks):
139
- models += list(model.objects.filter(pk__in=pks))
140
- return models
47
+ return CacheBuilder.from_model(cls).inject_cache(data)
@@ -0,0 +1,114 @@
1
+ from typing import Any, Generic, Sequence
2
+ from typing_extensions import TypeVar, get_args
3
+ from django.core.exceptions import ImproperlyConfigured
4
+ from camomilla.utils.getters import pointed_getter
5
+
6
+
7
+ T = TypeVar("T")
8
+
9
+
10
+ class _LazyType:
11
+ def __init__(self, path):
12
+ self.path = path
13
+
14
+ def evaluate(self, base_cls):
15
+ module, type_name = self._evaluate_path(self.path, base_cls)
16
+ return self._import(module, type_name)
17
+
18
+ def _evaluate_path(self, relative_path, base_cls):
19
+ base_module = base_cls.__module__
20
+
21
+ modules = self._get_modules(relative_path, base_module)
22
+
23
+ type_name = modules.pop()
24
+ module = ".".join(modules)
25
+ if not module:
26
+ module = base_module
27
+ return module, type_name
28
+
29
+ def _get_modules(self, relative_path, base_module):
30
+ canonical_path = relative_path.lstrip(".")
31
+ canonical_modules = canonical_path.split(".")
32
+
33
+ if not relative_path.startswith("."):
34
+ return canonical_modules
35
+
36
+ parents_amount = len(relative_path) - len(canonical_path)
37
+ parent_modules = base_module.split(".")
38
+ parents_amount = max(0, parents_amount - 1)
39
+ if parents_amount > len(parent_modules):
40
+ raise ValueError(f"Can't evaluate path '{relative_path}'")
41
+ return parent_modules[: parents_amount * -1] + canonical_modules
42
+
43
+ def _import(self, module_name, type_name):
44
+ module = __import__(module_name, fromlist=[type_name])
45
+ try:
46
+ return getattr(module, type_name)
47
+ except AttributeError:
48
+ raise ValueError(f"Can't find type '{module_name}.{type_name}'.")
49
+
50
+
51
+ def get_type(source: Generic[T], raise_exception=True) -> T:
52
+ try:
53
+ return get_args(source)[0]
54
+ except IndexError:
55
+ if raise_exception:
56
+ raise ImproperlyConfigured(
57
+ "Must provide a Model class for ForeignKey fields."
58
+ )
59
+ else:
60
+ return None
61
+
62
+
63
+ def get_type_eval(source: Generic[T], model: Any, raise_exception=True) -> T:
64
+ type = get_type(source, raise_exception)
65
+ if isinstance(type, str):
66
+ return _LazyType(type).evaluate(model)
67
+
68
+
69
+ def set_key(data, key, val):
70
+ if isinstance(data, Sequence):
71
+ key = int(key)
72
+ if key < len(data):
73
+ data[key] = val
74
+ else:
75
+ data.append(val)
76
+ return data
77
+ elif isinstance(data, dict):
78
+ data[key] = val
79
+ else:
80
+ setattr(data, key, val)
81
+ return data
82
+
83
+
84
+ def get_key(data, key, default):
85
+ if isinstance(data, Sequence):
86
+ try:
87
+ return data[int(key)]
88
+ except IndexError:
89
+ return default
90
+ return pointed_getter(data, key, default)
91
+
92
+
93
+ def pointed_setter(data, path, value):
94
+ path = path.split(".")
95
+ key = path.pop(0)
96
+ if not len(path):
97
+ return set_key(data, key, value)
98
+ default = [] if path[0].isdigit() else {}
99
+ return set_key(
100
+ data, key, pointed_setter(get_key(data, key, default), ".".join(path), value)
101
+ )
102
+
103
+
104
+ def map_method_aliases(new_cls):
105
+ method_aliases = {
106
+ "validate_python": "model_validate",
107
+ "validate_json": "model_validate_json",
108
+ # "dump_python": "model_dump",
109
+ # "dump_json": "model_dump_json",
110
+ "json_schema": "model_json_schema"
111
+ }
112
+ for alias_name, target_name in method_aliases.items():
113
+ setattr(new_cls, alias_name, getattr(new_cls, target_name))
114
+ return new_cls
@@ -1,5 +1,4 @@
1
1
  from django import template
2
- from django.utils.translation import get_language
3
2
 
4
3
 
5
4
  register = template.Library()
@@ -1 +1 @@
1
- __version__ = "6.0.0-beta.2"
1
+ __version__ = "6.0.0-beta.4"
@@ -0,0 +1,96 @@
1
+ from ckeditor_uploader.widgets import CKEditorUploadingWidget
2
+ from django import forms
3
+ from django.contrib import admin
4
+ from django.http import HttpResponse
5
+
6
+ from camomilla import settings
7
+
8
+ if settings.ENABLE_TRANSLATIONS:
9
+ from modeltranslation.admin import (
10
+ TabbedTranslationAdmin as TranslationAwareModelAdmin,
11
+ )
12
+ else:
13
+ from django.contrib.admin import ModelAdmin as TranslationAwareModelAdmin
14
+
15
+ from camomilla.models import Article, Content, Media, MediaFolder, Page, Tag, Menu
16
+
17
+
18
+ class AbstractPageAdmin(TranslationAwareModelAdmin):
19
+ change_form_template = "admin/camomilla/page/change_form.html"
20
+
21
+
22
+ class UserProfileAdmin(admin.ModelAdmin):
23
+ pass
24
+
25
+
26
+ class ArticleAdminForm(forms.ModelForm):
27
+ class Meta:
28
+ model = Article
29
+ exclude = ("slug",)
30
+ widgets = {"content": CKEditorUploadingWidget}
31
+
32
+
33
+ class ArticleAdmin(AbstractPageAdmin):
34
+ filter_horizontal = ("tags",)
35
+ form = ArticleAdminForm
36
+
37
+
38
+ class TagAdmin(TranslationAwareModelAdmin):
39
+ pass
40
+
41
+
42
+ class MediaFolderAdmin(admin.ModelAdmin):
43
+ readonly_fields = ("path",)
44
+
45
+
46
+ class ContentAdminForm(forms.ModelForm):
47
+ class Meta:
48
+ model = Content
49
+ fields = "__all__"
50
+ widgets = {"content": CKEditorUploadingWidget}
51
+
52
+
53
+ class ContentAdmin(TranslationAwareModelAdmin):
54
+ form = ContentAdminForm
55
+
56
+
57
+ class MediaAdmin(TranslationAwareModelAdmin):
58
+ exclude = (
59
+ "thumbnail",
60
+ "size",
61
+ "image_props",
62
+ )
63
+ readonly_fields = ("image_preview", "image_thumb_preview", "mime_type")
64
+ list_display = (
65
+ "__str__",
66
+ "title",
67
+ "image_thumb_preview",
68
+ )
69
+
70
+ def response_add(self, request, obj):
71
+ if request.GET.get("_popup", ""):
72
+ return HttpResponse(
73
+ """
74
+ <script type="text/javascript">
75
+ opener.dismissAddRelatedObjectPopup(window, %s, '%s');
76
+ </script>"""
77
+ % (obj.id, obj.json_repr)
78
+ )
79
+ else:
80
+ return super(MediaAdmin, self).response_add(request, obj)
81
+
82
+
83
+ class PageAdmin(AbstractPageAdmin):
84
+ readonly_fields = ("permalink",)
85
+
86
+
87
+ class MenuAdmin(TranslationAwareModelAdmin):
88
+ pass
89
+
90
+ admin.site.register(Article, ArticleAdmin)
91
+ admin.site.register(MediaFolder, MediaFolderAdmin)
92
+ admin.site.register(Tag, TagAdmin)
93
+ admin.site.register(Content, ContentAdmin)
94
+ admin.site.register(Media, MediaAdmin)
95
+ admin.site.register(Page, PageAdmin)
96
+ admin.site.register(Menu, MenuAdmin)
camomilla/theme/apps.py CHANGED
@@ -11,6 +11,9 @@ class CamomillaThemeConfig(AppConfig):
11
11
  def ready(self):
12
12
  installed_apps = getattr(settings, "INSTALLED_APPS", [])
13
13
  changed = False
14
+ if "django_jsonform" not in installed_apps:
15
+ changed = True
16
+ installed_apps = ["django_jsonform", *installed_apps]
14
17
  if "admin_interface" not in installed_apps:
15
18
  changed = True
16
19
  installed_apps = ["admin_interface", *installed_apps]
@@ -23,4 +26,12 @@ class CamomillaThemeConfig(AppConfig):
23
26
  apps.apps_ready = apps.models_ready = apps.loading = apps.ready = False
24
27
  apps.clear_cache()
25
28
  apps.populate(settings.INSTALLED_APPS)
26
- setattr(settings, "X_FRAME_OPTIONS", getattr(settings, "X_FRAME_OPTIONS", ))
29
+ setattr(
30
+ settings,
31
+ "X_FRAME_OPTIONS",
32
+ getattr(
33
+ settings,
34
+ "X_FRAME_OPTIONS",
35
+ "SAMEORIGIN"
36
+ ),
37
+ )
camomilla/translation.py CHANGED
@@ -14,9 +14,10 @@ class SeoMixinTranslationOptions(TranslationOptions):
14
14
  "canonical",
15
15
  )
16
16
 
17
+
17
18
  class AbstractPageTranslationOptions(SeoMixinTranslationOptions):
18
19
  fields = ("breadcrumbs_title", "slug", "status", "indexable", "template_data")
19
-
20
+
20
21
 
21
22
  @register(Article)
22
23
  class ArticleTranslationOptions(AbstractPageTranslationOptions):
@@ -48,6 +49,7 @@ class PageTranslationOptions(AbstractPageTranslationOptions):
48
49
  class UrlNodeTranslationOptions(TranslationOptions):
49
50
  fields = ("permalink",)
50
51
 
52
+
51
53
  @register(Menu)
52
54
  class MenuTranslationOptions(TranslationOptions):
53
- fields = ("nodes",)
55
+ fields = ("nodes",)
camomilla/urls.py CHANGED
@@ -20,6 +20,7 @@ from camomilla.views import (
20
20
  UserViewSet,
21
21
  MenuViewSet,
22
22
  )
23
+ from camomilla.views.pages import fetch_page
23
24
 
24
25
  router = routers.DefaultRouter()
25
26
 
@@ -36,6 +37,8 @@ router.register(r"menus", MenuViewSet, "camomilla-menus")
36
37
 
37
38
  urlpatterns = [
38
39
  path("", include(router.urls)),
40
+ path("pages-router/", fetch_page),
41
+ path("pages-router/<path:permalink>", fetch_page),
39
42
  path(
40
43
  "profiles/me/", lambda _: redirect("../../users/current/"), name="profiles-me"
41
44
  ),
@@ -43,12 +46,16 @@ urlpatterns = [
43
46
  path("auth/login/", CamomillaAuthLogin.as_view(), name="login"),
44
47
  path("auth/logout/", CamomillaAuthLogout.as_view(), name="logout"),
45
48
  path("languages/", LanguageViewSet.as_view(), name="get_languages"),
46
- path('openapi', get_schema_view(
47
- title="Camomilla",
48
- description="API for all things …",
49
- version="1.0.0",
50
- generator_class=SchemaGenerator
51
- ), name='openapi-schema'),
49
+ path(
50
+ "openapi",
51
+ get_schema_view(
52
+ title="Camomilla",
53
+ description="API for all things …",
54
+ version="1.0.0",
55
+ generator_class=SchemaGenerator,
56
+ ),
57
+ name="openapi-schema",
58
+ ),
52
59
  ]
53
60
 
54
61
  if find_spec("djsuperadmin.urls") is not None:
@@ -1,4 +1,4 @@
1
1
  from .normalization import *
2
2
  from .seo import *
3
3
  from .translation import *
4
- from .templates import *
4
+ from .templates import *
@@ -1,4 +1,4 @@
1
- from typing import Any, Union
1
+ from typing import Any, Callable, Union
2
2
 
3
3
 
4
4
  def safe_getter(instance: Union[dict, object], key: str, default: Any = None) -> Any:
@@ -15,3 +15,13 @@ def pointed_getter(
15
15
  if len(attrs) == 2:
16
16
  data = pointed_getter(data, attrs[1], default)
17
17
  return data
18
+
19
+
20
+ def find_and_replace_dict(obj: dict, predicate: Callable):
21
+ result = {}
22
+ for k, v in obj.items():
23
+ v = predicate(key=k, value=v)
24
+ if isinstance(v, dict):
25
+ v = find_and_replace_dict(v, predicate)
26
+ result[k] = v
27
+ return result
@@ -1,11 +1,11 @@
1
1
  from pathlib import Path
2
- from typing import Iterable
2
+ from typing import Sequence
3
3
 
4
4
  from django import template as django_template
5
5
  from os.path import relpath
6
6
 
7
7
 
8
- def get_all_templates_files() -> Iterable[str]:
8
+ def get_all_templates_files() -> Sequence[str]:
9
9
  dirs = []
10
10
  for engine in django_template.loader.engines.all():
11
11
  # Exclude pip installed site package template dirs
@@ -1,5 +1,5 @@
1
1
  import re
2
- from typing import Any, Iterable, Iterator
2
+ from typing import Any, Sequence, Iterator
3
3
 
4
4
  from django.db.models import Model, Q
5
5
  from django.utils.translation.trans_real import activate, get_language
@@ -8,7 +8,7 @@ from modeltranslation.utils import build_localized_fieldname
8
8
  from camomilla.settings import BASE_URL
9
9
 
10
10
 
11
- def activate_languages(languages: Iterable[str] = AVAILABLE_LANGUAGES) -> Iterator[str]:
11
+ def activate_languages(languages: Sequence[str] = AVAILABLE_LANGUAGES) -> Iterator[str]:
12
12
  old = get_language()
13
13
  for language in languages:
14
14
  activate(language)
@@ -36,7 +36,7 @@ def url_lang_decompose(url):
36
36
  if BASE_URL and url.startswith(BASE_URL):
37
37
  url = url[len(BASE_URL):]
38
38
  data = {"url": url, "permalink": url, "language": DEFAULT_LANGUAGE}
39
- result = re.match(f"^\/?({'|'.join(AVAILABLE_LANGUAGES)})?\/(.*)", url)
39
+ result = re.match(f"^\/?({'|'.join(AVAILABLE_LANGUAGES)})?\/(.*)", url) # noqa: W605
40
40
  groups = result and result.groups()
41
41
  if groups and len(groups) == 2:
42
42
  data["language"] = groups[0]
@@ -57,11 +57,14 @@ def lang_fallback_query(**kwargs):
57
57
  for lang in AVAILABLE_LANGUAGES:
58
58
  query |= Q(**{f"{key}_{lang}": value for key, value in kwargs.items()})
59
59
  if current_lang:
60
- query = (query & Q(**{f"{key}_{current_lang}__isnull": True for key in kwargs.keys()}))
60
+ query = query & Q(
61
+ **{f"{key}_{current_lang}__isnull": True for key in kwargs.keys()}
62
+ )
61
63
  query |= Q(**{f"{key}_{current_lang}": value for key, value in kwargs.items()})
62
64
  return query
63
65
 
64
66
 
65
- def is_translatable(model:Model) -> bool:
67
+ def is_translatable(model: Model) -> bool:
66
68
  from modeltranslation.translator import translator
67
- return model in translator.get_registered_models()
69
+
70
+ return model in translator.get_registered_models()
@@ -5,4 +5,4 @@ from .medias import *
5
5
  from .pages import *
6
6
  from .tags import *
7
7
  from .users import *
8
- from .menus import *
8
+ from .menus import *
@@ -6,7 +6,6 @@ from camomilla.views.mixins import BulkDeleteMixin, GetUserLanguageMixin
6
6
 
7
7
 
8
8
  class ArticleViewSet(GetUserLanguageMixin, BulkDeleteMixin, BaseModelViewset):
9
-
10
9
  queryset = Article.objects.all()
11
10
  serializer_class = ArticleSerializer
12
11
  permission_classes = (CamomillaBasePermissions,)
@@ -11,7 +11,6 @@ from camomilla.views.mixins import BulkDeleteMixin, GetUserLanguageMixin
11
11
 
12
12
 
13
13
  class ContentViewSet(GetUserLanguageMixin, BulkDeleteMixin, BaseModelViewset):
14
-
15
14
  queryset = Content.objects.all()
16
15
  serializer_class = ContentSerializer
17
16
  model = Content
@@ -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 [l[0] for l 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
@@ -45,7 +45,7 @@ class MediaFolderViewSet(
45
45
 
46
46
  def get_mixed_response(self, request, *args, **kwargs):
47
47
  search = self.request.GET.get("search", None)
48
- all = self.request.GET.get("all", "false").lower() == 'true'
48
+ all = self.request.GET.get("all", "false").lower() == "true"
49
49
  updir = None if all else kwargs.get("pk", None)
50
50
  if not search and all:
51
51
  self.search_fields = []
@@ -83,7 +83,6 @@ class MediaViewSet(
83
83
  TrigramSearchMixin,
84
84
  BaseModelViewset,
85
85
  ):
86
-
87
86
  queryset = Media.objects.all()
88
87
  serializer_class = MediaSerializer
89
88
  permission_classes = (CamomillaBasePermissions,)