django-camomilla-cms 6.0.0b2__tar.gz → 6.0.0b4__tar.gz

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 (121) hide show
  1. {django-camomilla-cms-6.0.0b2/django_camomilla_cms.egg-info → django-camomilla-cms-6.0.0b4}/PKG-INFO +1 -1
  2. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/__init__.py +1 -1
  3. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/apps.py +3 -0
  4. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/contrib/modeltranslation/hvad_migration.py +1 -2
  5. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/contrib/rest_framework/serializer.py +31 -1
  6. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/dynamic_pages_urls.py +7 -1
  7. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/fields/json.py +12 -9
  8. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/management/commands/regenerate_thumbnails.py +0 -1
  9. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/model_api.py +6 -4
  10. django-camomilla-cms-6.0.0b4/camomilla/models/__init__.py +5 -0
  11. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/models/article.py +0 -1
  12. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/models/media.py +1 -2
  13. django-camomilla-cms-6.0.0b4/camomilla/models/menu.py +89 -0
  14. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/models/mixins/__init__.py +0 -1
  15. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/models/page.py +66 -32
  16. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/openapi/schema.py +27 -0
  17. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/parsers.py +0 -1
  18. django-camomilla-cms-6.0.0b4/camomilla/serializers/fields/json.py +24 -0
  19. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/serializers/mixins/__init__.py +16 -2
  20. django-camomilla-cms-6.0.0b4/camomilla/serializers/page.py +63 -0
  21. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/serializers/user.py +2 -3
  22. django-camomilla-cms-6.0.0b4/camomilla/serializers/utils.py +35 -0
  23. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/settings.py +21 -1
  24. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/storages/optimize.py +1 -1
  25. django-camomilla-cms-6.0.0b4/camomilla/structured/__init__.py +120 -0
  26. django-camomilla-cms-6.0.0b4/camomilla/structured/cache.py +193 -0
  27. django-camomilla-cms-6.0.0b4/camomilla/structured/fields.py +149 -0
  28. django-camomilla-cms-6.0.0b4/camomilla/structured/models.py +47 -0
  29. django-camomilla-cms-6.0.0b4/camomilla/structured/utils.py +114 -0
  30. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/templatetags/camomilla_filters.py +0 -1
  31. django-camomilla-cms-6.0.0b4/camomilla/theme/__init__.py +1 -0
  32. {django-camomilla-cms-6.0.0b2/camomilla → django-camomilla-cms-6.0.0b4/camomilla/theme}/admin.py +12 -6
  33. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/theme/apps.py +12 -1
  34. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/translation.py +4 -2
  35. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/urls.py +13 -6
  36. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/utils/__init__.py +1 -1
  37. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/utils/getters.py +11 -1
  38. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/utils/templates.py +2 -2
  39. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/utils/translation.py +9 -6
  40. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/views/__init__.py +1 -1
  41. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/views/articles.py +0 -1
  42. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/views/contents.py +0 -1
  43. django-camomilla-cms-6.0.0b4/camomilla/views/decorators.py +26 -0
  44. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/views/medias.py +1 -2
  45. django-camomilla-cms-6.0.0b4/camomilla/views/menus.py +86 -0
  46. django-camomilla-cms-6.0.0b4/camomilla/views/pages.py +25 -0
  47. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/views/tags.py +0 -1
  48. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/views/users.py +0 -2
  49. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4/django_camomilla_cms.egg-info}/PKG-INFO +1 -1
  50. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/django_camomilla_cms.egg-info/SOURCES.txt +4 -1
  51. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/django_camomilla_cms.egg-info/requires.txt +3 -2
  52. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/setup.cfg +3 -2
  53. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/setup.py +1 -1
  54. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/tests/test_api.py +1 -0
  55. django-camomilla-cms-6.0.0b2/camomilla/models/__init__.py +0 -5
  56. django-camomilla-cms-6.0.0b2/camomilla/models/menu.py +0 -87
  57. django-camomilla-cms-6.0.0b2/camomilla/serializers/fields/json.py +0 -92
  58. django-camomilla-cms-6.0.0b2/camomilla/serializers/page.py +0 -16
  59. django-camomilla-cms-6.0.0b2/camomilla/serializers/utils.py +0 -30
  60. django-camomilla-cms-6.0.0b2/camomilla/structured/__init__.py +0 -105
  61. django-camomilla-cms-6.0.0b2/camomilla/structured/fields.py +0 -292
  62. django-camomilla-cms-6.0.0b2/camomilla/structured/models.py +0 -140
  63. django-camomilla-cms-6.0.0b2/camomilla/theme/__init__.py +0 -1
  64. django-camomilla-cms-6.0.0b2/camomilla/views/menus.py +0 -42
  65. django-camomilla-cms-6.0.0b2/camomilla/views/pages.py +0 -13
  66. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/LICENSE +0 -0
  67. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/MANIFEST.in +0 -0
  68. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/README.md +0 -0
  69. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/authentication.py +0 -0
  70. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/context_processors.py +0 -0
  71. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/contrib/__init__.py +0 -0
  72. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/contrib/modeltranslation/__init__.py +0 -0
  73. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/contrib/rest_framework/__init__.py +0 -0
  74. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/defaults.py +0 -0
  75. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/exceptions.py +0 -0
  76. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/fields/__init__.py +0 -0
  77. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/management/__init__.py +0 -0
  78. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/management/commands/__init__.py +0 -0
  79. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/models/content.py +0 -0
  80. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/openapi/__init__.py +0 -0
  81. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/permissions.py +0 -0
  82. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/serializers/__init__.py +0 -0
  83. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/serializers/article.py +0 -0
  84. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/serializers/base/__init__.py +0 -0
  85. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/serializers/content_type.py +0 -0
  86. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/serializers/fields/__init__.py +0 -0
  87. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/serializers/fields/file.py +0 -0
  88. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/serializers/fields/related.py +0 -0
  89. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/serializers/media.py +0 -0
  90. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/serializers/menu.py +0 -0
  91. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/serializers/validators.py +0 -0
  92. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/storages/__init__.py +0 -0
  93. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/storages/overwrite.py +0 -0
  94. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/templates/admin/camomilla/page/change_form.html +0 -0
  95. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/templates/defaults/articles/default.html +0 -0
  96. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/templates/defaults/base.html +0 -0
  97. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/templates/defaults/pages/default.html +0 -0
  98. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/templates/defaults/parts/langswitch.html +0 -0
  99. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/templates/defaults/parts/menu.html +0 -0
  100. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/templates/defaults/widgets/media_select_multiple.html +0 -0
  101. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/templatetags/__init__.py +0 -0
  102. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/templatetags/menus.py +0 -0
  103. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/theme/static/admin/css/responsive.css +0 -0
  104. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/theme/static/admin/img/favicon.ico +0 -0
  105. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/theme/static/admin/img/logo.svg +0 -0
  106. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/theme/templates/admin/base.html +0 -0
  107. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/theme/templates/rosetta/base.html +0 -0
  108. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/utils/normalization.py +0 -0
  109. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/utils/seo.py +0 -0
  110. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/views/base/__init__.py +0 -0
  111. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/views/languages.py +0 -0
  112. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/views/mixins/__init__.py +0 -0
  113. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/views/mixins/ordering.py +0 -0
  114. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/camomilla/views/mixins/pagination.py +0 -0
  115. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/django_camomilla_cms.egg-info/dependency_links.txt +0 -0
  116. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/django_camomilla_cms.egg-info/top_level.txt +0 -0
  117. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/tests/__init__.py +0 -0
  118. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/tests/test_camomilla_filters.py +0 -0
  119. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/tests/test_models.py +0 -0
  120. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/tests/test_utils.py +0 -0
  121. {django-camomilla-cms-6.0.0b2 → django-camomilla-cms-6.0.0b4}/tests/urls.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: django-camomilla-cms
