From 9cb3fb1d8a8ade3e14da3381e56235cc3fe34d18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniele=20Vigan=C3=B2?= Date: Sat, 17 Feb 2024 17:06:28 +0100 Subject: [PATCH] Replace ckeditor with tinymce due to deprecation --- .../migrations/0012_alter_book_notes.py | 19 ++++++ ram/bookshelf/models.py | 4 +- .../migrations/0010_alter_consist_notes.py | 19 ++++++ ram/consist/models.py | 4 +- ..._alter_siteconfiguration_about_and_more.py | 34 +++++++++++ ram/portal/models.py | 11 ++-- ram/ram/__init__.py | 2 +- ram/ram/settings.py | 23 ++++++-- ram/ram/urls.py | 9 ++- ram/ram/views.py | 59 +++++++++++++++++++ .../0022_alter_rollingstock_notes_and_more.py | 24 ++++++++ ram/roster/models.py | 6 +- requirements.txt | 2 +- 13 files changed, 196 insertions(+), 20 deletions(-) create mode 100644 ram/bookshelf/migrations/0012_alter_book_notes.py create mode 100644 ram/consist/migrations/0010_alter_consist_notes.py create mode 100644 ram/portal/migrations/0017_alter_flatpage_content_alter_siteconfiguration_about_and_more.py create mode 100644 ram/ram/views.py create mode 100644 ram/roster/migrations/0022_alter_rollingstock_notes_and_more.py diff --git a/ram/bookshelf/migrations/0012_alter_book_notes.py b/ram/bookshelf/migrations/0012_alter_book_notes.py new file mode 100644 index 0000000..73b8e08 --- /dev/null +++ b/ram/bookshelf/migrations/0012_alter_book_notes.py @@ -0,0 +1,19 @@ +# Generated by Django 5.0.2 on 2024-02-17 12:19 + +import tinymce.models +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookshelf", "0011_alter_book_language"), + ] + + operations = [ + migrations.AlterField( + model_name="book", + name="notes", + field=tinymce.models.HTMLField(blank=True), + ), + ] diff --git a/ram/bookshelf/models.py b/ram/bookshelf/models.py index 5c025a1..6c2dcf8 100644 --- a/ram/bookshelf/models.py +++ b/ram/bookshelf/models.py @@ -6,7 +6,7 @@ from django.conf import settings from django.urls import reverse from django_countries.fields import CountryField -from ckeditor_uploader.fields import RichTextUploadingField +from tinymce import models as tinymce from metadata.models import Tag from ram.utils import DeduplicatedStorage @@ -56,7 +56,7 @@ class Book(models.Model): tags = models.ManyToManyField( Tag, related_name="bookshelf", blank=True ) - notes = RichTextUploadingField(blank=True) + notes = tinymce.HTMLField(blank=True) creation_time = models.DateTimeField(auto_now_add=True) updated_time = models.DateTimeField(auto_now=True) diff --git a/ram/consist/migrations/0010_alter_consist_notes.py b/ram/consist/migrations/0010_alter_consist_notes.py new file mode 100644 index 0000000..39dbfa2 --- /dev/null +++ b/ram/consist/migrations/0010_alter_consist_notes.py @@ -0,0 +1,19 @@ +# Generated by Django 5.0.2 on 2024-02-17 12:19 + +import tinymce.models +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("consist", "0009_alter_consist_image"), + ] + + operations = [ + migrations.AlterField( + model_name="consist", + name="notes", + field=tinymce.models.HTMLField(blank=True), + ), + ] diff --git a/ram/consist/models.py b/ram/consist/models.py index f0e27c0..0ea0a9f 100644 --- a/ram/consist/models.py +++ b/ram/consist/models.py @@ -4,7 +4,7 @@ from uuid import uuid4 from django.db import models from django.urls import reverse -from ckeditor_uploader.fields import RichTextUploadingField +from tinymce import models as tinymce from ram.utils import DeduplicatedStorage from metadata.models import Company, Tag @@ -26,7 +26,7 @@ class Consist(models.Model): null=True, blank=True, ) - notes = RichTextUploadingField(blank=True) + notes = tinymce.HTMLField(blank=True) creation_time = models.DateTimeField(auto_now_add=True) updated_time = models.DateTimeField(auto_now=True) diff --git a/ram/portal/migrations/0017_alter_flatpage_content_alter_siteconfiguration_about_and_more.py b/ram/portal/migrations/0017_alter_flatpage_content_alter_siteconfiguration_about_and_more.py new file mode 100644 index 0000000..8a9e11d --- /dev/null +++ b/ram/portal/migrations/0017_alter_flatpage_content_alter_siteconfiguration_about_and_more.py @@ -0,0 +1,34 @@ +# Generated by Django 5.0.2 on 2024-02-17 12:19 + +import tinymce.models +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("portal", "0016_remove_siteconfiguration_site_name"), + ] + + operations = [ + migrations.AlterField( + model_name="flatpage", + name="content", + field=tinymce.models.HTMLField(), + ), + migrations.AlterField( + model_name="siteconfiguration", + name="about", + field=tinymce.models.HTMLField(blank=True), + ), + migrations.AlterField( + model_name="siteconfiguration", + name="footer", + field=tinymce.models.HTMLField(blank=True), + ), + migrations.AlterField( + model_name="siteconfiguration", + name="footer_extended", + field=tinymce.models.HTMLField(blank=True), + ), + ] diff --git a/ram/portal/models.py b/ram/portal/models.py index 43a34d7..52c2014 100644 --- a/ram/portal/models.py +++ b/ram/portal/models.py @@ -6,8 +6,7 @@ from django.dispatch.dispatcher import receiver from django.utils.safestring import mark_safe from solo.models import SingletonModel -from ckeditor.fields import RichTextField -from ckeditor_uploader.fields import RichTextUploadingField +from tinymce import models as tinymce from ram import __version__ as app_version from ram.utils import slugify @@ -15,7 +14,7 @@ from ram.utils import slugify class SiteConfiguration(SingletonModel): site_author = models.CharField(max_length=256, blank=True) - about = RichTextField(blank=True) + about = tinymce.HTMLField(blank=True) items_per_page = models.CharField( max_length=2, choices=[(str(x * 3), str(x * 3)) for x in range(2, 11)], @@ -30,8 +29,8 @@ class SiteConfiguration(SingletonModel): ], default="type", ) - footer = RichTextField(blank=True) - footer_extended = RichTextField(blank=True) + footer = tinymce.HTMLField(blank=True) + footer_extended = tinymce.HTMLField(blank=True) show_version = models.BooleanField(default=True) use_cdn = models.BooleanField(default=True) extra_head = models.TextField(blank=True) @@ -56,7 +55,7 @@ class Flatpage(models.Model): name = models.CharField(max_length=256, unique=True) path = models.CharField(max_length=256, unique=True) published = models.BooleanField(default=False) - content = RichTextUploadingField() + content = tinymce.HTMLField() creation_time = models.DateTimeField(auto_now_add=True) updated_time = models.DateTimeField(auto_now=True) diff --git a/ram/ram/__init__.py b/ram/ram/__init__.py index 5578c39..3c53cf0 100644 --- a/ram/ram/__init__.py +++ b/ram/ram/__init__.py @@ -1,4 +1,4 @@ from ram.utils import git_suffix -__version__ = "0.9.6" +__version__ = "0.10.0" __version__ += git_suffix(__file__) diff --git a/ram/ram/settings.py b/ram/ram/settings.py index b4122de..9b0a516 100644 --- a/ram/ram/settings.py +++ b/ram/ram/settings.py @@ -44,8 +44,7 @@ INSTALLED_APPS = [ "adminsortable2", "django_countries", "solo", - "ckeditor", - "ckeditor_uploader", + "tinymce", "rest_framework", "ram", "portal", @@ -60,7 +59,7 @@ MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.common.CommonMiddleware", - 'django.middleware.csrf.CsrfViewMiddleware', + "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", @@ -142,7 +141,23 @@ DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" MEDIA_URL = "media/" MEDIA_ROOT = STORAGE_DIR / "media" -CKEDITOR_UPLOAD_PATH = "uploads/" + +TINYMCE_DEFAULT_CONFIG = { + "height": "500px", + "menubar": False, + "plugins": "autolink lists link image charmap preview anchor " + "searchreplace visualblocks code fullscreen insertdatetime media " + "table paste code", + "toolbar": "undo redo | " + "bold italic underline strikethrough removeformat | " + "fontsizeselect formatselect | " + "alignleft aligncenter alignright alignjustify | " + "outdent indent numlist bullist | " + "insertfile image media pageembed template link anchor codesample | " + "charmap | " + "fullscreen preview code", + "images_upload_url": "/tinymce/upload_image", +} COUNTRIES_OVERRIDE = { "EU": "Europe", diff --git a/ram/ram/urls.py b/ram/ram/urls.py index ae2b14a..f449e7b 100644 --- a/ram/ram/urls.py +++ b/ram/ram/urls.py @@ -20,9 +20,16 @@ from django.conf.urls.static import static from django.contrib import admin from django.urls import include, path +from ram.views import UploadImage + urlpatterns = [ path("", lambda r: redirect("portal/")), - path("ckeditor/", include("ckeditor_uploader.urls")), + path('tinymce/', include('tinymce.urls')), + path( + "tinymce/upload_image", + UploadImage.as_view(), + name="upload_image" + ), path("portal/", include("portal.urls")), path("admin/", admin.site.urls), path("api/v1/consist/", include("consist.urls")), diff --git a/ram/ram/views.py b/ram/ram/views.py new file mode 100644 index 0000000..ef28587 --- /dev/null +++ b/ram/ram/views.py @@ -0,0 +1,59 @@ +import os +import datetime +import posixpath + +from pathlib import Path +from PIL import Image, UnidentifiedImageError + +from django.views import View +from django.conf import settings +from django.http import ( + JsonResponse, HttpResponseForbidden, HttpResponse +) +from django.utils.text import slugify as slugify +from django.utils.decorators import method_decorator +from django.views.decorators.csrf import csrf_exempt + + +@method_decorator(csrf_exempt, name='dispatch') +class UploadImage(View): + def post(self, request, application=None, model=None): + if not request.user.is_authenticated: + raise HttpResponseForbidden() + + file_obj = request.FILES['file'] + file_name, file_extension = os.path.splitext(file_obj.name) + file_name = slugify(file_name) + file_extension + + try: + Image.open(file_obj) + except UnidentifiedImageError: + response = HttpResponse("Invalid extension") # FIXME + response.status_code = 400 + return response + + today = datetime.date.today() + container = ( + "uploads", + today.strftime("%Y"), + today.strftime("%m"), + today.strftime("%d"), + ) + + file_path = os.path.join( + settings.MEDIA_ROOT, + *(p for p in container) + ) + Path(file_path).mkdir(parents=True, exist_ok=True) + with open(os.path.join(file_path, file_name), 'wb+') as f: + for chunk in file_obj.chunks(): + f.write(chunk) + + return JsonResponse({ + 'message': 'Image uploaded successfully', + 'location': posixpath.join( + settings.MEDIA_URL, + *(p for p in container), + file_name + ) + }) diff --git a/ram/roster/migrations/0022_alter_rollingstock_notes_and_more.py b/ram/roster/migrations/0022_alter_rollingstock_notes_and_more.py new file mode 100644 index 0000000..e015dc1 --- /dev/null +++ b/ram/roster/migrations/0022_alter_rollingstock_notes_and_more.py @@ -0,0 +1,24 @@ +# Generated by Django 5.0.2 on 2024-02-17 12:19 + +import tinymce.models +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("roster", "0021_alter_rollingstockdocument_file_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="rollingstock", + name="notes", + field=tinymce.models.HTMLField(blank=True), + ), + migrations.AlterField( + model_name="rollingstockjournal", + name="log", + field=tinymce.models.HTMLField(), + ), + ] diff --git a/ram/roster/models.py b/ram/roster/models.py index 5ae18d8..80b9f86 100644 --- a/ram/roster/models.py +++ b/ram/roster/models.py @@ -7,7 +7,7 @@ from django.urls import reverse from django.conf import settings from django.dispatch import receiver -from ckeditor_uploader.fields import RichTextUploadingField +from tinymce import models as tinymce from ram.models import Document, Image, PropertyInstance from ram.utils import DeduplicatedStorage @@ -85,7 +85,7 @@ class RollingStock(models.Model): era = models.CharField(max_length=32, blank=True) production_year = models.SmallIntegerField(null=True, blank=True) purchase_date = models.DateField(null=True, blank=True) - notes = RichTextUploadingField(blank=True) + notes = tinymce.HTMLField(blank=True) tags = models.ManyToManyField( Tag, related_name="rolling_stock", blank=True ) @@ -175,7 +175,7 @@ class RollingStockJournal(models.Model): blank=False, ) date = models.DateField() - log = RichTextUploadingField() + log = tinymce.HTMLField() private = models.BooleanField(default=False) creation_time = models.DateTimeField(auto_now_add=True) updated_time = models.DateTimeField(auto_now=True) diff --git a/requirements.txt b/requirements.txt index 3717bce..f749874 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ django-solo django-countries django-health-check django-admin-sortable2 -django-ckeditor +django-tinymce # Optional: # psycopg2-binary # Optional: # pySerial # Required by django-countries and not always installed