django-camomilla-cms 6.0.1__tar.gz → 6.1.1__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 (144) hide show
  1. {django_camomilla_cms-6.0.1/django_camomilla_cms.egg-info → django_camomilla_cms-6.1.1}/PKG-INFO +1 -1
  2. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/__init__.py +1 -1
  3. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/models/menu.py +1 -1
  4. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/models/page.py +22 -3
  5. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/serializers/page.py +4 -3
  6. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/settings.py +7 -1
  7. django_camomilla_cms-6.1.1/camomilla/theme/__init__.py +1 -0
  8. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/urls.py +3 -3
  9. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/views/menus.py +8 -3
  10. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/views/pages.py +4 -4
  11. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1/django_camomilla_cms.egg-info}/PKG-INFO +1 -1
  12. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/django_camomilla_cms.egg-info/SOURCES.txt +2 -0
  13. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/setup.py +1 -1
  14. django_camomilla_cms-6.1.1/tests/test_page_meta.py +88 -0
  15. django_camomilla_cms-6.1.1/tests/test_page_relation_api.py +77 -0
  16. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/tests/test_pages.py +2 -4
  17. django_camomilla_cms-6.0.1/camomilla/theme/__init__.py +0 -1
  18. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/LICENSE +0 -0
  19. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/MANIFEST.in +0 -0
  20. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/README.md +0 -0
  21. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/apps.py +0 -0
  22. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/authentication.py +0 -0
  23. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/context_processors.py +0 -0
  24. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/contrib/__init__.py +0 -0
  25. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/contrib/modeltranslation/__init__.py +0 -0
  26. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/contrib/modeltranslation/hvad_migration.py +0 -0
  27. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/defaults.py +0 -0
  28. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/dynamic_pages_urls.py +0 -0
  29. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/exceptions.py +0 -0
  30. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/fields/__init__.py +0 -0
  31. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/fields/json.py +0 -0
  32. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/management/__init__.py +0 -0
  33. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/management/commands/__init__.py +0 -0
  34. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/management/commands/regenerate_thumbnails.py +0 -0
  35. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/managers/__init__.py +0 -0
  36. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/managers/pages.py +0 -0
  37. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/model_api.py +0 -0
  38. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/models/__init__.py +0 -0
  39. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/models/article.py +0 -0
  40. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/models/content.py +0 -0
  41. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/models/media.py +0 -0
  42. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/models/mixins/__init__.py +0 -0
  43. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/openapi/__init__.py +0 -0
  44. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/openapi/schema.py +0 -0
  45. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/parsers.py +0 -0
  46. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/permissions.py +0 -0
  47. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/redirects.py +0 -0
  48. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/serializers/__init__.py +0 -0
  49. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/serializers/article.py +0 -0
  50. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/serializers/base/__init__.py +0 -0
  51. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/serializers/content_type.py +0 -0
  52. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/serializers/fields/__init__.py +0 -0
  53. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/serializers/fields/file.py +0 -0
  54. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/serializers/fields/related.py +0 -0
  55. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/serializers/media.py +0 -0
  56. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/serializers/menu.py +0 -0
  57. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/serializers/mixins/__init__.py +0 -0
  58. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/serializers/mixins/fields.py +0 -0
  59. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/serializers/mixins/filter_fields.py +0 -0
  60. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/serializers/mixins/json.py +0 -0
  61. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/serializers/mixins/language.py +0 -0
  62. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/serializers/mixins/nesting.py +0 -0
  63. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/serializers/mixins/optimize.py +0 -0
  64. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/serializers/mixins/ordering.py +0 -0
  65. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/serializers/mixins/page.py +0 -0
  66. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/serializers/mixins/translation.py +0 -0
  67. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/serializers/user.py +0 -0
  68. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/serializers/utils.py +0 -0
  69. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/serializers/validators.py +0 -0
  70. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/sitemap.py +0 -0
  71. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/storages/__init__.py +0 -0
  72. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/storages/default.py +0 -0
  73. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/storages/optimize.py +0 -0
  74. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/storages/overwrite.py +0 -0
  75. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/templates/admin/camomilla/page/change_form.html +0 -0
  76. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/templates/defaults/articles/default.html +0 -0
  77. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/templates/defaults/base.html +0 -0
  78. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/templates/defaults/pages/default.html +0 -0
  79. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/templates/defaults/parts/langswitch.html +0 -0
  80. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/templates/defaults/parts/menu.html +0 -0
  81. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/templates/defaults/widgets/media_select_multiple.html +0 -0
  82. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/templates_context/__init__.py +0 -0
  83. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/templates_context/autodiscover.py +0 -0
  84. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/templates_context/rendering.py +0 -0
  85. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/templatetags/__init__.py +0 -0
  86. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/templatetags/camomilla_filters.py +0 -0
  87. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/templatetags/menus.py +0 -0
  88. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/templatetags/model_extras.py +0 -0
  89. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/theme/admin/__init__.py +0 -0
  90. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/theme/admin/pages.py +0 -0
  91. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/theme/admin/translations.py +0 -0
  92. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/theme/apps.py +0 -0
  93. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/theme/static/admin/css/responsive.css +0 -0
  94. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/theme/static/admin/img/favicon.ico +0 -0
  95. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/theme/static/admin/img/logo.svg +0 -0
  96. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/theme/templates/admin/base.html +0 -0
  97. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/theme/templates/rosetta/base.html +0 -0
  98. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/translation.py +0 -0
  99. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/utils/__init__.py +0 -0
  100. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/utils/getters.py +0 -0
  101. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/utils/normalization.py +0 -0
  102. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/utils/query_parser.py +0 -0
  103. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/utils/seo.py +0 -0
  104. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/utils/setters.py +0 -0
  105. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/utils/templates.py +0 -0
  106. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/utils/translation.py +0 -0
  107. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/views/__init__.py +0 -0
  108. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/views/articles.py +0 -0
  109. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/views/base/__init__.py +0 -0
  110. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/views/contents.py +0 -0
  111. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/views/decorators.py +0 -0
  112. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/views/languages.py +0 -0
  113. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/views/medias.py +0 -0
  114. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/views/mixins/__init__.py +0 -0
  115. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/views/mixins/bulk_actions.py +0 -0
  116. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/views/mixins/language.py +0 -0
  117. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/views/mixins/optimize.py +0 -0
  118. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/views/mixins/ordering.py +0 -0
  119. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/views/mixins/pagination.py +0 -0
  120. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/views/mixins/permissions.py +0 -0
  121. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/views/tags.py +0 -0
  122. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/camomilla/views/users.py +0 -0
  123. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/django_camomilla_cms.egg-info/dependency_links.txt +0 -0
  124. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/django_camomilla_cms.egg-info/requires.txt +0 -0
  125. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/django_camomilla_cms.egg-info/top_level.txt +0 -0
  126. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/pyproject.toml +0 -0
  127. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/setup.cfg +0 -0
  128. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/tests/__init__.py +0 -0
  129. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/tests/fixtures/__init__.py +0 -0
  130. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/tests/test_admin_page_form.py +0 -0
  131. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/tests/test_api.py +0 -0
  132. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/tests/test_camomilla_filters.py +0 -0
  133. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/tests/test_media.py +0 -0
  134. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/tests/test_menu.py +0 -0
  135. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/tests/test_model_api.py +0 -0
  136. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/tests/test_model_api_permissions.py +0 -0
  137. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/tests/test_model_api_register.py +0 -0
  138. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/tests/test_models.py +0 -0
  139. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/tests/test_query_parser.py +0 -0
  140. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/tests/test_templates_context.py +0 -0
  141. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/tests/test_utils.py +0 -0
  142. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/tests/utils/__init__.py +0 -0
  143. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/tests/utils/api.py +0 -0
  144. {django_camomilla_cms-6.0.1 → django_camomilla_cms-6.1.1}/tests/utils/media.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-camomilla-cms
