From 77d978fecbae43943218ce4786e726ff4b2c51e8 Mon Sep 17 00:00:00 2001 From: Egor fureunoir Gorbunov Date: Thu, 19 Jun 2025 13:15:04 +0300 Subject: [PATCH] Features: 1) Integrated django-summernote for rich text editing; 2) Added Summernote fields in PostAdmin with configuration; 3) Configured summernote settings in a separated module; Fixes: 1) Adjusted modeltranslation admin import to use external jQuery variant; 2) Corrected optional flags for package dependencies in poetry.lock; Extra: 1) Removed custom PostAdminForm in favor of SummernoteModelAdmin; 2) Enhanced formatting consistency in api_urls and admin files. --- blog/admin.py | 18 +++--------------- blog/forms.py | 13 ------------- core/admin.py | 4 +++- evibes/api_urls.py | 30 +++++++++++++++++++++++++----- evibes/settings/__init__.py | 1 + evibes/settings/base.py | 1 + evibes/settings/summernote.py | 20 ++++++++++++++++++++ poetry.lock | 23 ++++++++++++++++++++--- pyproject.toml | 1 + 9 files changed, 74 insertions(+), 37 deletions(-) delete mode 100644 blog/forms.py create mode 100644 evibes/settings/summernote.py diff --git a/blog/admin.py b/blog/admin.py index 59096c9e..ce9d122d 100644 --- a/blog/admin.py +++ b/blog/admin.py @@ -1,30 +1,19 @@ from django.contrib import admin -from django.utils.safestring import mark_safe -from django.utils.translation import gettext_lazy as _ +from django_summernote.admin import SummernoteModelAdmin -from .forms import PostAdminForm from .models import Post, PostTag @admin.register(Post) -class PostAdmin(admin.ModelAdmin): - def preview_html(self, obj): - html = obj.content.html or "{}".format(_("(no content yet)")) - # noinspection DjangoSafeString - return mark_safe(html) - - preview_html.short_description = _("rendered HTML") # type: ignore - - form = PostAdminForm +class PostAdmin(admin.ModelAdmin, SummernoteModelAdmin): list_display = ("title", "author", "slug", "created", "modified") list_filter = ("author", "tags", "created", "modified") search_fields = ("title", "content") filter_horizontal = ("tags",) date_hierarchy = "created" autocomplete_fields = ("author", "tags") + summernote_fields = ("content",) - readonly_fields = ("preview_html",) - # noinspection PyUnresolvedReferences fieldsets = ( ( None, @@ -33,7 +22,6 @@ class PostAdmin(admin.ModelAdmin): "author", "title", "content", - "preview_html", "file", "tags", ) diff --git a/blog/forms.py b/blog/forms.py deleted file mode 100644 index 83177963..00000000 --- a/blog/forms.py +++ /dev/null @@ -1,13 +0,0 @@ -from django import forms - -from blog.models import Post -from blog.widgets import MarkdownEditorWidget - - -class PostAdminForm(forms.ModelForm): - class Meta: - model = Post - fields = ("author", "content", "tags", "title") - widgets = { - "content": MarkdownEditorWidget(attrs={"style": "min-height: 500px;"}), - } diff --git a/core/admin.py b/core/admin.py index 805be6e7..f6d717e4 100644 --- a/core/admin.py +++ b/core/admin.py @@ -7,7 +7,9 @@ from django.contrib.admin import ModelAdmin, TabularInline from django.contrib.gis.admin import GISModelAdmin from django.urls import path from django.utils.translation import gettext_lazy as _ -from modeltranslation.admin import TabbedTranslationAdmin +from modeltranslation.admin import ( + TabbedExternalJqueryTranslationAdmin as TabbedTranslationAdmin, +) from mptt.admin import DraggableMPTTAdmin from evibes.settings import CONSTANCE_CONFIG diff --git a/evibes/api_urls.py b/evibes/api_urls.py index e66fdde7..12ed6d05 100644 --- a/evibes/api_urls.py +++ b/evibes/api_urls.py @@ -7,20 +7,40 @@ from django.views.decorators.csrf import csrf_exempt from drf_spectacular.views import SpectacularAPIView from core.graphene.schema import schema -from core.views import CustomGraphQLView, CustomRedocView, CustomSwaggerView, favicon_view, index +from core.views import ( + CustomGraphQLView, + CustomRedocView, + CustomSwaggerView, + favicon_view, + index, +) from evibes.settings import SPECTACULAR_PLATFORM_SETTINGS urlpatterns = [ path(r"health/", include("health_check.urls")), path("prometheus/", include("django_prometheus.urls")), - path(r"graphql/", csrf_exempt(CustomGraphQLView.as_view(graphiql=True, schema=schema))), + path( + r"graphql/", + csrf_exempt(CustomGraphQLView.as_view(graphiql=True, schema=schema)), + ), path( r"docs/", - SpectacularAPIView.as_view(urlconf="evibes.api_urls", custom_settings=SPECTACULAR_PLATFORM_SETTINGS), + SpectacularAPIView.as_view( + urlconf="evibes.api_urls", custom_settings=SPECTACULAR_PLATFORM_SETTINGS + ), name="schema-platform", ), - path(r"docs/swagger/", CustomSwaggerView.as_view(url_name="schema-platform"), name="swagger-ui-platform"), - path(r"docs/redoc/", CustomRedocView.as_view(url_name="schema-platform"), name="redoc-ui-platform"), + path( + r"docs/swagger/", + CustomSwaggerView.as_view(url_name="schema-platform"), + name="swagger-ui-platform", + ), + path( + r"docs/redoc/", + CustomRedocView.as_view(url_name="schema-platform"), + name="redoc-ui-platform", + ), + path("summernote/", include("django_summernote.urls")), path(r"i18n/", include("django.conf.urls.i18n")), path(r"favicon.ico", favicon_view), path(r"", index), diff --git a/evibes/settings/__init__.py b/evibes/settings/__init__.py index 2c87ce42..37041f6b 100644 --- a/evibes/settings/__init__.py +++ b/evibes/settings/__init__.py @@ -10,3 +10,4 @@ from .emailing import * # noqa: F403 from .extensions import * # noqa: F403 from .graphene import * # noqa: F403 from .logconfig import * # noqa: F403 +from .summernote import * # noqa: F403 diff --git a/evibes/settings/base.py b/evibes/settings/base.py index ebce3659..6f7b9331 100644 --- a/evibes/settings/base.py +++ b/evibes/settings/base.py @@ -122,6 +122,7 @@ INSTALLED_APPS: list[str] = [ "django_celery_results", "django_extensions", "django_redis", + "django_summernote", "widget_tweaks", "mptt", "rest_framework", diff --git a/evibes/settings/summernote.py b/evibes/settings/summernote.py new file mode 100644 index 00000000..3b9631ad --- /dev/null +++ b/evibes/settings/summernote.py @@ -0,0 +1,20 @@ +SUMMERNOTE_THEME = "bs5" +SUMMERNOTE_CONFIG = { + "iframe": False, + "summernote": { + "lang": None, + "toolbar": [ + ["style", ["style"]], + ["font", ["bold", "underline", "clear"]], + ["fontname", ["fontname"]], + ["color", ["color"]], + ["para", ["ul", "ol", "paragraph"]], + ["table", ["table"]], + ["insert", ["link", "picture", "video"]], + ["view", ["fullscreen", "codeview", "help"]], + ], + }, + "attachment_require_authentication": True, + "disable_attachment": False, + "attachment_absolute_uri": True, +} diff --git a/poetry.lock b/poetry.lock index 27da5449..d1edc2e6 100644 --- a/poetry.lock +++ b/poetry.lock @@ -395,7 +395,7 @@ uvloop = ["uvloop (>=0.15.2)"] name = "bleach" version = "6.2.0" description = "An easy safelist-based HTML-sanitizing tool." -optional = true +optional = false python-versions = ">=3.9" files = [ {file = "bleach-6.2.0-py3-none-any.whl", hash = "sha256:117d9c6097a7c3d22fd578fcd8d35ff1e125df6736f554da4e432fdd63f31e5e"}, @@ -1477,6 +1477,23 @@ files = [ django = "*" typing-extensions = "*" +[[package]] +name = "django-summernote" +version = "0.8.20.0" +description = "Summernote plugin for Django" +optional = false +python-versions = "*" +files = [ + {file = "django-summernote-0.8.20.0.tar.gz", hash = "sha256:52e9b12438ed9eac0d77729f758f2aae06e468b5cbce133e24100d58ae4e43a8"}, +] + +[package.dependencies] +bleach = "*" +django = "*" + +[package.extras] +dev = ["django-dummy-plug", "pytest", "pytest-django"] + [[package]] name = "django-timezone-field" version = "7.1" @@ -4955,7 +4972,7 @@ files = [ name = "webencodings" version = "0.5.1" description = "Character encoding aliases for legacy web content" -optional = true +optional = false python-versions = "*" files = [ {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, @@ -5135,4 +5152,4 @@ worker = ["celery", "celery-prometheus-exporter", "django-celery-beat", "django- [metadata] lock-version = "2.0" python-versions = ">=3.12,<3.13" -content-hash = "f0d3ee182729fb4060ac11cd915a5c548203ab3ab4ab0b56e5a97947b3056e43" +content-hash = "db1001cbb7cbb20f5a927eb20c69e706bf6620cdc60e8334bede11439ce12c21" diff --git a/pyproject.toml b/pyproject.toml index 04d774c6..1af94d29 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,6 +37,7 @@ django-redis = "6.0.0" django-ratelimit = "4.1.0" django-storages = "1.14.6" django-stubs = "5.2.1" +django-summernote = "0.8.20.0" django-widget-tweaks = "1.5.0" django-md-field = "0.1.0" djangorestframework = "3.16.0"