rework prose editor configuration

This commit is contained in:
Tobias Brunner 2025-07-08 16:24:28 +02:00
parent 6b689704b0
commit 166f518db0
No known key found for this signature in database
5 changed files with 46 additions and 15 deletions

View file

@ -2,9 +2,8 @@ from django.db import models
from django.urls import reverse
from django.utils.text import slugify
from django.contrib.auth.models import User
from django_prose_editor.fields import ProseEditorField
from django.utils import timezone
from .base import validate_image_size
from .base import validate_image_size, get_prose_editor_field
from .services import Service
from .providers import CloudProvider, ConsultingPartner
from .images import ImageReference
@ -16,7 +15,7 @@ class Article(ImageReference):
excerpt = models.TextField(
max_length=500, help_text="Brief description of the article"
)
content = ProseEditorField()
content = get_prose_editor_field()
meta_keywords = models.CharField(
max_length=255, blank=True, help_text="SEO keywords separated by commas"
)
@ -57,7 +56,7 @@ class Article(ImageReference):
blank=True,
null=True,
validators=[validate_image_size],
help_text="Optional Open Graph image for social sharing (max 1MB). If not provided, the article's main image will be used."
help_text="Optional Open Graph image for social sharing (max 1MB). If not provided, the article's main image will be used.",
)
# Publishing controls

View file

@ -6,6 +6,38 @@ import mimetypes
import xml.etree.ElementTree as ET
# Centralized ProseEditorField configuration
PROSE_EDITOR_CONFIG = {
"extensions": {
"Bold": True,
"Italic": True,
"Strike": True,
"Underline": True,
"HardBreak": True,
"Heading": {"levels": [1, 2, 3, 4, 5, 6]},
"BulletList": True,
"OrderedList": True,
"Blockquote": True,
"Link": True,
"Table": True,
"History": True,
"HTML": True,
"Typographic": True,
},
"sanitize": True,
}
def get_prose_editor_field(**kwargs):
"""
Returns a ProseEditorField with the standard configuration.
Additional kwargs can be passed to override or add field options.
"""
config = PROSE_EDITOR_CONFIG.copy()
config.update(kwargs)
return ProseEditorField(**config)
def validate_image_size(value, mb=1):
filesize = value.size
if filesize > mb * 1024 * 1024:
@ -88,7 +120,7 @@ class ReusableText(models.Model):
blank=True,
related_name="children",
)
text = ProseEditorField()
text = get_prose_editor_field()
class Meta:
ordering = ["name"]

View file

@ -1,10 +1,10 @@
from django.db import models
from django_prose_editor.fields import ProseEditorField
from .base import get_prose_editor_field
class WebsiteFaq(models.Model):
question = models.CharField(max_length=200)
answer = ProseEditorField()
answer = get_prose_editor_field()
order = models.IntegerField(default=0)
class Meta:

View file

@ -1,16 +1,15 @@
from django.db import models
from django.urls import reverse
from django.utils.text import slugify
from django_prose_editor.fields import ProseEditorField
from .base import validate_image_size
from .base import validate_image_size, get_prose_editor_field
from .images import ImageReference
class CloudProvider(ImageReference):
name = models.CharField(max_length=100)
slug = models.SlugField(unique=True)
description = ProseEditorField()
description = get_prose_editor_field()
website = models.URLField()
linkedin = models.URLField(blank=True)
phone = models.CharField(max_length=25, blank=True, null=True)
@ -45,7 +44,7 @@ class CloudProvider(ImageReference):
class ConsultingPartner(ImageReference):
name = models.CharField(max_length=200)
slug = models.SlugField(unique=True)
description = ProseEditorField()
description = get_prose_editor_field()
website = models.URLField(blank=True)
linkedin = models.URLField(blank=True)
phone = models.CharField(max_length=25, blank=True, null=True)

View file

@ -11,6 +11,7 @@ from .base import (
ManagedServiceProvider,
validate_image_size,
Currency,
get_prose_editor_field,
)
from .providers import CloudProvider
from .images import ImageReference
@ -19,10 +20,10 @@ from .images import ImageReference
class Service(ImageReference):
name = models.CharField(max_length=200)
slug = models.SlugField(max_length=250, unique=True)
description = ProseEditorField()
description = get_prose_editor_field()
tagline = models.TextField(max_length=500, blank=True, null=True)
categories = models.ManyToManyField(Category, related_name="services")
features = ProseEditorField()
features = get_prose_editor_field()
is_featured = models.BooleanField(default=False)
is_coming_soon = models.BooleanField(default=False)
disable_listing = models.BooleanField(default=False)
@ -74,7 +75,7 @@ class ServiceOffering(models.Model):
cloud_provider = models.ForeignKey(
CloudProvider, on_delete=models.CASCADE, related_name="offerings"
)
description = ProseEditorField(blank=True, null=True)
description = get_prose_editor_field(blank=True, null=True)
offer_description = models.ForeignKey(
ReusableText,
on_delete=models.PROTECT,
@ -129,7 +130,7 @@ class PlanPrice(models.Model):
class Plan(models.Model):
name = models.CharField(max_length=100)
description = ProseEditorField(blank=True, null=True)
description = get_prose_editor_field(blank=True, null=True)
plan_description = models.ForeignKey(
ReusableText,
on_delete=models.PROTECT,