From 6c9e12ef095885f898b177a878f5fd964f75f828 Mon Sep 17 00:00:00 2001 From: Julian Dehm Date: Tue, 19 Sep 2023 15:22:11 +0200 Subject: [PATCH] replace django-ckedtitor with django-ckeditor-5 --- adhocracy4/ckeditor/fields.py | 17 +----- .../icons/collapsibleitem.png | Bin 419 -> 0 bytes .../icons/hidpi/collapsibleitem.png | Bin 991 -> 0 bytes .../static/ckeditor_collapsible/plugin.js | 44 --------------- adhocracy4/ckeditor/storage.py | 12 ++++ adhocracy4/projects/admin.py | 6 +- .../migrations/0040_auto_20230919_0952.py | 31 +++++++++++ .../migrations/0041_ckeditor5_iframes.py | 52 ++++++++++++++++++ adhocracy4/projects/models.py | 6 +- pyproject.toml | 2 +- requirements/base.txt | 2 + .../migrations/0006_alter_idea_description.py | 20 +++++++ tests/apps/ideas/models.py | 4 +- ...5_alter_moderatorfeedback_feedback_text.py | 18 ++++++ tests/apps/moderatorfeedback/models.py | 4 +- tests/ckeditor/test_ckeditor_fields.py | 12 ---- 16 files changed, 148 insertions(+), 82 deletions(-) delete mode 100644 adhocracy4/ckeditor/static/ckeditor_collapsible/icons/collapsibleitem.png delete mode 100644 adhocracy4/ckeditor/static/ckeditor_collapsible/icons/hidpi/collapsibleitem.png delete mode 100644 adhocracy4/ckeditor/static/ckeditor_collapsible/plugin.js create mode 100644 adhocracy4/ckeditor/storage.py create mode 100644 adhocracy4/projects/migrations/0040_auto_20230919_0952.py create mode 100644 adhocracy4/projects/migrations/0041_ckeditor5_iframes.py create mode 100644 tests/apps/ideas/migrations/0006_alter_idea_description.py create mode 100644 tests/apps/moderatorfeedback/migrations/0005_alter_moderatorfeedback_feedback_text.py delete mode 100644 tests/ckeditor/test_ckeditor_fields.py diff --git a/adhocracy4/ckeditor/fields.py b/adhocracy4/ckeditor/fields.py index 9bcbb1246..cdb63c43c 100644 --- a/adhocracy4/ckeditor/fields.py +++ b/adhocracy4/ckeditor/fields.py @@ -1,23 +1,10 @@ from ckeditor.fields import RichTextField from ckeditor_uploader.fields import RichTextUploadingField -_extra_plugins = ["collapsibleItem", "embed", "embedbase"] -_external_plugin_resources = [ - ( - "collapsibleItem", - "/static/ckeditor_collapsible/", - "plugin.js", - ) -] - +# FIXME: remove these fields / file class RichTextCollapsibleMixin: - def __init__(self, *args, **kwargs): - kwargs["extra_plugins"] = kwargs.get("extra_plugins", []) + _extra_plugins - kwargs["external_plugin_resources"] = ( - kwargs.get("external_plugin_resources", []) + _external_plugin_resources - ) - super().__init__(*args, **kwargs) + pass class RichTextCollapsibleField(RichTextCollapsibleMixin, RichTextField): diff --git a/adhocracy4/ckeditor/static/ckeditor_collapsible/icons/collapsibleitem.png b/adhocracy4/ckeditor/static/ckeditor_collapsible/icons/collapsibleitem.png deleted file mode 100644 index 18014ffa3efa6682eb45bb400b80f6f997dc1911..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 419 zcmV;U0bKrxP)U3`nVFg0I5;>M2^s*^ z3^o8cv@I+wrpCp^F#-)?07g5C0Ziz&LjlOYIZ*bs6DLk&fBEv|e?&wCgQlh?c4r{F z00|^;a&j8L75@K^Zoq%6fe11Sh?${ssUVFYF%S*Icny%1m1RZ_gRHD9xtN$3CV~d= z^71k$DJe0isj0yg)YR0>1~GzzgBh?HfD)u%zkUUW!N-pu;bK7cXAr%9{dxuk1qFsD zPo6OR{reZium7Pm19D(t16UH%eK(4%IwL?YoL0|-k?OFRbj2|PDfl1Hg` zY!aCOlgR{bx0{QGTa^wj2HhTu2rpG63*ZEbCZ&CN}ipP$#&fT2>jsZfl1S~8&^?LMFHc`la&>@6Ul&oj^^;_p1MI{c_SVz1{?oS{bHSTduDCdGQqZjc`5wJwAK%A|m zDXPVQn~10;f1E>nMO@}JKvx6g!%~R|b@7Re#>dC+(Y%@m91aHy z!-a(f-G)Ta6QDK~)124qeV9xprQzY>`>NU(7Z(RZq0ps~k&#Dxdwb0ImX;PkfdZB3 z_4W0#4bea~!WP%}XUQ6bvd*^$VYsi`UG=;(lni3u1Q8Ujg@Dptda#Yme2 ztX3gw`fXoi0aFaTiKf~nv-HCF%t N002ovPDHLkV1kb)%T53Q diff --git a/adhocracy4/ckeditor/static/ckeditor_collapsible/plugin.js b/adhocracy4/ckeditor/static/ckeditor_collapsible/plugin.js deleted file mode 100644 index e6bd2cf77..000000000 --- a/adhocracy4/ckeditor/static/ckeditor_collapsible/plugin.js +++ /dev/null @@ -1,44 +0,0 @@ -// CKEditor collapsibleItem plugin based on https://github.com/pkerspe/ckeditor-bootstrap-collapsibleItem -/* globals CKEDITOR django */ - -CKEDITOR.dtd.$editable.a = 1 - -CKEDITOR.plugins.add('collapsibleItem', { - requires: 'widget', - icons: 'collapsibleitem', - hidpi: true, - init: function (editor) { - editor.widgets.add('collapsibleItem', { - button: django.gettext('Insert Collapsible Item'), - template: '
' + - '
' + django.gettext('Title') + '
' + - '
' + django.gettext('Body text') + '
' + - '
', - editables: { - title: { - selector: '.collapsible-item-title', - allowedContent: 'strong em u' - }, - content: { - selector: '.collapsible-item-body', - allowedContent: 'p;br;span(*)[*];ul;ol;li;strong;em;u;hr;a;a[*];a(*)[*];img(*)[*];' - } - }, - allowedContent: 'div(!collapsible-item*)[*]', - requiredContent: 'div(collapsible-item);', - upcast: function (element) { - return element.name === 'div' && element.hasClass('collapsible-item') - } - }) - }, - - onLoad: function () { - CKEDITOR.addCss( - '.collapsible-item::before {font-size:10px;color:#000;content: "' + django.gettext('Collapsible element') + '"}' + - '.collapsible-item {padding: 8px;margin: 10px;background: #eee;border-radius: 8px;border: 1px solid #ddd;box-shadow: 0 1px 1px #fff inset, 0 -1px 0px #ccc inset;}' + - '.collapsible-item-title, .collapsible-item-body {box-shadow: 0 1px 1px #ddd inset;border: 1px solid #cccccc;border-radius: 5px;background: #fff;}' + - '.collapsible-item-title {margin: 0 0 8px;padding: 5px 8px;}' + - '.collapsible-item-body {padding: 0 8px;}' - ) - } -}) diff --git a/adhocracy4/ckeditor/storage.py b/adhocracy4/ckeditor/storage.py new file mode 100644 index 000000000..70a4fe2f8 --- /dev/null +++ b/adhocracy4/ckeditor/storage.py @@ -0,0 +1,12 @@ +import os +from urllib.parse import urljoin + +from django.conf import settings +from django.core.files.storage import FileSystemStorage + + +class CustomStorage(FileSystemStorage): + """Custom storage to store uploads in a subfolder called uploads""" + + location = os.path.join(settings.MEDIA_ROOT, "uploads") + base_url = urljoin(settings.MEDIA_URL, "uploads/") diff --git a/adhocracy4/projects/admin.py b/adhocracy4/projects/admin.py index b5f96a7f7..4413e3e5b 100644 --- a/adhocracy4/projects/admin.py +++ b/adhocracy4/projects/admin.py @@ -1,7 +1,7 @@ -from ckeditor_uploader.widgets import CKEditorUploadingWidget from django import forms from django.contrib import admin from django.utils.translation import gettext_lazy as _ +from django_ckeditor_5.widgets import CKEditor5Widget from . import models @@ -37,10 +37,10 @@ class ProjectAdminForm(forms.ModelForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.fields["information"].widget = CKEditorUploadingWidget( + self.fields["information"].widget = CKEditor5Widget( config_name="collapsible-image-editor" ) - self.fields["result"].widget = CKEditorUploadingWidget( + self.fields["result"].widget = CKEditor5Widget( config_name="collapsible-image-editor" ) diff --git a/adhocracy4/projects/migrations/0040_auto_20230919_0952.py b/adhocracy4/projects/migrations/0040_auto_20230919_0952.py new file mode 100644 index 000000000..3e2326935 --- /dev/null +++ b/adhocracy4/projects/migrations/0040_auto_20230919_0952.py @@ -0,0 +1,31 @@ +# Generated by Django 3.2.20 on 2023-09-19 09:52 + +from django.db import migrations +import django_ckeditor_5.fields + + +class Migration(migrations.Migration): + dependencies = [ + ("a4projects", "0039_add_alt_text_to_field"), + ] + + operations = [ + migrations.AlterField( + model_name="project", + name="information", + field=django_ckeditor_5.fields.CKEditor5Field( + blank=True, + help_text="This description should tell participants what the goal of the project is, how the project’s participation will look like. It will be always visible in the „Info“ tab on your project’s page.", + verbose_name="Description of your project", + ), + ), + migrations.AlterField( + model_name="project", + name="result", + field=django_ckeditor_5.fields.CKEditor5Field( + blank=True, + help_text="Here you should explain what the expected outcome of the project will be and how you are planning to use the results. If the project is finished you should add a summary of the results.", + verbose_name="Results of your project", + ), + ), + ] diff --git a/adhocracy4/projects/migrations/0041_ckeditor5_iframes.py b/adhocracy4/projects/migrations/0041_ckeditor5_iframes.py new file mode 100644 index 000000000..ebed56fe1 --- /dev/null +++ b/adhocracy4/projects/migrations/0041_ckeditor5_iframes.py @@ -0,0 +1,52 @@ +# Generated by Django 3.2.20 on 2023-11-16 11:35 + +from bs4 import BeautifulSoup +from django.db import migrations + + +def replace_iframe_with_figur(apps, schema_editor): + template = ( + '
' + ) + Project = apps.get_model("a4projects", "Project") + informations = 0 + results = 0 + for project in Project.objects.all(): + soup = BeautifulSoup(project.information, "html.parser") + iframes = soup.findAll("iframe") + changed = False + for iframe in iframes: + figure = BeautifulSoup( + template.format(url=iframe.attrs["src"]), "html.parser" + ) + iframe.replaceWith(figure) + informations += 1 + if iframes: + project.information = soup.prettify(formatter="html") + changed = True + soup = BeautifulSoup(project.result, "html.parser") + iframes = soup.findAll("iframe") + for iframe in iframes: + figure = BeautifulSoup( + template.format(url=iframe.attrs["src"]), "html.parser" + ) + iframe.replaceWith(figure) + results += 1 + if iframes: + project.result = soup.prettify(formatter="html") + changed = True + if changed: + project.save() + + +class Migration(migrations.Migration): + dependencies = [ + ("a4projects", "0040_auto_20230919_0952"), + ] + + operations = [ + migrations.RunPython( + replace_iframe_with_figur, + ), + ] diff --git a/adhocracy4/projects/models.py b/adhocracy4/projects/models.py index 7324228b3..6fbf92c11 100644 --- a/adhocracy4/projects/models.py +++ b/adhocracy4/projects/models.py @@ -9,11 +9,11 @@ from django.utils import timezone from django.utils.functional import cached_property from django.utils.translation import gettext_lazy as _ +from django_ckeditor_5.fields import CKEditor5Field from django_enumfield.enum import EnumField from adhocracy4 import transforms as html_transforms from adhocracy4.administrative_districts.models import AdministrativeDistrict -from adhocracy4.ckeditor.fields import RichTextCollapsibleUploadingField from adhocracy4.images import fields from adhocracy4.maps.fields import PointField from adhocracy4.models import base @@ -194,7 +194,7 @@ class Project( "in max. 250 chars." ), ) - information = RichTextCollapsibleUploadingField( + information = CKEditor5Field( blank=True, config_name="collapsible-image-editor", verbose_name=_("Description of your project"), @@ -205,7 +205,7 @@ class Project( "in the „Info“ tab on your project’s page." ), ) - result = RichTextCollapsibleUploadingField( + result = CKEditor5Field( blank=True, config_name="collapsible-image-editor", verbose_name=_("Results of your project"), diff --git a/pyproject.toml b/pyproject.toml index 853bfc912..8f714a11e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ dependencies = [ "django-allauth", "django-autoslug", "django-background-tasks", - "django-ckeditor", + "django-ckeditor-5", "django-enumfield", "django-filter", "django-multiselectfield", diff --git a/requirements/base.txt b/requirements/base.txt index 7c2fd7a50..970e7aa17 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1,8 +1,10 @@ +beautifulsoup4==4.12.2 bleach[css]==6.0.0 Django==3.2.20 django-allauth==0.54.0 git+https://github.com/liqd/django-autoslug.git@liqd2212#egg=django-autoslug django-background-tasks==1.2.5 +https://github.com/liqd/django-ckeditor-5/releases/download/v0.2.11-liqd/django_ckeditor_5-0.2.11-py3-none-any.whl django-ckeditor==6.6.1 django-enumfield==3.1 django-filter==22.1 diff --git a/tests/apps/ideas/migrations/0006_alter_idea_description.py b/tests/apps/ideas/migrations/0006_alter_idea_description.py new file mode 100644 index 000000000..368ecacf7 --- /dev/null +++ b/tests/apps/ideas/migrations/0006_alter_idea_description.py @@ -0,0 +1,20 @@ +# Generated by Django 3.2.20 on 2023-09-19 13:10 + +from django.db import migrations +import django_ckeditor_5.fields + + +class Migration(migrations.Migration): + dependencies = [ + ("a4test_ideas", "0005_idea_is_bool_test"), + ] + + operations = [ + migrations.AlterField( + model_name="idea", + name="description", + field=django_ckeditor_5.fields.CKEditor5Field( + blank=True, verbose_name="Description" + ), + ), + ] diff --git a/tests/apps/ideas/models.py b/tests/apps/ideas/models.py index 18f84fe97..6eb1aec76 100644 --- a/tests/apps/ideas/models.py +++ b/tests/apps/ideas/models.py @@ -1,6 +1,6 @@ -from ckeditor.fields import RichTextField from django.contrib.contenttypes.fields import GenericRelation from django.db import models +from django_ckeditor_5.fields import CKEditor5Field from adhocracy4.categories.fields import CategoryField from adhocracy4.comments.models import Comment @@ -19,7 +19,7 @@ class IdeaQuerySet(RateableQuerySet, CommentableQuerySet): class Idea(Item): name = models.CharField(max_length=120, default="Can i haz cheezburger, pls?") - description = RichTextField(verbose_name="Description", blank=True) + description = CKEditor5Field(verbose_name="Description", blank=True) moderator_status = models.CharField(max_length=256, blank=True) moderator_feedback_text = models.OneToOneField( ModeratorFeedback, diff --git a/tests/apps/moderatorfeedback/migrations/0005_alter_moderatorfeedback_feedback_text.py b/tests/apps/moderatorfeedback/migrations/0005_alter_moderatorfeedback_feedback_text.py new file mode 100644 index 000000000..b55aa799c --- /dev/null +++ b/tests/apps/moderatorfeedback/migrations/0005_alter_moderatorfeedback_feedback_text.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.20 on 2023-09-19 13:10 + +from django.db import migrations +import django_ckeditor_5.fields + + +class Migration(migrations.Migration): + dependencies = [ + ("moderatorfeedback", "0004_verbose_name_created_modified"), + ] + + operations = [ + migrations.AlterField( + model_name="moderatorfeedback", + name="feedback_text", + field=django_ckeditor_5.fields.CKEditor5Field(blank=True), + ), + ] diff --git a/tests/apps/moderatorfeedback/models.py b/tests/apps/moderatorfeedback/models.py index 3469b1b42..c4206f735 100644 --- a/tests/apps/moderatorfeedback/models.py +++ b/tests/apps/moderatorfeedback/models.py @@ -1,9 +1,9 @@ -from ckeditor.fields import RichTextField +from django_ckeditor_5.fields import CKEditor5Field from adhocracy4.models.base import UserGeneratedContentModel class ModeratorFeedback(UserGeneratedContentModel): - feedback_text = RichTextField( + feedback_text = CKEditor5Field( blank=True, ) diff --git a/tests/ckeditor/test_ckeditor_fields.py b/tests/ckeditor/test_ckeditor_fields.py deleted file mode 100644 index c62c43841..000000000 --- a/tests/ckeditor/test_ckeditor_fields.py +++ /dev/null @@ -1,12 +0,0 @@ -import pytest - - -@pytest.mark.django_db -def test_ckeditor_collapsible_field(project): - ckeditor_field = project._meta.get_field("information") - assert "collapsibleItem" in ckeditor_field.extra_plugins - assert ( - "collapsibleItem", - "/static/ckeditor_collapsible/", - "plugin.js", - ) in ckeditor_field.external_plugin_resources