4 Commits

Author SHA1 Message Date
e7d34ce8e0 Remove unused args in upload_image 2024-02-17 23:06:41 +01:00
19eb70c492 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
2024-02-17 23:05:18 +01:00
4428b8c11d Fix a RuntimeWarning introduced in Django 5 (#29) 2024-01-20 22:08:10 +01:00
8400a5acd3 Add a sample background to sample_data 2023-11-12 15:30:13 +01:00
24 changed files with 388 additions and 70 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,121 @@
# Generated by Django 5.0.1 on 2024-01-20 21:02
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("bookshelf", "0010_alter_bookimage_image"),
]
operations = [
migrations.AlterField(
model_name="book",
name="language",
field=models.CharField(
choices=[
("af", "Afrikaans"),
("ar", "Arabic"),
("ar-dz", "Algerian Arabic"),
("ast", "Asturian"),
("az", "Azerbaijani"),
("bg", "Bulgarian"),
("be", "Belarusian"),
("bn", "Bengali"),
("br", "Breton"),
("bs", "Bosnian"),
("ca", "Catalan"),
("ckb", "Central Kurdish (Sorani)"),
("cs", "Czech"),
("cy", "Welsh"),
("da", "Danish"),
("de", "German"),
("dsb", "Lower Sorbian"),
("el", "Greek"),
("en", "English"),
("en-au", "Australian English"),
("en-gb", "British English"),
("eo", "Esperanto"),
("es", "Spanish"),
("es-ar", "Argentinian Spanish"),
("es-co", "Colombian Spanish"),
("es-mx", "Mexican Spanish"),
("es-ni", "Nicaraguan Spanish"),
("es-ve", "Venezuelan Spanish"),
("et", "Estonian"),
("eu", "Basque"),
("fa", "Persian"),
("fi", "Finnish"),
("fr", "French"),
("fy", "Frisian"),
("ga", "Irish"),
("gd", "Scottish Gaelic"),
("gl", "Galician"),
("he", "Hebrew"),
("hi", "Hindi"),
("hr", "Croatian"),
("hsb", "Upper Sorbian"),
("hu", "Hungarian"),
("hy", "Armenian"),
("ia", "Interlingua"),
("id", "Indonesian"),
("ig", "Igbo"),
("io", "Ido"),
("is", "Icelandic"),
("it", "Italian"),
("ja", "Japanese"),
("ka", "Georgian"),
("kab", "Kabyle"),
("kk", "Kazakh"),
("km", "Khmer"),
("kn", "Kannada"),
("ko", "Korean"),
("ky", "Kyrgyz"),
("lb", "Luxembourgish"),
("lt", "Lithuanian"),
("lv", "Latvian"),
("mk", "Macedonian"),
("ml", "Malayalam"),
("mn", "Mongolian"),
("mr", "Marathi"),
("ms", "Malay"),
("my", "Burmese"),
("nb", "Norwegian Bokmål"),
("ne", "Nepali"),
("nl", "Dutch"),
("nn", "Norwegian Nynorsk"),
("os", "Ossetic"),
("pa", "Punjabi"),
("pl", "Polish"),
("pt", "Portuguese"),
("pt-br", "Brazilian Portuguese"),
("ro", "Romanian"),
("ru", "Russian"),
("sk", "Slovak"),
("sl", "Slovenian"),
("sq", "Albanian"),
("sr", "Serbian"),
("sr-latn", "Serbian Latin"),
("sv", "Swedish"),
("sw", "Swahili"),
("ta", "Tamil"),
("te", "Telugu"),
("tg", "Tajik"),
("th", "Thai"),
("tk", "Turkmen"),
("tr", "Turkish"),
("tt", "Tatar"),
("udm", "Udmurt"),
("ug", "Uyghur"),
("uk", "Ukrainian"),
("ur", "Urdu"),
("uz", "Uzbek"),
("vi", "Vietnamese"),
("zh-hans", "Simplified Chinese"),
("zh-hant", "Traditional Chinese"),
],
default="en",
max_length=7,
),
),
]

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

@@ -6,6 +6,7 @@ from portal.models import SiteConfiguration, Flatpage
@admin.register(SiteConfiguration)
class SiteConfigurationAdmin(SingletonModelAdmin):
readonly_fields = ("site_name",)
fieldsets = (
(
None,

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,16 @@
# Generated by Django 5.0.1 on 2024-01-20 21:02
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("portal", "0015_siteconfiguration_use_cdn"),
]
operations = [
migrations.RemoveField(
model_name="siteconfiguration",
name="site_name",
),
]

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

@@ -1,23 +1,20 @@
import django
from django.db import models
from django.conf import settings
from django.urls import reverse
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
class SiteConfiguration(SingletonModel):
site_name = models.CharField(
max_length=256, default="Railroad Assets Manager"
)
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)],
@@ -32,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)
@@ -44,6 +41,9 @@ class SiteConfiguration(SingletonModel):
def __str__(self):
return "Site Configuration"
def site_name(self):
return settings.SITE_NAME
def version(self):
return app_version
@@ -55,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.5"
__version__ = "0.10.0"
__version__ += git_suffix(__file__)

View File

@@ -1,10 +1,4 @@
from django.contrib import admin
from django.db.utils import OperationalError, ProgrammingError
from portal.utils import get_site_conf
from django.conf import settings
try:
site_name = get_site_conf().site_name
except (OperationalError, ProgrammingError):
site_name = "Train Assets Manager"
admin.site.site_header = site_name
admin.site.site_header = settings.SITE_NAME

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,13 +141,31 @@ 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",
"XX": "None",
}
SITE_NAME = "Railroad Assets Manger"
# Image used on cards without a custom image uploaded.
# The file must be placed in the root of the 'static' folder
DEFAULT_CARD_IMAGE = "coming_soon.svg"

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

View File

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

BIN
sample_data/background.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB