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 # 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 from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
import uuid import uuid
@@ -47,7 +48,8 @@ class Migration(migrations.Migration):
("ISBN", models.CharField(max_length=13, unique=True)), ("ISBN", models.CharField(max_length=13, unique=True)),
("publication_year", models.SmallIntegerField(blank=True, null=True)), ("publication_year", models.SmallIntegerField(blank=True, null=True)),
("purchase_date", models.DateField(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)), ("creation_time", models.DateTimeField(auto_now_add=True)),
("updated_time", models.DateTimeField(auto_now=True)), ("updated_time", models.DateTimeField(auto_now=True)),
("authors", models.ManyToManyField(to="bookshelf.author")), ("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.urls import reverse
from django_countries.fields import CountryField from django_countries.fields import CountryField
from ckeditor_uploader.fields import RichTextUploadingField from tinymce import models as tinymce
from metadata.models import Tag from metadata.models import Tag
from ram.utils import DeduplicatedStorage from ram.utils import DeduplicatedStorage
@@ -56,7 +56,7 @@ class Book(models.Model):
tags = models.ManyToManyField( tags = models.ManyToManyField(
Tag, related_name="bookshelf", blank=True Tag, related_name="bookshelf", blank=True
) )
notes = RichTextUploadingField(blank=True) notes = tinymce.HTMLField(blank=True)
creation_time = models.DateTimeField(auto_now_add=True) creation_time = models.DateTimeField(auto_now_add=True)
updated_time = models.DateTimeField(auto_now=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 # 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 from django.db import migrations
@@ -11,9 +12,9 @@ class Migration(migrations.Migration):
] ]
operations = [ operations = [
migrations.AlterField( # migrations.AlterField(
model_name="consist", # model_name="consist",
name="notes", # name="notes",
field=ckeditor_uploader.fields.RichTextUploadingField(blank=True), # 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.db import models
from django.urls import reverse from django.urls import reverse
from ckeditor_uploader.fields import RichTextUploadingField from tinymce import models as tinymce
from ram.utils import DeduplicatedStorage from ram.utils import DeduplicatedStorage
from metadata.models import Company, Tag from metadata.models import Company, Tag
@@ -26,7 +26,7 @@ class Consist(models.Model):
null=True, null=True,
blank=True, blank=True,
) )
notes = RichTextUploadingField(blank=True) notes = tinymce.HTMLField(blank=True)
creation_time = models.DateTimeField(auto_now_add=True) creation_time = models.DateTimeField(auto_now_add=True)
updated_time = models.DateTimeField(auto_now=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 # Generated by Django 4.1 on 2022-08-23 15:54
import ckeditor.fields # ckeditor dependency removal
import ckeditor_uploader.fields # import ckeditor.fields
# import ckeditor_uploader.fields
from django.db import migrations from django.db import migrations
@@ -12,24 +13,24 @@ class Migration(migrations.Migration):
] ]
operations = [ operations = [
migrations.AlterField( # migrations.AlterField(
model_name="flatpage", # model_name="flatpage",
name="content", # name="content",
field=ckeditor_uploader.fields.RichTextUploadingField(), # field=ckeditor_uploader.fields.RichTextUploadingField(),
), # ),
migrations.AlterField( # migrations.AlterField(
model_name="siteconfiguration", # model_name="siteconfiguration",
name="about", # name="about",
field=ckeditor.fields.RichTextField(blank=True), # field=ckeditor.fields.RichTextField(blank=True),
), # ),
migrations.AlterField( # migrations.AlterField(
model_name="siteconfiguration", # model_name="siteconfiguration",
name="footer", # name="footer",
field=ckeditor.fields.RichTextField(blank=True), # field=ckeditor.fields.RichTextField(blank=True),
), # ),
migrations.AlterField( # migrations.AlterField(
model_name="siteconfiguration", # model_name="siteconfiguration",
name="footer_extended", # name="footer_extended",
field=ckeditor.fields.RichTextField(blank=True), # 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 django.utils.safestring import mark_safe
from solo.models import SingletonModel from solo.models import SingletonModel
from ckeditor.fields import RichTextField from tinymce import models as tinymce
from ckeditor_uploader.fields import RichTextUploadingField
from ram import __version__ as app_version from ram import __version__ as app_version
from ram.utils import slugify from ram.utils import slugify
@@ -15,7 +14,7 @@ from ram.utils import slugify
class SiteConfiguration(SingletonModel): class SiteConfiguration(SingletonModel):
site_author = models.CharField(max_length=256, blank=True) site_author = models.CharField(max_length=256, blank=True)
about = RichTextField(blank=True) about = tinymce.HTMLField(blank=True)
items_per_page = models.CharField( items_per_page = models.CharField(
max_length=2, max_length=2,
choices=[(str(x * 3), str(x * 3)) for x in range(2, 11)], choices=[(str(x * 3), str(x * 3)) for x in range(2, 11)],
@@ -30,8 +29,8 @@ class SiteConfiguration(SingletonModel):
], ],
default="type", default="type",
) )
footer = RichTextField(blank=True) footer = tinymce.HTMLField(blank=True)
footer_extended = RichTextField(blank=True) footer_extended = tinymce.HTMLField(blank=True)
show_version = models.BooleanField(default=True) show_version = models.BooleanField(default=True)
use_cdn = models.BooleanField(default=True) use_cdn = models.BooleanField(default=True)
extra_head = models.TextField(blank=True) extra_head = models.TextField(blank=True)
@@ -56,7 +55,7 @@ class Flatpage(models.Model):
name = models.CharField(max_length=256, unique=True) name = models.CharField(max_length=256, unique=True)
path = models.CharField(max_length=256, unique=True) path = models.CharField(max_length=256, unique=True)
published = models.BooleanField(default=False) published = models.BooleanField(default=False)
content = RichTextUploadingField() content = tinymce.HTMLField()
creation_time = models.DateTimeField(auto_now_add=True) creation_time = models.DateTimeField(auto_now_add=True)
updated_time = models.DateTimeField(auto_now=True) updated_time = models.DateTimeField(auto_now=True)

View File

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

View File

@@ -44,8 +44,7 @@ INSTALLED_APPS = [
"adminsortable2", "adminsortable2",
"django_countries", "django_countries",
"solo", "solo",
"ckeditor", "tinymce",
"ckeditor_uploader",
"rest_framework", "rest_framework",
"ram", "ram",
"portal", "portal",
@@ -60,7 +59,7 @@ MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware", "django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware", "django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware", "django.middleware.common.CommonMiddleware",
'django.middleware.csrf.CsrfViewMiddleware', "django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware", "django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware",
@@ -142,7 +141,23 @@ DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
MEDIA_URL = "media/" MEDIA_URL = "media/"
MEDIA_ROOT = STORAGE_DIR / "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 = { COUNTRIES_OVERRIDE = {
"EU": "Europe", "EU": "Europe",

View File

@@ -13,6 +13,7 @@ Including another URLconf
1. Import the include() function: from django.urls import include, path 1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
""" """
from django.apps import apps from django.apps import apps
from django.conf import settings from django.conf import settings
from django.shortcuts import redirect from django.shortcuts import redirect
@@ -20,9 +21,12 @@ from django.conf.urls.static import static
from django.contrib import admin from django.contrib import admin
from django.urls import include, path from django.urls import include, path
from ram.views import UploadImage
urlpatterns = [ urlpatterns = [
path("", lambda r: redirect("portal/")), 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("portal/", include("portal.urls")),
path("admin/", admin.site.urls), path("admin/", admin.site.urls),
path("api/v1/consist/", include("consist.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 # 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 from django.db import migrations
@@ -11,9 +12,9 @@ class Migration(migrations.Migration):
] ]
operations = [ operations = [
migrations.AlterField( # migrations.AlterField(
model_name="rollingstock", # model_name="rollingstock",
name="notes", # name="notes",
field=ckeditor_uploader.fields.RichTextUploadingField(blank=True), # field=ckeditor_uploader.fields.RichTextUploadingField(blank=True),
), # ),
] ]

View File

@@ -1,6 +1,7 @@
# Generated by Django 4.1 on 2022-08-27 12:43 # 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 from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
@@ -25,7 +26,8 @@ class Migration(migrations.Migration):
), ),
), ),
("date", models.DateField()), ("date", models.DateField()),
("log", ckeditor_uploader.fields.RichTextUploadingField()), # ("log", ckeditor_uploader.fields.RichTextUploadingField()),
("log", models.TextField()),
("private", models.BooleanField(default=False)), ("private", models.BooleanField(default=False)),
("creation_time", models.DateTimeField(auto_now_add=True)), ("creation_time", models.DateTimeField(auto_now_add=True)),
("updated_time", models.DateTimeField(auto_now=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.conf import settings
from django.dispatch import receiver 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.models import Document, Image, PropertyInstance
from ram.utils import DeduplicatedStorage from ram.utils import DeduplicatedStorage
@@ -85,7 +85,7 @@ class RollingStock(models.Model):
era = models.CharField(max_length=32, blank=True) era = models.CharField(max_length=32, blank=True)
production_year = models.SmallIntegerField(null=True, blank=True) production_year = models.SmallIntegerField(null=True, blank=True)
purchase_date = models.DateField(null=True, blank=True) purchase_date = models.DateField(null=True, blank=True)
notes = RichTextUploadingField(blank=True) notes = tinymce.HTMLField(blank=True)
tags = models.ManyToManyField( tags = models.ManyToManyField(
Tag, related_name="rolling_stock", blank=True Tag, related_name="rolling_stock", blank=True
) )
@@ -175,7 +175,7 @@ class RollingStockJournal(models.Model):
blank=False, blank=False,
) )
date = models.DateField() date = models.DateField()
log = RichTextUploadingField() log = tinymce.HTMLField()
private = models.BooleanField(default=False) private = models.BooleanField(default=False)
creation_time = models.DateTimeField(auto_now_add=True) creation_time = models.DateTimeField(auto_now_add=True)
updated_time = models.DateTimeField(auto_now=True) updated_time = models.DateTimeField(auto_now=True)

View File

@@ -7,7 +7,7 @@ django-solo
django-countries django-countries
django-health-check django-health-check
django-admin-sortable2 django-admin-sortable2
django-ckeditor django-tinymce
# Optional: # psycopg2-binary # Optional: # psycopg2-binary
# Optional: # pySerial # Optional: # pySerial
# Required by django-countries and not always installed # Required by django-countries and not always installed