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
camomilla/__init__.py CHANGED
@@ -1,5 +1,11 @@
1
- __version__ = "5.8.5"
1
+ __version__ = "6.0.0"
2
2
 
3
3
 
4
4
  def get_core_apps():
5
- return ["rest_framework", "rest_framework.authtoken", "hvad"]
5
+ return ["rest_framework", "rest_framework.authtoken"]
6
+
7
+
8
+ def autodiscover():
9
+ from camomilla.templates_context.autodiscover import autodiscover_context_files
10
+
11
+ autodiscover_context_files()
camomilla/apps.py CHANGED
@@ -1,8 +1,16 @@
1
1
  from __future__ import unicode_literals
2
2
 
3
3
  from django.apps import AppConfig
4
+ from django.conf import settings
4
5
 
5
6
 
6
7
  class CamomillaConfig(AppConfig):
7
- default_auto_field = 'django.db.models.AutoField'
8
+ default_auto_field = "django.db.models.AutoField"
8
9
  name = "camomilla"
10
+
11
+ def ready(self):
12
+ migration_modules = getattr(settings, "MIGRATION_MODULES", {})
13
+ if "camomilla" not in migration_modules:
14
+ migration_modules["camomilla"] = "camomilla_migrations"
15
+ setattr(settings, "MIGRATION_MODULES", migration_modules)
16
+ self.module.autodiscover()
@@ -0,0 +1,6 @@
1
+ from camomilla.models import Menu
2
+
3
+
4
+ def MenusContextProcessor(request):
5
+ qs = Menu.objects.all()
6
+ return {"menus": Menu.defaultdict(**{m.key: m for m in qs})}
File without changes
@@ -0,0 +1,126 @@
1
+ from django.conf import settings
2
+ from django.db import migrations, connection
3
+
4
+
5
+ class KeepTranslationsMixin:
6
+ """
7
+ This mixin make it possible to keep translations when migrating from django-hvad to modeltranslation and viceversa.
8
+ To use it, you have to add a dictionary to your migration class called "keep_translations".
9
+ The dictionary must have model paths as keys and a list of fields to keep as values.
10
+
11
+
12
+ Example:
13
+ ```python
14
+ class Migration(KeepTranslationsMixin, migrations.Migration):
15
+ keep_translations = {
16
+ "app.Model": ("field1", "field2", "field3")
17
+ }
18
+ ```
19
+ """
20
+
21
+ _saved_data_from_plain = {}
22
+ language_codes = dict(getattr(settings, "LANGUAGES", {})).keys()
23
+
24
+ def is_operation_legit(self, o):
25
+ return not (
26
+ isinstance(o, migrations.RemoveField)
27
+ and o.name == "master"
28
+ and o.model_name.endswith("translation")
29
+ )
30
+
31
+ def __init__(self, *args, **kwargs):
32
+ super().__init__(*args, **kwargs)
33
+ self.operations = [o for o in self.operations if self.is_operation_legit(o)]
34
+ self.operations.insert(
35
+ 0, migrations.RunPython(self._getDataFromHvad, self._restoreDataToHvad)
36
+ )
37
+ self.operations.append(
38
+ migrations.RunPython(
39
+ self._restoreDataToModelTranslation, self._getDataFromModelTranslation
40
+ )
41
+ )
42
+
43
+ def _getDataFromHvad(self, apps, schemaeditor):
44
+ for modelPath, fields in self.keep_translations.items():
45
+ Model = apps.get_model(*modelPath.split("."))
46
+ table = Model._meta.db_table + "_translation"
47
+ if "language_code" not in fields:
48
+ fields = ("language_code",) + fields
49
+ with connection.cursor() as cursor:
50
+ cursor.execute("SELECT master_id FROM {0};".format(table))
51
+ masters = list(set(cursor.fetchall()))
52
+ for master in masters:
53
+ cursor.execute(
54
+ "SELECT {0} FROM {1} WHERE master_id={2};".format(
55
+ ",".join(fields), table, master[0]
56
+ )
57
+ )
58
+ rows = cursor.fetchall()
59
+ self._saved_data_from_plain[modelPath] = (
60
+ self._saved_data_from_plain.get(modelPath, {})
61
+ )
62
+ self._saved_data_from_plain[modelPath][master[0]] = (
63
+ self._saved_data_from_plain[modelPath].get(master[0], [])
64
+ )
65
+ for row in rows:
66
+ self._saved_data_from_plain[modelPath][master[0]].append(
67
+ dict(zip(fields, row))
68
+ )
69
+
70
+ def _getDataFromModelTranslation(self, apps, schemaeditor):
71
+ for modelPath, fields in self.keep_translations.items():
72
+ Model = apps.get_model(*modelPath.split("."))
73
+ table = Model._meta.db_table
74
+ for lang in self.language_codes:
75
+ t_fields = ("id",) + tuple(
76
+ "{0}_{1}".format(f, lang) for f in fields if f != "id"
77
+ )
78
+ with connection.cursor() as cursor:
79
+ cursor.execute(
80
+ "SELECT {0} FROM {1}".format(
81
+ ",".join(t_fields),
82
+ table,
83
+ )
84
+ )
85
+ rows = cursor.fetchall()
86
+ self._saved_data_from_plain[modelPath] = (
87
+ self._saved_data_from_plain.get(modelPath, [])
88
+ )
89
+ for row in rows:
90
+ row_data = dict(zip(("master_id", *fields), row))
91
+ row_data.update({"language_code": lang})
92
+ self._saved_data_from_plain[modelPath].append(row_data)
93
+
94
+ def _restoreDataToModelTranslation(self, apps, schemaeditor):
95
+ for key, master_dict in self._saved_data_from_plain.items():
96
+ Model = apps.get_model(*key.split("."))
97
+ for pk, translations in master_dict.items():
98
+ try:
99
+ obj = Model.objects.get(pk=pk)
100
+ except Model.DoesNotExist:
101
+ continue
102
+ for translation in translations:
103
+ lang = translation.pop("language_code")
104
+ for attr, value in translation.items():
105
+ setattr(obj, "{0}_{1}".format(attr, lang), value)
106
+ obj.save()
107
+
108
+ def _restoreDataToHvad(self, apps, schemaeditor):
109
+ for key, rows in self._saved_data_from_plain.items():
110
+ Model = apps.get_model(*key.split("."))
111
+ table = Model._meta.db_table + "_translation"
112
+ for row in rows:
113
+ with connection.cursor() as cursor:
114
+ print(row)
115
+ cursor.execute(
116
+ "INSERT INTO {0} ({1}) VALUES ({2});".format(
117
+ table,
118
+ ",".join(row.keys()),
119
+ ",".join(
120
+ [
121
+ "'{}'".format(v).replace("'None'", "NULL")
122
+ for v in row.values()
123
+ ]
124
+ ),
125
+ )
126
+ )
@@ -0,0 +1,33 @@
1
+ from django.shortcuts import redirect, render
2
+ from django.urls import path
3
+
4
+ from camomilla import settings
5
+ from django.conf import settings as django_settings
6
+ from .models import Page, UrlRedirect
7
+
8
+
9
+ def fetch(request, *args, **kwargs):
10
+ can_preview = request.user.is_staff or settings.DEBUG
11
+ preview = can_preview and request.GET.get("preview", False)
12
+ append_slash = getattr(django_settings, "APPEND_SLASH", True)
13
+ redirect_obj = UrlRedirect.find_redirect(request)
14
+ if redirect_obj:
15
+ return redirect_obj.redirect()
16
+ if append_slash and not request.path.endswith("/"):
17
+ q_string = request.META.get("QUERY_STRING", "")
18
+ return redirect(request.path + "/" + ("?" + q_string if q_string else ""))
19
+ if "permalink" in kwargs:
20
+ page = Page.get_or_404(
21
+ request, bypass_public_check=preview, bypass_type_check=True
22
+ )
23
+ elif settings.AUTO_CREATE_HOMEPAGE is False:
24
+ page, _ = Page.get_or_404(permalink="/", bypass_type_check=True)
25
+ else:
26
+ page, _ = Page.get_or_create_homepage()
27
+ return render(request, page.get_template_path(request), page.get_context(request))
28
+
29
+
30
+ urlpatterns = [
31
+ path("", fetch, name="camomilla-homepage"),
32
+ path("<path:permalink>", fetch, name="camomilla-permalink"),
33
+ ]
@@ -0,0 +1,13 @@
1
+ from django.db import models
2
+
3
+ from .json import ArrayField, JSONField
4
+
5
+ ORDERING_ACCEPTED_FIELDS = (
6
+ models.BigIntegerField,
7
+ models.IntegerField,
8
+ models.PositiveIntegerField,
9
+ models.PositiveSmallIntegerField,
10
+ models.SmallIntegerField,
11
+ )
12
+
13
+ __all__ = ["JSONField", "ArrayField", "ORDERING_ACCEPTED_FIELDS"]
@@ -1,32 +1,18 @@
1
1
  import json