3
- Version: 6.0.1
3
+ Version: 6.1.1
4
4
  Summary: Django powered cms
5
5
  Author-email: Lotrèk <dimmitutto@lotrek.it>
6
6
  License: MIT
@@ -1,4 +1,4 @@
1
- __version__ = "6.0.1"
1
+ __version__ = "6.1.1"
2
2
 
3
3
 
4
4
  def get_core_apps():
@@ -74,7 +74,7 @@ class MenuNode(BaseModel):
74
74
 
75
75
 
76
76
  class Menu(models.Model):
77
- key = models.CharField(max_length=200, unique=True, editable=False)
77
+ key = models.CharField(max_length=200, unique=True, editable=True, default=uuid4)
78
78
  available_classes = models.JSONField(default=dict, editable=False)
79
79
  enabled = models.BooleanField(default=True)
80
80
  nodes = StructuredJSONField(default=list, schema=MenuNode)
@@ -29,6 +29,7 @@ from camomilla import settings
29
29
  from camomilla.templates_context.rendering import ctx_registry
30
30
  from django.conf import settings as django_settings
31
31
  from modeltranslation.utils import build_localized_fieldname
32
+ from django.utils.module_loading import import_string
32
33
 
33
34
 
34
35
  class UrlRedirect(models.Model):
