Replace ckeditor with tinymce (#30)

* Replace ckeditor with tinymce due to deprecation
* Remove any ckeditor dependency from old migrations
   Disable alters, replace create with plain models.TextField
* Reformat files
* Add more hardening in image_upload
This commit is contained in:
2024-02-17 23:05:18 +01:00
committed by GitHub
parent 4428b8c11d
commit 19eb70c492
19 changed files with 242 additions and 59 deletions

View File

@@ -1,6 +1,7 @@
# Generated by Django 4.2.5 on 2023-10-01 20:16
import ckeditor_uploader.fields
# ckeditor removal
# import ckeditor_uploader.fields
from django.db import migrations, models
import django.db.models.deletion
import uuid
@@ -47,7 +48,8 @@ class Migration(migrations.Migration):
("ISBN", models.CharField(max_length=13, unique=True)),
("publication_year", models.SmallIntegerField(blank=True, null=True)),
("purchase_date", models.DateField(blank=True, null=True)),
("notes", ckeditor_uploader.fields.RichTextUploadingField(blank=True)),
# ("notes", ckeditor_uploader.fields.RichTextUploadingField(blank=True)),
("notes", models.TextField(blank=True)),
("creation_time", models.DateTimeField(auto_now_add=True)),
("updated_time", models.DateTimeField(auto_now=True)),
("authors", models.ManyToManyField(to="bookshelf.author")),

View File

@@ -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),
),
]

View File

@@ -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)

View File

@@ -1,6 +1,7 @@
# Generated by Django 4.1 on 2022-08-23 15:54
import ckeditor_uploader.fields
# ckeditor removal
# import ckeditor_uploader.fields
from django.db import migrations
@@ -11,9 +12,9 @@ class Migration(migrations.Migration):
]
operations = [
migrations.AlterField(
model_name="consist",
name="notes",
field=ckeditor_uploader.fields.RichTextUploadingField(blank=True),
),
# migrations.AlterField(
# model_name="consist",
# name="notes",
# field=ckeditor_uploader.fields.RichTextUploadingField(blank=True),
# ),
]

View File

@@ -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),
),
]

View File

@@ -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)

View File

@@ -1,7 +1,8 @@
# Generated by Django 4.1 on 2022-08-23 15:54
import ckeditor.fields
import ckeditor_uploader.fields
# ckeditor dependency removal
# import ckeditor.fields
# import ckeditor_uploader.fields
from django.db import migrations
@@ -12,24 +13,24 @@ class Migration(migrations.Migration):
]
operations = [
migrations.AlterField(
model_name="flatpage",
name="content",
field=ckeditor_uploader.fields.RichTextUploadingField(),
),
migrations.AlterField(
model_name="siteconfiguration",
name="about",
field=ckeditor.fields.RichTextField(blank=True),
),
migrations.AlterField(
model_name="siteconfiguration",
name="footer",
field=ckeditor.fields.RichTextField(blank=True),
),
migrations.AlterField(
model_name="siteconfiguration",
name="footer_extended",
field=ckeditor.fields.RichTextField(blank=True),
),
# migrations.AlterField(
# model_name="flatpage",
# name="content",
# field=ckeditor_uploader.fields.RichTextUploadingField(),
# ),
# migrations.AlterField(
# model_name="siteconfiguration",
# name="about",
# field=ckeditor.fields.RichTextField(blank=True),
# ),
# migrations.AlterField(
# model_name="siteconfiguration",
# name="footer",
# field=ckeditor.fields.RichTextField(blank=True),
# ),
# migrations.AlterField(
# model_name="siteconfiguration",
# name="footer_extended",
# field=ckeditor.fields.RichTextField(blank=True),
# ),
]

View File

@@ -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),
),
]

View File

@@ -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)

View File

@@ -1,4 +1,4 @@
from ram.utils import git_suffix
__version__ = "0.9.6"
__version__ = "0.10.0"
__version__ += git_suffix(__file__)

View File

@@ -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",

View File

@@ -13,6 +13,7 @@ Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.apps import apps
from django.conf import settings
from django.shortcuts import redirect
@@ -20,9 +21,12 @@ 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")),

62
ram/ram/views.py Normal file
View File

@@ -0,0 +1,62 @@
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 (
HttpResponseBadRequest,
HttpResponseForbidden,
JsonResponse,
)
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:
return HttpResponseBadRequest()
today = datetime.date.today()
container = (
"uploads",
today.strftime("%Y"),
today.strftime("%m"),
today.strftime("%d"),
)
dir_path = os.path.join(settings.MEDIA_ROOT, *(p for p in container))
file_path = os.path.normpath(os.path.join(dir_path, file_name))
# even if we apply slugify to the file name, add more hardening
# to avoid any path transversal risk
if not file_path.startswith(str(settings.MEDIA_ROOT)):
return HttpResponseBadRequest()
Path(dir_path).mkdir(parents=True, exist_ok=True)
with open(file_path, "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
),
}
)

View File

@@ -1,6 +1,7 @@
# Generated by Django 4.1 on 2022-08-23 15:54
import ckeditor_uploader.fields
# ckeditor removal
# import ckeditor_uploader.fields
from django.db import migrations
@@ -11,9 +12,9 @@ class Migration(migrations.Migration):
]
operations = [
migrations.AlterField(
model_name="rollingstock",
name="notes",
field=ckeditor_uploader.fields.RichTextUploadingField(blank=True),
),
# migrations.AlterField(
# model_name="rollingstock",
# name="notes",
# field=ckeditor_uploader.fields.RichTextUploadingField(blank=True),
# ),
]

View File

@@ -1,6 +1,7 @@
# Generated by Django 4.1 on 2022-08-27 12:43
import ckeditor_uploader.fields
# ckeditor removal
# import ckeditor_uploader.fields
from django.db import migrations, models
import django.db.models.deletion
@@ -25,7 +26,8 @@ class Migration(migrations.Migration):
),
),
("date", models.DateField()),
("log", ckeditor_uploader.fields.RichTextUploadingField()),
# ("log", ckeditor_uploader.fields.RichTextUploadingField()),
("log", models.TextField()),
("private", models.BooleanField(default=False)),
("creation_time", models.DateTimeField(auto_now_add=True)),
("updated_time", models.DateTimeField(auto_now=True)),

View File

@@ -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(),
),
]

View File

@@ -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)