2
+
2
3
  import django
3
4
  from django.conf import settings
5
+
4
6
  if django.VERSION >= (4, 0):
5
7
  from django.db.models import JSONField as DjangoJSONField
6
8
  else:
7
9
  from django.contrib.postgres.fields import JSONField as DjangoJSONField
10
+
8
11
  from django.contrib.postgres.fields import ArrayField as DjangoArrayField
9
12
  from django.db import models
10
13
 
11
14
 
12
- ORDERING_ACCEPTED_FIELDS = (
13
- models.BigIntegerField,
14
- models.IntegerField,
15
- models.PositiveIntegerField,
16
- models.PositiveSmallIntegerField,
17
- models.SmallIntegerField,
18
- )
19
-
20
-
21
- class JSONField(DjangoJSONField):
22
- pass
23
-
24
-
25
- class ArrayField(DjangoArrayField):
26
- pass
27
-
28
-
29
- if "sqlite" in settings.DATABASES["default"]["ENGINE"]:
15
+ if "sqlite" in settings.DATABASES["default"]["ENGINE"]: # noqa: C901
30
16
 
31
17
  class JSONField(models.Field):
32
18
  def db_type(self, connection):
@@ -70,3 +56,14 @@ if "sqlite" in settings.DATABASES["default"]["ENGINE"]:
70
56
  }