3
- Version: 6.0.0b2
3
+ Version: 6.0.0b4
4
4
  Summary: Django powered cms
5
5
  Home-page: https://github.com/lotrekagency/camomilla
6
6
  Author: Lotrèk
@@ -1,4 +1,4 @@
1
- __version__ = "6.0.0-beta.2"
1
+ __version__ = "6.0.0-beta.4"
2
2
 
3
3
 
4
4
  def get_core_apps():
@@ -3,6 +3,8 @@ from __future__ import unicode_literals
3
3
  from django.apps import AppConfig
4
4
  from django.conf import settings
5
5
 
6
+ from camomilla.context.autodiscover import autodiscover_context_files
7
+
6
8
 
7
9
  class CamomillaConfig(AppConfig):
8
10
  default_auto_field = "django.db.models.AutoField"
@@ -13,3 +15,4 @@ class CamomillaConfig(AppConfig):
13
15
  if "camomilla" not in migration_modules:
14
16
  migration_modules["camomilla"] = "camomilla_migrations"
15
17
  setattr(settings, "MIGRATION_MODULES", migration_modules)
18
+ autodiscover_context_files()
@@ -3,7 +3,6 @@ from django.db import migrations, connection
3
3
 
4
4
 
5
5
  class KeepTranslationsMixin:
6
-
7
6
  _saved_data_from_plain = {}
8
7
  language_codes = dict(getattr(settings, "LANGUAGES", {})).keys()
9
8
 
@@ -30,7 +29,7 @@ class KeepTranslationsMixin:
30
29
  for modelPath, fields in self.keep_translations.items():
31
30
  Model = apps.get_model(*modelPath.split("."))
32
31
  table = Model._meta.db_table + "_translation"
33
- if not "language_code" in fields:
32
+ if "language_code" not in fields:
34
33
  fields = ("language_code",) + fields
35
34
  with connection.cursor() as cursor:
36
35
  cursor.execute("SELECT master_id FROM {0};".format(table))
@@ -52,7 +52,7 @@ class TranslationsMixin(serializers.ModelSerializer):
52
52
  @property
53
53
  def _writable_fields(self):
54
54
  for field in super()._writable_fields:
55
- if not field.field_name in self.translation_fields:
55
+ if field.field_name not in self.translation_fields:
56
56
  yield field
57
57
 
58
58
  def to_internal_value(self, data):
@@ -77,3 +77,33 @@ class TranslationsMixin(serializers.ModelSerializer):
77
77
  @property
78
78
  def is_translatable(self):
79
79
  return is_translatable(pointed_getter(self, "Meta.model"))
80
+
81
+
82
+ class RemoveTranslationsMixin(serializers.ModelSerializer):
83
+ @cached_property
84
+ def translation_fields(self):
85
+ try:
86
+ return translator.get_options_for_model(self.Meta.model).get_field_names()
87
+ except NotRegistered:
88
+ return []
89
+
90
+ def get_default_field_names(self, declared_fields, model_info):
91
+ request = self.context.get("request", False)
92
+ included_translations = request and request.GET.get(
93
+ "included_translations", False
94
+ )
95
+ if included_translations == "all":
96
+ return super().get_default_field_names(declared_fields, model_info)
97
+ elif included_translations is not False:
98
+ included_translations = included_translations.split(",")
99
+ else:
100
+ included_translations = []
101
+
102
+ field_names = super().get_default_field_names(declared_fields, model_info)
103
+ for lang in mt_settings.AVAILABLE_LANGUAGES:
104
+ if lang not in included_translations:
105
+ for field in self.translation_fields:
106
+ localized_fieldname = build_localized_fieldname(field, lang)
107
+ if localized_fieldname in field_names:
108
+ field_names.remove(localized_fieldname)
109
+ return field_names
@@ -1,13 +1,19 @@
1
1
  from django.shortcuts import render
2
2
  from django.urls import path
3
3
 
4
+ from camomilla import settings
5
+
4
6
  from .models import Page
5
7
 
6
8
 
7
9
  def fetch(request, *args, **kwargs):
8
10
  preview = request.user.is_staff and request.GET.get("preview", False)
9
11
  if "permalink" in kwargs:
10
- page = Page.get_or_404(request, bypass_public_check=preview, bypass_type_check=True)
12
+ page = Page.get_or_404(
13
+ request, bypass_public_check=preview, bypass_type_check=True
14
+ )
15
+ elif settings.AUTO_CREATE_HOMEPAGE is False:
16
+ page, _ = Page.get_or_404(permalink="/", bypass_type_check=True)
11
17
  else:
12
18
  page, _ = Page.get_or_create_homepage()
13
19
  return render(request, page.get_template_path(request), page.get_context(request))
@@ -12,15 +12,7 @@ from django.contrib.postgres.fields import ArrayField as DjangoArrayField
12
12
  from django.db import models
13
13
 
14
14
 
15
- class JSONField(DjangoJSONField):
16
- pass
17
-
18
-
19
- class ArrayField(DjangoArrayField):
20
- pass
21
-
22
-
23
- if "sqlite" in settings.DATABASES["default"]["ENGINE"]:
15
+ if "sqlite" in settings.DATABASES["default"]["ENGINE"]: # noqa: C901
24
16
 
25
17
  class JSONField(models.Field):
26
18
  def db_type(self, connection):
@@ -64,3 +56,14 @@ if "sqlite" in settings.DATABASES["default"]["ENGINE"]:
64
56
  }