@@ -287,6 +288,22 @@ class AbstractPage(SeoMixin, MetaMixin, models.Model, metaclass=PageBase):
287
288
  context.update(new_ctx)
288
289
  return ctx_registry.get_context_for_page(self, request, super_ctx=context)
289
290
 
291
+ @classmethod
292
+ def get_serializer(cls):
293
+ from camomilla.serializers.mixins import AbstractPageMixin
294
+
295
+ standard_serializer = (
296
+ pointed_getter(cls, "_page_meta.standard_serializer")
297
+ or settings.PAGES_DEFAULT_SERIALIZER
298
+ )
299
+ if isinstance(standard_serializer, str):
300
+ standard_serializer = import_string(standard_serializer)
301
+ if not issubclass(standard_serializer, AbstractPageMixin):
302
+ raise ValueError(
303
+ f"Standard serializer {standard_serializer} must be a subclass of AbstractPageMixin"
304
+ )
305
+ return standard_serializer
306
+
290
307
  @property
291
308
  def model_name(self) -> str:
292
309
  return self._meta.app_label + "." + self._meta.model_name
@@ -326,7 +343,7 @@ class AbstractPage(SeoMixin, MetaMixin, models.Model, metaclass=PageBase):
326
343
  def childs(self) -> models.Manager:
327
344
  if hasattr(self._page_meta, "child_page_field"):
328
345
  return getattr(self, self._page_meta.child_page_field)
329
- return getattr(self, PAGE_CHILD_RELATED_NAME % self.model_info)
346
+ return getattr(self, PAGE_CHILD_RELATED_NAME % self.model_info, self.__class__.objects.none())
330
347
 
331
348
  @property
332
349
  def parent(self) -> models.Model:
@@ -356,7 +373,8 @@ class AbstractPage(SeoMixin, MetaMixin, models.Model, metaclass=PageBase):
356
373
  def generate_permalink(self, safe: bool = True) -> str:
357
374
  permalink = f"/{slugify(self.title or '', allow_unicode=True)}"
358
375
  if self.parent:
359
- permalink = f"/{self.parent.permalink}{permalink}"
376
+ parent_permalink = (self.parent.permalink or "").lstrip("/")
377
+ permalink = f"/{parent_permalink}{permalink}"
360
378
  set_nofallbacks(self, "permalink", permalink)
361
379
  qs = UrlNode.objects.exclude(pk=getattr(self.url_node or object, "pk", None))
362
380
  if safe and qs.filter(permalink=permalink).exists():
@@ -455,7 +473,7 @@ class AbstractPage(SeoMixin, MetaMixin, models.Model, metaclass=PageBase):
455
473
  preview = request and getattr(request, "GET", {}).get("preview", False)
456
474
  permalinks = get_field_translations(self.url_node or object, "permalink", None)
457
475
  for lang in activate_languages():