71
57
  )
72
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):
@@ -0,0 +1,3 @@
1
+ from .pages import PageQuerySet
2
+
3
+ __all__ = ["PageQuerySet"]
@@ -0,0 +1,116 @@
1
+ from django.db.models.query import QuerySet
2
+ from django.core.exceptions import ObjectDoesNotExist
3
+ from django.apps import apps
4
+ from django.db import models
5
+ from django.utils import timezone
6
+ from django.db.utils import ProgrammingError, OperationalError
7
+ from typing import Sequence, Tuple
8
+
9
+ URL_NODE_RELATED_NAME = "%(app_label)s_%(class)s"
10
+
11
+
12
+ class PageQuerySet(QuerySet):
13
+
14
+ __UrlNodeModel = None
15
+
16
+ @property
17
+ def UrlNodeModel(self):
18
+ if not self.__UrlNodeModel:
19
+ self.__UrlNodeModel = apps.get_model("camomilla", "UrlNode")
20
+ return self.__UrlNodeModel
21
+
22
+ def get_permalink_kwargs(self, kwargs):
23
+ return list(
24
+ set(kwargs.keys()).intersection(
25
+ set(self.UrlNodeModel.LANG_PERMALINK_FIELDS + ["permalink"])
26
+ )
27
+ )
28
+
29
+ def get(self, *args, **kwargs):
30
+ permalink_args = self.get_permalink_kwargs(kwargs)
31
+ if len(permalink_args):
32
+ try:
33
+ node = self.UrlNodeModel.objects.get(
34
+ **{arg: kwargs.pop(arg) for arg in permalink_args}
35
+ )
36
+ kwargs["url_node"] = node
37
+ except ObjectDoesNotExist:
38
+ raise self.model.DoesNotExist(
39
+ "%s matching query does not exist." % self.model._meta.object_name
40
+ )
41
+ return super(PageQuerySet, self).get(*args, **kwargs)
42
+
43
+
44
+ class UrlNodeManager(models.Manager):
45
+ @property
46
+ def related_names(self):
47
+ self._related_names = getattr(
48
+ self,
49
+ "_related_names",
50
+ super().get_queryset().values_list("related_name", flat=True).distinct(),
51
+ )
52
+ return self._related_names
53
+
54
+ def _annotate_fields(
55
+ self,
56
+ qs: models.QuerySet,
57
+ field_names: Sequence[Tuple[str, models.Field, models.Value]],
58
+ ):
59
+ for field_name, output_field, default in field_names:
60
+ whens = [
61
+ models.When(
62
+ related_name=related_name,
63
+ then=models.F("__".join([related_name, field_name])),
64
+ )
65
+ for related_name in self.related_names
66
+ ]
67
+ qs = qs.annotate(
68
+ **{
69
+ field_name: models.Case(
70
+ *whens, output_field=output_field, default=default
71
+ )
72
+ }
73
+ )
74
+ return self._annotate_is_public(qs)
75
+
76
+ def _annotate_is_public(self, qs: models.QuerySet):
77
+ return qs.annotate(
78
+ is_public=models.Case(
79
+ models.When(status="PUB", then=True),
80
+ models.When(
81
+ status="PLA", publication_date__lte=timezone.now(), then=True
82
+ ),
83
+ default=False,
84
+ output_field=models.BooleanField(default=False),
85
+ )
86
+ )
87
+
88
+ def get_queryset(self):
89
+ try:
90
+ return self._annotate_fields(
91
+ super().get_queryset(),
92
+ [
93
+ (
94
+ "indexable",
95
+ models.BooleanField(),
96
+ models.Value(None, models.BooleanField()),
97
+ ),
98
+ (
99
+ "status",
100
+ models.CharField(),
101
+ models.Value("DRF", models.CharField()),
102
+ ),
103
+ (
104
+ "publication_date",
105
+ models.DateTimeField(),
106
+ models.Value(timezone.now(), models.DateTimeField()),
107
+ ),
108
+ (
109
+ "date_updated_at",
110
+ models.DateTimeField(),
111
+ models.Value(timezone.now(), models.DateTimeField()),
112
+ ),
113
+ ],
114
+ )
115
+ except (ProgrammingError, OperationalError):
116
+ return super().get_queryset()
camomilla/model_api.py ADDED
@@ -0,0 +1,86 @@
1
+ from rest_framework import routers
2
+
3
+ from django.urls import path, include
4
+ from camomilla.views.base import BaseModelViewset
5
+ from camomilla.serializers.base import BaseModelSerializer
6
+
7
+ router = routers.DefaultRouter()
8
+ urlpatterns = []
9
+
10
+
11
+ def register(
12
+ base_serializer=BaseModelSerializer,
13
+ base_viewset=BaseModelViewset,
14
+ serializer_meta={},
15
+ viewset_attrs={},
16
+ filters=None,
17
+ ):
18
+ """
19
+ Register a model to the API.
20
+ :param base_serializer: The base serializer to use for the model.
21
+ :param base_viewset: The base viewset to use for the model.
22
+ :param serializer_meta: The meta class to use for the serializer.
23
+ :param viewset_attrs: The attributes to add to the viewset.
24
+ :param filters: The filters to apply to the queryset.
25
+ :return: The model.
26
+ """
27
+
28
+ def inner(model):
29
+ global urlpatterns
30
+ base_meta = {
31
+ "model": model,
32
+ "fields": "__all__",
33
+ }
34
+ if "exclude" in serializer_meta:
35
+ base_meta.pop("fields")
36
+ serializer = type(
37
+ f"{model.__name__}Serializer",
38
+ (base_serializer,),
39
+ {
40
+ "Meta": type(
41
+ "Meta",
42
+ (),
43
+ {
44
+ **base_meta,
45
+ **serializer_meta,
46
+ },
47
+ )
48
+ },
49
+ )
50
+
51
+ def get_queryset(self, *args, **kwargs):
52
+ qs = super(base_viewset, self).get_queryset(*args, **kwargs)
53
+ return qs if filters is None else qs.filter(**filters)
54
+
55
+ viewset = type(
56
+ f"{model.__name__}ViewSet",
57
+ (base_viewset,),
58
+ {
59
+ "queryset": model.objects.all(),
60
+ "model": model,
61
+ "get_queryset": get_queryset,
62
+ "serializer_class": serializer,
63
+ **viewset_attrs,
64
+ },
65
+ )
66
+
67
+ model_path = "".join(
68
+ [
69
+ (
70
+ "-" + character.lower()
71
+ if character.isupper() and index > 0
72
+ else character
73
+ )
74
+ for index, character in enumerate(model.__name__)
75
+ ]
76
+ ).lstrip("-")
77
+
78
+ router.register(
79
+ f"{model_path.replace(' ', '_').lower().lower()}",
80
+ viewset,
81
+ f"{model.__name__.lower()}_api",
82
+ )
83
+ urlpatterns = [path("", include(router.urls))]
84
+ return model
85
+
86
+ return inner
@@ -1,6 +1,5 @@
1
- from .article import *
2
- from .category import *
3
- from .content import *
4
- from .media import *
5
- from .page import *
6
- from .tag import *
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,62 +1,44 @@
1
- from django.conf import settings
1
+ from django.conf import settings as dj_settings
2
2
  from django.db import models
