Replace WYSIWYG editor with Markdown editor across all relevant models and admin fields. Add utilities for rendering and stripping markdown. Adjust serializers, views, and templates to support markdown content. Introduce `PastedImage` model and upload endpoint for handling inline image uploads in markdown. This change simplifies content formatting while enhancing flexibility with markdown support.
138 lines
4.3 KiB
Python
138 lines
4.3 KiB
Python
from django.conf import settings
|
|
from django.db.models import (
|
|
CASCADE,
|
|
BooleanField,
|
|
CharField,
|
|
FileField,
|
|
ForeignKey,
|
|
ManyToManyField,
|
|
TextField,
|
|
)
|
|
from django.utils.functional import cached_property
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
from engine.core.abstract import NiceModel
|
|
from engine.core.utils.db import TweakedAutoSlugField, unicode_slugify_function
|
|
from engine.core.utils.markdown import strip_markdown
|
|
|
|
|
|
class Post(NiceModel):
|
|
__doc__ = _( # pyright: ignore[reportUnknownVariableType]
|
|
"Represents a blog post model. "
|
|
"The Post class defines the structure and behavior of a blog post. "
|
|
"It includes attributes for author, title, content, optional file attachment, slug, and associated tags. "
|
|
"The class enforces constraints such as requiring either content or a file attachment but not both simultaneously. "
|
|
"It also supports automatic slug generation based on the title."
|
|
)
|
|
|
|
is_publicly_visible = True
|
|
|
|
author = ForeignKey(
|
|
to=settings.AUTH_USER_MODEL,
|
|
on_delete=CASCADE,
|
|
blank=False,
|
|
null=False,
|
|
related_name="posts",
|
|
)
|
|
title = CharField(
|
|
unique=True,
|
|
max_length=128,
|
|
blank=False,
|
|
null=False,
|
|
help_text=_("post title"),
|
|
verbose_name=_("title"),
|
|
)
|
|
content = TextField(
|
|
verbose_name=_("content"),
|
|
help_text=_("post content"),
|
|
blank=True,
|
|
null=True,
|
|
)
|
|
file = FileField(upload_to="posts/", blank=True, null=True)
|
|
slug = TweakedAutoSlugField(
|
|
populate_from="title",
|
|
slugify_function=unicode_slugify_function,
|
|
allow_unicode=True,
|
|
unique=True,
|
|
editable=False,
|
|
max_length=88,
|
|
overwrite=True,
|
|
null=True,
|
|
)
|
|
tags = ManyToManyField(to="blog.PostTag", blank=True, related_name="posts")
|
|
meta_description = CharField(max_length=150, blank=True, null=True)
|
|
is_static_page = BooleanField(
|
|
default=False,
|
|
verbose_name=_("is static page"),
|
|
help_text=_(
|
|
"is this a post for a page with static URL (e.g. `/help/delivery`)?"
|
|
),
|
|
)
|
|
|
|
def __str__(self):
|
|
return f"{self.title} | {self.author.first_name} {self.author.last_name}"
|
|
|
|
@cached_property
|
|
def seo_description(self) -> str:
|
|
return strip_markdown(self.content or "")[:180]
|
|
|
|
class Meta:
|
|
verbose_name = _("post")
|
|
verbose_name_plural = _("posts")
|
|
|
|
def save(self, *args, **kwargs):
|
|
if self.file:
|
|
raise ValueError(
|
|
_("file uploads are not supported yet - use content instead")
|
|
)
|
|
if not any([self.file, self.content]) or all([self.file, self.content]):
|
|
raise ValueError(
|
|
_("a file or content must be provided - mutually exclusive")
|
|
)
|
|
super().save(*args, **kwargs)
|
|
|
|
|
|
class PostTag(NiceModel):
|
|
"""
|
|
Represents a tag associated with a post.
|
|
|
|
The PostTag class is used to define and manage tags that can be assigned
|
|
to posts. These tags include an internal identifier and a user-friendly
|
|
display name. The class supports internationalization for both the internal
|
|
identifier and the display name.
|
|
|
|
Attributes:
|
|
is_publicly_visible (bool): Determines if the tag is visible publicly.
|
|
tag_name (CharField): An internal tag identifier for the post's tag. It is a required
|
|
field with a maximum length of 255 characters.
|
|
name (CharField): A user-friendly, unique display name for the post's tag
|
|
with a maximum length of 255 characters.
|
|
|
|
Meta:
|
|
verbose_name (str): Human-readable singular name of the PostTag model.
|
|
verbose_name_plural (str): Human-readable plural name of the PostTag model.
|
|
"""
|
|
|
|
is_publicly_visible = True
|
|
posts: "Post"
|
|
|
|
tag_name = CharField(
|
|
blank=False,
|
|
null=False,
|
|
max_length=255,
|
|
help_text=_("internal tag identifier for the post tag"),
|
|
verbose_name=_("tag name"),
|
|
)
|
|
name = CharField(
|
|
max_length=255,
|
|
help_text=_("user-friendly name for the post tag"),
|
|
verbose_name=_("tag display name"),
|
|
unique=True,
|
|
)
|
|
|
|
def __str__(self):
|
|
return self.tag_name
|
|
|
|
class Meta:
|
|
verbose_name = _("post tag")
|
|
verbose_name_plural = _("post tags")
|