458
- if lang in permalinks:
476
+ if lang in permalinks and permalinks[lang]:
459
477
  permalinks[lang] = (
460
478
  UrlNode.reverse_url(permalinks[lang])
461
479
  if preview or self.is_public
@@ -475,6 +493,7 @@ class AbstractPage(SeoMixin, MetaMixin, models.Model, metaclass=PageBase):
475
493
  parent_page_field = "parent_page"
476
494
  default_template = settings.PAGE_DEFAULT_TEMPLATE
477
495
  inject_context_func = settings.PAGE_INJECT_CONTEXT_FUNC
496
+ standard_serializer = settings.PAGES_DEFAULT_SERIALIZER
478
497
 
479
498
 
480
499
  class Page(AbstractPage):
@@ -22,7 +22,7 @@ class PageSerializer(AbstractPageMixin, BaseModelSerializer):
22
22
  fields = "__all__"
23
23
 
24
24
 
25
- class BasicUrlNodeSerializer(BaseModelSerializer):
25
+ class UrlNodeSerializer(BaseModelSerializer):
26
26
  is_public = serializers.SerializerMethodField()
27
27
  status = serializers.SerializerMethodField()
28
28
  indexable = serializers.SerializerMethodField()
@@ -41,17 +41,18 @@ class BasicUrlNodeSerializer(BaseModelSerializer):
41
41
  return instance.page.indexable
42
42
 
43
43
 
44
- class UrlNodeSerializer(BasicUrlNodeSerializer):
44
+ class RouteSerializer(UrlNodeSerializer):
45
45
  alternates = serializers.SerializerMethodField()
46
46
 
47
47
  def get_alternates(self, instance: UrlNode):
48
48
  return instance.page.alternate_urls()
49
49
 
50
50
  def to_representation(self, instance: UrlNode):
51
+ standard_serializer = instance.page.get_serializer()
51
52
  model_serializer = build_standard_model_serializer(
52
53
  instance.page.__class__,
53
54
  depth=10,
54
- bases=(AbstractPageMixin,) + get_standard_bases(),
55
+ bases=(standard_serializer,) + get_standard_bases(),
55
56
  )
56
57
  return {
57
58
  **super().to_representation(instance),
@@ -49,6 +49,12 @@ PAGE_INJECT_CONTEXT_FUNC = pointed_getter(
49
49
  django_settings, "CAMOMILLA.RENDER.PAGE.INJECT_CONTEXT", None
50
50
  )
51
51
 
52
+ PAGES_DEFAULT_SERIALIZER = pointed_getter(
53
+ django_settings,
54
+ "CAMOMILLA.API.PAGES.DEFAULT_SERIALIZER",
55
+ "camomilla.serializers.mixins.AbstractPageMixin",
56
+ )
57
+
52
58
  ENABLE_TRANSLATIONS = (
53
59
  ENABLE_REGISTRATIONS and "modeltranslation" in django_settings.INSTALLED_APPS
54
60
  )
@@ -127,6 +133,6 @@ DEBUG = pointed_getter(django_settings, "CAMOMILLA.DEBUG", django_settings.DEBUG
127
133
  # "URL": "http://localhost:4321"
128
134
  # }
129
135
  # }
130
- # "API": {"NESTING_DEPTH": 10, "TRANSLATION_ACCESSOR": "translations"},
136
+ # "API": {"NESTING_DEPTH": 10, "TRANSLATION_ACCESSOR": "translations", "PAGES": {"DEFAULT_SERIALIZER": "camomilla.serializers.page.RouteSerializer"}},
131
137
  # "DEBUG": False
132
138
  # }
@@ -0,0 +1 @@
1
+ __version__ = "6.1.1"
@@ -19,7 +19,7 @@ from camomilla.views import (
19
19
  UserViewSet,
20
20
  MenuViewSet,
21
21
  )
22
- from camomilla.views.pages import fetch_page
22
+ from camomilla.views.pages import pages_router
23
23
  from camomilla.redirects import url_patterns as old_redirects
24
24
 
25
25
  router = routers.DefaultRouter()
@@ -37,8 +37,8 @@ router.register(r"menus", MenuViewSet, "camomilla-menus")
37
37
  urlpatterns = [
38
38
  *old_redirects,
39
39
  path("", include(router.urls)),
40
- path("pages-router/", fetch_page),
41
- path("pages-router/<path:permalink>", fetch_page),
40
+ path("pages-router/", pages_router),
41
+ path("pages-router/<path:permalink>", pages_router),
42
42
  path("token-auth/", CamomillaObtainAuthToken.as_view(), name="api_token"),
43
43
  path("auth/login/", CamomillaAuthLogin.as_view(), name="login"),
44
44
  path("auth/logout/", CamomillaAuthLogout.as_view(), name="logout"),
@@ -9,7 +9,7 @@ from camomilla.models import AbstractPage, Menu
9
9
  from camomilla.models.page import UrlNode
10
10
  from camomilla.permissions import CamomillaBasePermissions
11
11
  from camomilla.serializers import ContentTypeSerializer, MenuSerializer
12
- from camomilla.serializers.page import BasicUrlNodeSerializer
12
+ from camomilla.serializers.page import UrlNodeSerializer
13
13
  from camomilla.views.base import BaseModelViewset
14
14
  from camomilla.views.decorators import active_lang
15
15
 
@@ -66,7 +66,12 @@ class MenuViewSet(BaseModelViewset):
66
66
  raise Http404("No object matches the given query.")
67
67
  return Response(
68
68
  [
69
- {"id": obj.pk, "name": str(obj), "url_node_id": obj.url_node.pk}
69
+ {
70
+ "id": obj.pk,
71
+ "name": str(obj),
72
+ "url_node_id": obj.url_node.pk,
73
+ "model": f"{content_type.app_label}.{content_type.model}",
74
+ }
70
75
  for obj in content_type.model_class().objects.exclude(
71
76
  url_node__isnull=True
72
77
  )
@@ -78,4 +83,4 @@ class MenuViewSet(BaseModelViewset):
78
83
  def search_urlnode(self, request, *args, **kwargs):
79
84
  url_node = request.GET.get("q", "")
80
85
  qs = UrlNode.objects.filter(permalink__icontains=url_node).order_by("permalink")
81
- return Response(BasicUrlNodeSerializer(qs, many=True).data)
86
+ return Response(UrlNodeSerializer(qs, many=True).data)
@@ -2,7 +2,7 @@ from camomilla.models import Page
2
2
  from camomilla.models.page import UrlNode, UrlRedirect
3
3
  from camomilla.permissions import CamomillaBasePermissions
4
4
  from camomilla.serializers import PageSerializer
5
- from camomilla.serializers.page import UrlNodeSerializer
5
+ from camomilla.serializers.page import RouteSerializer
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
@@ -26,10 +26,10 @@ class PageViewSet(GetUserLanguageMixin, BulkDeleteMixin, BaseModelViewset):
26
26
  permissions.AllowAny,
27
27
  ]
28
28
  )
29
- def fetch_page(request, permalink=""):
29
+ def pages_router(request, permalink=""):
30
30
  redirect = UrlRedirect.find_redirect_from_url(f"/{permalink}")
31
31
  if redirect:
32
32
  redirect = redirect.redirect()
33
33
  return Response({"redirect": redirect.url, "status": redirect.status_code})
34
- node = get_object_or_404(UrlNode, permalink=f"/{permalink}")
35
- return Response(UrlNodeSerializer(node, context={"request": request}).data)
34
+ node: UrlNode = get_object_or_404(UrlNode, permalink=f"/{permalink}")
35
+ return Response(RouteSerializer(node, context={"request": request}).data)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-camomilla-cms
3
- Version: 6.0.1
3
+ Version: 6.1.1
4
4
  Summary: Django powered cms
5
5
  Author-email: Lotrèk <dimmitutto@lotrek.it>
6
6
  License: MIT
@@ -129,6 +129,8 @@ tests/test_model_api.py
129
129
  tests/test_model_api_permissions.py
130
130
  tests/test_model_api_register.py
131
131
  tests/test_models.py
132
+ tests/test_page_meta.py
133
+ tests/test_page_relation_api.py
132
134
  tests/test_pages.py
133
135
  tests/test_query_parser.py
134
136
  tests/test_templates_context.py
@@ -1,5 +1,5 @@
1
1
  from setuptools import setup
2
2
 
3
- __version__ = "6.0.1"
3
+ __version__ = "6.1.1"
4
4
 
5
5
  setup(version=__version__)
@@ -0,0 +1,88 @@
1
+ import pytest
2
+ from django.test import TestCase
3
+ from rest_framework.test import APIClient
4
+
5
+ from tests.utils.media import load_asset_and_remove_media
6
+ from .utils.api import login_superuser
7
+ from camomilla.models import Page, Media
8
+ from example.website.models import CustomPageMetaModel, InvalidPageMetaModel
9
+
10
+
11
+ class PagaMetaTestCase(TestCase):
12
+ def setUp(self):
13
+ self.client = APIClient()
14
+ token = login_superuser()
15
+ self.client.credentials(HTTP_AUTHORIZATION="Token " + token)
16
+
17
+ @pytest.mark.django_db
18
+ def test_page_meta_rendering(self):
19
+ asset = load_asset_and_remove_media("37059501.png")
20
+ Media.objects.create(
21
+ file=asset,
22
+ alt_text="Test Media",
23
+ title="Test Media",
24
+ description="Description of test media",
25
+ )
26
+ page = CustomPageMetaModel.objects.create(
27
+ title="Test Page",
28
+ custom_field="Custom Data",
29
+ permalink="test-page",
30
+ status="PUB",
31
+ autopermalink=False,
32
+ )
33
+ page.save()
34
+ response = self.client.get("/test-page/")
35
+ assert response.status_code == 200
36
+ content = response.content.decode()
37
+ assert "👻 I&#x27;m beeing injected!" in content
38
+ assert "<h1>I'm the custom template!</h1>" in content
39
+ assert "<ul><li>Test Media</li></ul>" in content
40
+
41
+ @pytest.mark.django_db
42
+ def test_page_meta_custom_parent_page(self):
43
+ parent_page = Page.objects.create(
44
+ title="Parent Page",
45
+ permalink="parent-page",
46
+ status="PUB",
47
+ autopermalink=False,
48
+ )
49
+ child_page = CustomPageMetaModel.objects.create(
50
+ title="Child Page",
51
+ custom_field="Child Data",
52
+ status="PUB",
53
+ custom_parent_page=parent_page,
54
+ )
55
+ assert child_page.permalink == "/parent-page/child-page"
56
+
57
+ @pytest.mark.django_db
58
+ def test_page_meta_custom_serializer(self):
59
+
60
+ CustomPageMetaModel.objects.create(
61
+ title="Test Page with Custom Serializer",
62
+ custom_field="Custom Data",
63
+ permalink="test-page-custom-serializer",
64
+ status="PUB",
65
+ autopermalink=False,
66
+ )
67
+
68
+ response = self.client.get(
69
+ "/api/camomilla/pages-router/test-page-custom-serializer"
70
+ )
71
+ assert response.status_code == 200
72
+ data = response.json()
73
+ assert data["title"] == "Test Page with Custom Serializer"
74
+ assert data["custom_field"] == "Custom Data"
75
+ assert data["permalink"] == "/test-page-custom-serializer"
76
+ assert (
77
+ data["serializer_custom_field"]
78
+ == "I'm coming from CustomPageSerializer! 🫡"
79
+ )
80
+
81
+ @pytest.mark.django_db
82
+ def test_page_meta_custom_serializer_error(self):
83
+ with pytest.raises(ValueError) as exc_info:
84
+ InvalidPageMetaModel.get_serializer()
85
+ assert (
86
+ str(exc_info.value)
87
+ == "Standard serializer <class 'example.website.serializers.InvalidSerializer'> must be a subclass of AbstractPageMixin"
88
+ )
@@ -0,0 +1,77 @@
1
+ from django.test import TestCase
2
+ from rest_framework.test import APIClient
3
+ from .utils.api import login_superuser
4
+ from example.website.models import (
5
+ ExposedRelatedPageModel,
6
+ UnexposedRelatedPageModel,
7
+ RelatedPageModel,
8
+ )
9
+
10
+ client = APIClient()
11
+
12
+
13
+ class PageRelationApi(TestCase):
14
+ def setUp(self):
15
+ token = login_superuser()
16
+ client.credentials(HTTP_AUTHORIZATION="Token " + token)
17
+
18
+ def test_exposed_relation(self):
19
+ exposed_related_page_model = ExposedRelatedPageModel.objects.create(
20
+ title="ExposedRelatedPageModel 1",
21
+ permalink="exposed-related-page-model-1",
22
+ status="PUB",
23
+ autopermalink=False,
24
+ )
25
+ related_page_model = RelatedPageModel.objects.create(
26
+ title="RelatedPageModel 1",
27
+ permalink="related-page-model-1",
28
+ status="PUB",
29
+ autopermalink=False,
30
+ )
31
+ related_page_model.exposed_pages.add(exposed_related_page_model)
32
+ related_page_model.save()
33
+
34
+ response = client.get("/api/camomilla/pages-router/related-page-model-1")
35
+ assert response.status_code == 200
36
+ data = response.json()
37
+ assert data["exposed_pages"][0]["id"] == exposed_related_page_model.id
38
+
39
+ response = client.get(
40
+ "/api/camomilla/pages-router/exposed-related-page-model-1"
41
+ )
42
+ assert response.status_code == 200
43
+ data = response.json()
44
+ assert (
45
+ "related_pages" in data
46
+ ), "Exposed related pages should be included in the API response"
47
+
48
+ def test_unexposed_relation(self):
49
+ unexposed_related_page_model = UnexposedRelatedPageModel.objects.create(
50
+ title="UnexposedRelatedPageModel 1",
51
+ permalink="unexposed-related-page-model-1",
52
+ status="PUB",
53
+ autopermalink=False,
54
+ )
55
+ related_page_model = RelatedPageModel.objects.create(
56
+ title="RelatedPageModel 1",
57
+ permalink="related-page-model-1",
58
+ status="PUB",
59
+ autopermalink=False,
60
+ )
61
+
62
+ related_page_model.unexposed_pages.add(unexposed_related_page_model)
63
+ related_page_model.save()
64
+
65
+ response = client.get("/api/camomilla/pages-router/related-page-model-1")
66
+ assert response.status_code == 200
67
+ data = response.json()
68
+ assert data["unexposed_pages"][0]["id"] == unexposed_related_page_model.id
69
+
70
+ response = client.get(
71
+ "/api/camomilla/pages-router/unexposed-related-page-model-1"
72
+ )
73
+ assert response.status_code == 200
74
+ data = response.json()
75
+ assert (
76
+ "related_pages" not in data
77
+ ), "Unexposed related pages should not be included in the API response"
@@ -165,13 +165,11 @@ class PagesTestCase(TestCase):
165
165
  # EN parent page with automatic url creation
166
166
  response = self.client.get("/api/camomilla/pages/3/?language=en")
167
167
  assert response.json()["autopermalink"] == True
168
- assert response.json()["permalink"] == "//permalink_manual_en_2/title_page_3"
168
+ assert response.json()["permalink"] == "/permalink_manual_en_2/title_page_3"
169
169
  # IT parent page with automatic url creation
170
170
  response = self.client.get("/api/camomilla/pages/3/?language=it")
171
171
  assert response.json()["autopermalink"] == True
172
- assert (
173
- response.json()["permalink"] == "//permalink_manuale_it_2/titolo_pagina_3"
174
- )
172
+ assert response.json()["permalink"] == "/permalink_manuale_it_2/titolo_pagina_3"
175
173
 
176
174
  # Check url uniqueness and consistency EN
177
175
  response = self.client.post(
@@ -1 +0,0 @@
1
- __version__ = "6.0.1"