3
3
 
4
- from django.utils.translation import gettext_lazy as _
4
+ from camomilla.models.page import AbstractPage
5
+ from camomilla import settings
5
6
 
6
- from hvad.models import TranslatedFields
7
7
 
8
- from .mixins import SeoMixin, MetaMixin
9
-
10
-
11
- CONTENT_STATUS = (
12
- ("PUB", _("Published")),
13
- ("DRF", _("Draft")),
14
- ("TRS", _("Trash")),
15
- ("PLA", _("Planned")),
16
- )
17
-
18
-
19
- class BaseArticle(SeoMixin, MetaMixin):
20
-
21
- seo_attr = "permalink"
22
-
23
- identifier = models.CharField(max_length=200, unique=True)
24
- translations = TranslatedFields(
25
- content=models.TextField(default=""),
26
- permalink=models.SlugField(max_length=200, blank=False),
27
- )
8
+ class AbstractArticle(AbstractPage):
9
+ content = models.TextField(default="")
28
10
  author = models.ForeignKey(
29
- settings.AUTH_USER_MODEL, blank=True, null=True, on_delete=models.SET_NULL
30
- )
31
- status = models.CharField(
32
- max_length=3,
33
- choices=CONTENT_STATUS,
34
- default="DRF",
11
+ dj_settings.AUTH_USER_MODEL, blank=True, null=True, on_delete=models.SET_NULL
35
12
  )