65
57
  )
66
58
  return name, path, args, kwargs
59
+
60
+ else:
61
+
62
+ class JSONField(DjangoJSONField):
63
+ pass
64
+
65
+ class ArrayField(DjangoArrayField):
66
+ pass
67
+
68
+
69
+ __all__ = [JSONField, ArrayField]
@@ -3,7 +3,6 @@ from camomilla.models import Media
3
3
 
4
4
 
5
5
  class Command(BaseCommand):
6
-
7
6
  help = "Regenerates all the thumbnail"
8
7
 
9
8
  def handle(self, *args, **options):
@@ -13,7 +13,7 @@ def register(
13
13
  base_viewset=BaseModelViewset,
14
14
  serializer_meta={},
15
15
  viewset_attrs={},
16
- filters = None
16
+ filters=None,
17
17
  ):
18
18
  """
19
19
  Register a model to the API.
@@ -30,8 +30,8 @@ def register(
30
30
  "model": model,
31
31
  "fields": "__all__",
32
32
  }
33
- if 'exclude' in serializer_meta:
34
- base_meta.pop('fields')
33
+ if "exclude" in serializer_meta:
34
+ base_meta.pop("fields")
35
35
  serializer = type(
36
36
  f"{model.__name__}Serializer",
37
37
  (base_serializer,),
@@ -51,7 +51,9 @@ def register(
51
51
  f"{model.__name__}ViewSet",
52
52
  (base_viewset,),
53
53
  {
54
- "get_queryset": lambda self: model.objects.all() if filters is None else model.objects.filter(**filters),
54
+ "get_queryset": lambda self: model.objects.all()
55
+ if filters is None
56
+ else model.objects.filter(**filters),
55
57
  "serializer_class": serializer,
56
58
  **viewset_attrs,
57
59
  },
@@ -0,0 +1,5 @@
1
+ from .article import * # NOQA
2
+ from .content import * # NOQA
3
+ from .media import * # NOQA
4
+ from .page import * # NOQA
5
+ from .menu import * # NOQA
@@ -1,6 +1,5 @@
1
1
  from django.conf import settings as dj_settings
2
2
  from django.db import models
3
- from django.utils.translation import gettext_lazy as _
4
3
 
5
4
  from camomilla.models.page import AbstractPage
6
5
  from camomilla import settings
@@ -5,7 +5,6 @@ from io import BytesIO
5
5
  import magic
6
6
  from django.core.exceptions import ValidationError
7
7
  from django.core.files.base import ContentFile
8
- from django.core.files.storage import default_storage as storage
9
8
  from django.db import models
10
9
  from django.db.models.fields.related import ForeignObjectRel
11
10
  from django.db.models.signals import post_save, pre_delete
@@ -181,7 +180,7 @@ class Media(models.Model):
181
180
  def _get_file_size(self):
182
181
  try:
183
182
  return self.file.storage.size(self.file.name)
184
- except:
183
+ except Exception:
185
184
  return 0
186
185
 
187
186
  def __str__(self):
@@ -0,0 +1,89 @@
1
+ from enum import Enum
2
+ from uuid import uuid4
3
+ from django.contrib.contenttypes.models import ContentType
4
+ from django.db import models
5
+ from django.utils.translation import gettext_lazy as _
6
+ from django.template.loader import render_to_string
7
+ from django.template import RequestContext
8
+ from django.utils.safestring import mark_safe
9
+ from pydantic import (
10
+ Field,
11
+ SerializationInfo,
12
+ computed_field,
13
+ model_serializer,
14
+ )
15
+ from camomilla import structured
16
+ from camomilla.models.page import UrlNode
17
+ from typing import Optional, Union, Callable
18
+
19
+
20
+ class LinkTypes(str, Enum):
21
+ relational = "RE"
22
+ static = "ST"
23
+
24
+
25
+ class MenuNodeLink(structured.BaseModel):
26
+ link_type: LinkTypes = LinkTypes.static
27
+ static: str = None
28
+ content_type: int = None
29
+ page_id: int = None
30
+ url_node: UrlNode = None
31
+
32
+ @model_serializer(mode="wrap", when_used="json")
33
+ def update_relational(self, handler: Callable, info: SerializationInfo):
34
+ if self.link_type == LinkTypes.relational:
35
+ if self.content_type and self.page_id:
36
+ c_type = ContentType.objects.filter(pk=self.content_type).first()
37
+ model = c_type and c_type.model_class()
38
+ page = model and model.objects.filter(pk=self.page_id).first()
39
+ self.url_node = page and page.url_node
40
+ return handler(self)
41
+
42
+ def get_url(self, request=None):
43
+ if self.link_type == LinkTypes.relational:
44
+ return isinstance(self.url_node, UrlNode) and self.url_node.routerlink
45
+ elif self.link_type == LinkTypes.static:
46
+ return self.static
47
+
48
+ @computed_field
49
+ @property
50
+ def url(self) -> Optional[str]:
51
+ return self.get_url()
52
+
53
+
54
+ class MenuNode(structured.BaseModel):
55
+ id: str = Field(default_factory=uuid4)
56
+ meta: dict = {}
57
+ nodes: list["MenuNode"] = []
58
+ title: str = ""
59
+ link: MenuNodeLink
60
+
61
+
62
+ class Menu(models.Model):
63
+ key = models.CharField(max_length=200, unique=True, editable=False)
64
+ available_classes = models.JSONField(default=dict, editable=False)
65
+ enabled = models.BooleanField(default=True)
66
+ nodes = structured.StructuredJSONField(default=list, schema=MenuNode)
67
+
68
+ class Meta:
69
+ verbose_name = _("menu")
70
+ verbose_name_plural = _("menus")
71
+
72
+ def render(
73
+ self,
74
+ template_path: str,
75
+ request=None,
76
+ context: Union[dict, RequestContext] = {},
77
+ ):
78
+ if isinstance(context, RequestContext):
79
+ context = context.flatten()
80
+ context.update({"menu": self})
81
+ return mark_safe(render_to_string(template_path, context, request))
82
+
83
+ class defaultdict(dict):
84
+ def __missing__(self, key):
85
+ dict.__setitem__(self, key, Menu.objects.get_or_create(key=key)[0])
86
+ return self[key]
87
+
88
+ def __str__(self) -> str:
89
+ return self.key
@@ -2,7 +2,6 @@ from django.db import models
2
2
 
3
3
 
4
4
  class SeoMixin(models.Model):
5
-
6
5
  title = models.CharField(max_length=200, null=True, blank=True)
7
6
  description = models.TextField(null=True, blank=True)
8
7
  og_description = models.TextField(blank=True, null=True)
@@ -1,4 +1,4 @@
1
- from typing import Iterable, Tuple
1
+ from typing import Sequence, Tuple
2
2
  from uuid import uuid4
3
3
 
4
4
  from django.core.exceptions import ObjectDoesNotExist
@@ -20,13 +20,15 @@ from camomilla.utils import (
20
20
  lang_fallback_query,
21
21
  set_nofallbacks,
22
22
  url_lang_decompose,
23
- get_all_templates_files
23
+ get_all_templates_files,
24
24
  )
25
25
  from camomilla.utils.getters import pointed_getter
26
26
  from camomilla import settings
27
+ from camomilla.context.rendering import ctx_registry
28
+
27
29
 
28
30
  def GET_TEMPLATE_CHOICES():
29
- return [(t,t)for t in get_all_templates_files()]
31
+ return [(t, t) for t in get_all_templates_files()]
30
32
 
31
33
 
32
34
  class UrlNodeManager(models.Manager):
@@ -42,7 +44,7 @@ class UrlNodeManager(models.Manager):
42
44
  def _annotate_fields(
43
45
  self,
44
46
  qs: models.QuerySet,
45
- field_names: Iterable[Tuple[str, models.Field, models.Value]],
47
+ field_names: Sequence[Tuple[str, models.Field, models.Value]],
46
48
  ):
47
49
  for field_name, output_field, default in field_names:
48
50
  whens = [
@@ -66,9 +68,21 @@ class UrlNodeManager(models.Manager):
66
68
  return self._annotate_fields(
67
69
  super().get_queryset(),
68
70
  [
69
- ("indexable", models.BooleanField(), models.Value(None, models.BooleanField())),
70
- ("status", models.BooleanField(), models.Value(None, models.BooleanField())),
71
- ("pubblication_date", models.DateTimeField(), models.Value(timezone.now(), models.DateTimeField())),
71
+ (
72
+ "indexable",
73
+ models.BooleanField(),
74
+ models.Value(None, models.BooleanField()),
75
+ ),
76
+ (
77
+ "status",
78
+ models.BooleanField(),
79
+ models.Value(None, models.BooleanField()),
80
+ ),
81
+ (
82
+ "pubblication_date",
83
+ models.DateTimeField(),
84
+ models.Value(timezone.now(), models.DateTimeField()),
85
+ ),
72
86
  ],
73
87
  )
74
88
  except (ProgrammingError, OperationalError):
@@ -83,15 +97,19 @@ class UrlNode(models.Model):
83
97
  @property
84
98
  def page(self) -> "AbstractPage":
85
99
  return getattr(self, self.related_name)
86
-
87
- @property
88
- def routerlink(self) -> str:
100
+
101
+ @staticmethod
102
+ def reverse_url(permalink: str) -> str:
89
103
  try:
90
- if self.permalink == '/':
91
- return reverse('camomilla-homepage')
92
- return reverse('camomilla-permalink', args=(self.permalink.lstrip("/"),))
104
+ if permalink == "/":
105
+ return reverse("camomilla-homepage")
106
+ return reverse("camomilla-permalink", args=(permalink.lstrip("/"),))
93
107
  except NoReverseMatch:
94
- return self.permalink
108
+ return None
109
+
110
+ @property
111
+ def routerlink(self) -> str:
112
+ return self.reverse_url(self.permalink) or self.permalink
95
113
 
96
114
 
97
115
  PAGE_CHILD_RELATED_NAME = "%(app_label)s_%(class)s_child_pages"
@@ -123,7 +141,11 @@ class AbstractPage(SeoMixin, MetaMixin, models.Model, metaclass=PageBase):
123
141
  date_created = models.DateTimeField(auto_now_add=True)
124
142
  date_updated_at = models.DateTimeField(auto_now=True)
125
143
  url_node = models.OneToOneField(
126
- UrlNode, on_delete=models.CASCADE, related_name=URL_NODE_RELATED_NAME, null=True, editable=False
144
+ UrlNode,
145
+ on_delete=models.CASCADE,
146
+ related_name=URL_NODE_RELATED_NAME,
147
+ null=True,
148
+ editable=False,
127
149
  )
128
150
  breadcrumbs_title = models.CharField(max_length=128, null=True, blank=True)
129
151
  slug = models.SlugField(max_length=150, allow_unicode=True, null=True, blank=True)
@@ -145,16 +167,16 @@ class AbstractPage(SeoMixin, MetaMixin, models.Model, metaclass=PageBase):
145
167
  blank=True,
146
168
  on_delete=models.CASCADE,
147
169
  )
148
-
170
+
149
171
  def __init__(self, *args, **kwargs):
150
172
  super(AbstractPage, self).__init__(*args, **kwargs)
151
- self._meta.get_field('template').choices = lazy(GET_TEMPLATE_CHOICES,list)()
173
+ self._meta.get_field("template").choices = lazy(GET_TEMPLATE_CHOICES, list)()
152
174
 
153
175
  def __str__(self) -> str:
154
176
  return "(%s) %s" % (self.__class__.__name__, self.title or self.permalink)
155
177
 
156
178
  def get_context(self, request=None):
157
- context={
179
+ context = {
158
180
  "page": self,
159
181
  "page_model": {"class": self.__class__.__name__, "module": self.__module__},
160
182
  "request": request,
@@ -164,7 +186,7 @@ class AbstractPage(SeoMixin, MetaMixin, models.Model, metaclass=PageBase):
164
186
  new_ctx = inject_func(request=request, super_ctx=context)
165
187
  if isinstance(new_ctx, dict):
166
188
  context.update(new_ctx)
167
- return context
189
+ return ctx_registry.get_context_for_page(self, request, super_ctx=context)
168
190
 
169
191
  @property
170
192
  def model_name(self) -> str:
@@ -177,13 +199,13 @@ class AbstractPage(SeoMixin, MetaMixin, models.Model, metaclass=PageBase):
177
199
  @property
178
200
  def permalink(self) -> str:
179
201
  return self.url_node and self.url_node.permalink
180
-
202
+
181
203
  @property
182
204
  def routerlink(self) -> str:
183
205
  return self.url_node and self.url_node.routerlink
184
-
206
+
185
207
  @property
186
- def breadcrumbs(self) -> Iterable[dict]:
208
+ def breadcrumbs(self) -> Sequence[dict]:
187
209
  breadcrumb = {
188
210
  "permalink": self.permalink,
189
211
  "title": self.breadcrumbs_title or self.title or self.slug,
@@ -197,7 +219,9 @@ class AbstractPage(SeoMixin, MetaMixin, models.Model, metaclass=PageBase):
197
219
  if self.status == "PUB":
198
220
  return True
199
221
  if self.status == "PLA":
200
- return bool(self.pubblication_date) and timezone.now() > self.pubblication_date
222
+ return (
223
+ bool(self.pubblication_date) and timezone.now() > self.pubblication_date
224
+ )
201
225
  return False
202
226
 
203
227
  def get_template_path(self, request=None) -> str:
@@ -222,7 +246,7 @@ class AbstractPage(SeoMixin, MetaMixin, models.Model, metaclass=PageBase):
222
246
 
223
247
  def _update_url_node(self, force: bool = False) -> UrlNode:
224
248
  self.url_node = self._get_or_create_url_node()
225
- for _ in activate_languages():
249
+ for __ in activate_languages():
226
250
  old_permalink = self.permalink
227
251
  new_permalink = self.generate_permalink()
228
252
  force = force or old_permalink != new_permalink
@@ -232,7 +256,7 @@ class AbstractPage(SeoMixin, MetaMixin, models.Model, metaclass=PageBase):
232
256
  self.update_childs()
233
257
  return self.url_node
234
258
 
235
- def generate_permalink(self, safe:bool=True) -> str:
259
+ def generate_permalink(self, safe: bool = True) -> str:
236
260
  slug = get_nofallbacks(self, "slug")
237
261
  if slug is None and not self.permalink:
238
262
  translations = get_field_translations(self, "slug").values()
@@ -277,14 +301,20 @@ class AbstractPage(SeoMixin, MetaMixin, models.Model, metaclass=PageBase):
277
301
  ).first()
278
302
  page = node and node.page
279
303
  type_error = not bypass_type_check and not isinstance(page, cls)
280
- public_error = not bypass_public_check and not getattr(page or object, "is_public", False)
304
+ public_error = not bypass_public_check and not getattr(
305
+ page or object, "is_public", False
306
+ )
281
307
  if not page or type_error or public_error:
282
308
  bases = (UrlNode.DoesNotExist,)
283
309
  if hasattr(cls, "DoesNotExist"):
284
310
  bases += (cls.DoesNotExist,)
285
- raise type("PageDoesNotExist", bases, {})(
286
- "%s matching query does not exist." % cls._meta.object_name
287
- )
311
+ message = "%s matching query does not exist." % cls._meta.object_name
312
+ if public_error:
313
+ message = (
314
+ "Match found: %s.\nThe page appears not to be public.\nUse ?preview=true in the url to see it."
315
+ % page
316
+ )
317
+ raise type("PageDoesNotExist", bases, {})(message)
288
318
  return page
289
319
 
290
320
  @classmethod
@@ -311,11 +341,15 @@ class AbstractPage(SeoMixin, MetaMixin, models.Model, metaclass=PageBase):
311
341
  def get_or_404(cls, request, *args, **kwargs) -> "AbstractPage":
312
342
  try:
313
343
  return cls.get(request, *args, **kwargs)
314
- except ObjectDoesNotExist:
315
- raise Http404("No %s matches the given query." % cls._meta.object_name)
344
+ except ObjectDoesNotExist as ex:
345
+ raise Http404(ex)
316
346
 
317
347
  def alternate_urls(self, *args, **kwargs) -> dict:
318
- return get_field_translations(self.url_node or object, "permalink", None)
348
+ permalinks = get_field_translations(self.url_node or object, "permalink", None)
349
+ for lang in activate_languages():
350
+ if lang in permalinks:
351
+ permalinks[lang] = UrlNode.reverse_url(permalinks[lang])
352
+ return permalinks
319
353
 
320
354
  class Meta:
321
355
  abstract = True
@@ -7,9 +7,13 @@ from camomilla.contrib.rest_framework.serializer import (
7
7
  plain_to_nest,
8
8
  TRANS_ACCESSOR,
9
9
  )
10
+ from camomilla.serializers.fields.json import StructuredJSONField
11
+ from camomilla.utils.getters import find_and_replace_dict, pointed_getter
10
12
 
11
13
 
12
14
  class AutoSchema(DRFAutoSchema):
15
+ extra_components = {}
16
+
13
17
  def map_serializer(self, serializer):
14
18
  schema = super(AutoSchema, self).map_serializer(serializer)
15
19
  if isinstance(serializer, TranslationsMixin) and serializer.is_translatable:
@@ -23,6 +27,29 @@ class AutoSchema(DRFAutoSchema):
23
27
  }
24
28
  return schema
25
29
 
30
+ def get_components(self, path, method):
31
+ components = super().get_components(path, method)
32
+ if len(self.extra_components.keys()) > 0:
33
+ components = {**(components or {}), **self.extra_components}
34
+ return components
35
+
36
+ def map_field(self, field):
37
+ if isinstance(field, StructuredJSONField):
38
+
39
+ def replace(key, value):
40
+ if isinstance(value, str) and value.startswith("#/definitions"):
41
+ return value.replace("#/definitions", "#/components/schemas")
42
+ return value
43
+
44
+ self.extra_components.update(
45
+ **find_and_replace_dict(
46
+ field.json_schema.pop("definitions", {}), replace
47
+ )
48
+ )
49
+
50
+ return find_and_replace_dict(field.json_schema, replace)
51
+ return super().map_field(field)
52
+
26
53
 
27
54
  class SchemaGenerator(DRFSchemaGenerator):
28
55
  def create_view(self, callback, method, request=None):
@@ -38,7 +38,6 @@ def compile_payload(data, path, value):
38
38
 
39
39
 
40
40
  class MultipartJsonParser(parsers.BaseParser):
41
-
42
41
  media_type = "multipart/form-data"
43
42
 
44
43
  def parse(self, stream, media_type=None, parser_context=None):
@@ -0,0 +1,24 @@
1
+ from rest_framework import serializers
2
+ from rest_framework.utils import model_meta
3
+
4
+
5
+ class StructuredJSONField(serializers.JSONField):
6
+ def __init__(self, **kwargs):
7
+ self.schema = kwargs.pop("schema", None)
8
+ super().__init__(**kwargs)
9
+
10
+ def bind(self, field_name, parent):
11
+ if self.schema is None and isinstance(parent, serializers.ModelSerializer):
12
+ info = model_meta.get_field_info(parent.Meta.model)
13
+ field = info.fields[field_name]
14
+ self.schema = field.schema
15
+ self.many = field.many
16
+ self.json_schema = field.schema.json_schema()
17
+ super().bind(field_name, parent)
18
+
19
+ def to_representation(self, instance):
20
+ if isinstance(instance, list) and self.many:
21
+ return super().to_representation(
22
+ self.schema.dump_python(instance, exclude_unset=True)
23
+ )
24
+ return super().to_representation(instance.model_dump(exclude_unset=True))
@@ -20,6 +20,9 @@ if django.VERSION >= (4, 0):
20
20
  else:
21
21
  from django.contrib.postgres.fields import JSONField as DjangoJSONField
22
22
 
23
+ from typing import TYPE_CHECKING
24
+ if TYPE_CHECKING:
25
+ from camomilla.models.page import AbstractPage
23
26
 
24
27
  # TODO: decide what to do with LangInfoMixin mixin!
25
28
  class LangInfoMixin(metaclass=serializers.SerializerMetaclass):
@@ -121,8 +124,17 @@ class NestMixin:
121
124
  return field_class, field_kwargs
122
125
 
123
126
 
124
-
125
127
  class AbstractPageMixin(serializers.ModelSerializer):
128
+ breadcrumbs = serializers.SerializerMethodField()
129
+ routerlink = serializers.CharField(read_only=True)
130
+ template = serializers.SerializerMethodField()
131
+
132
+ def get_template(self, instance: 'AbstractPage'):
133
+ return instance.get_template_path()
134
+
135
+ def get_breadcrumbs(self, instance: 'AbstractPage'):
136
+ return instance.breadcrumbs
137
+
126
138
  LANG_PERMALINK_FIELDS = [
127
139
  build_localized_fieldname("permalink", lang)
128
140
  for lang in AVAILABLE_LANGUAGES
@@ -134,6 +146,9 @@ class AbstractPageMixin(serializers.ModelSerializer):
134
146
  return super().translation_fields + ["permalink"]
135
147
 
136
148
  def get_default_field_names(self, *args):
149
+ from camomilla.contrib.rest_framework.serializer import RemoveTranslationsMixin
150
+ if RemoveTranslationsMixin in self.__class__.__bases__: # noqa: E501
151
+ return super().get_default_field_names(*args)
137
152
  return (
138
153
  [f for f in super().get_default_field_names(*args) if f != "url_node"]
139
154
  + self.LANG_PERMALINK_FIELDS
@@ -150,4 +165,3 @@ class AbstractPageMixin(serializers.ModelSerializer):
150
165
 
151
166
  def get_validators(self):
152
167
  return super().get_validators() + [UniquePermalinkValidator()]
153
-