36
13
  highlight_image = models.ForeignKey(
37
- "camomilla.Media", blank=True, null=True, on_delete=models.SET_NULL
14
+ "camomilla.Media",
15
+ blank=True,
16
+ null=True,
17
+ on_delete=models.SET_NULL,
18
+ related_name="%(app_label)s_%(class)s_highlight_images",
38
19
  )
39
- date = models.DateTimeField(auto_now=True)
40
- pubblication_date = models.DateTimeField(null=True, blank=True)
41
- tags = models.ManyToManyField("camomilla.Tag", blank=True)
42
- categories = models.ManyToManyField("camomilla.Category", blank=True)
43
- ordering = models.PositiveIntegerField(default=0, blank=False, null=False)
20
+ tags = models.ManyToManyField("Tag", blank=True)
44
21
 
45
22
  class Meta:
46
23
  abstract = True
47
- unique_together = [("permalink", "language_code")]
48
24
  ordering = ["ordering"]
49
25
 
50
- def save(self, *args, **kwargs):
51
- import uuid
52
26
 
53
- if not self.identifier:
54
- self.identifier = "{0}".format(str(uuid.uuid4()))
55
- super(BaseArticle, self).save(*args, **kwargs)
27
+ class Article(AbstractArticle):
28
+ class PageMeta:
29
+ default_template = settings.ARTICLE_DEFAULT_TEMPLATE
30
+ inject_context_func = settings.ARTICLE_INJECT_CONTEXT_FUNC
31
+
32
+
33
+ class AbstractTag(models.Model):
34
+ name = models.CharField(max_length=200, unique=True)
35
+
36
+ class Meta:
37
+ abstract = True
56
38
 
57
39
  def __str__(self):
58
- return self.title
40
+ return "(%s) %s" % (self.__class__.__name__, self.name)
59
41
 
60
42
 
61
- class Article(BaseArticle):
62
- translations = TranslatedFields()
43
+ class Tag(AbstractTag):
44
+ pass
@@ -1,20 +1,11 @@
1
1
  from django.db import models
2
-
3
2
  from django.urls import reverse
4
-
5
- from hvad.models import TranslatableModel, TranslatedFields
6
-
7
3
  from djsuperadmin.mixins import DjSuperAdminMixin
8
4
 
9
5
 
10
- class BaseContent(DjSuperAdminMixin, TranslatableModel):
11
- identifier = models.CharField(max_length=200)
12
- translations = TranslatedFields(
13
- title=models.CharField(max_length=200),
14
- subtitle=models.CharField(max_length=200, blank=True, null=True, default=""),
15
- permalink=models.CharField(max_length=200, blank=False, null=True),
16
- content=models.TextField(default=""),
17
- )
6
+ class AbstractContent(DjSuperAdminMixin, models.Model):
7
+ identifier = models.TextField()
8
+ content = models.TextField(default="")
18
9
  page = models.ForeignKey(
19
10
  "camomilla.Page",
20
11
  blank=False,
@@ -33,11 +24,13 @@ class BaseContent(DjSuperAdminMixin, TranslatableModel):
33
24
 
34
25
  class Meta:
35
26
  abstract = True
36
- unique_together = [("page", "identifier")]
27
+ unique_together = ["identifier", "page"]
37
28
 
38
29
  def __str__(self):
30
+ if len(self.identifier) > 40:
31
+ return "%s..." % self.identifier[:40]
39
32
  return self.identifier
40
33
 
41
34
 
42
- class Content(BaseContent):
43
- translations = TranslatedFields()
35
+ class Content(AbstractContent):
36
+ pass