schon/blog/models.py
Egor fureunoir Gorbunov 06290c0278 Features: 1) Add dynamic static page generation in StaticPagesSitemap by integrating active blog posts marked as static pages; 2) Introduce SitemapLanguageMixin to handle language-based URL generation across sitemaps; 3) Add is_static_page field to Post model for designating posts as static pages;
Fixes: 1) Correct router naming in `blog/urls.py` from `payment_router` to `blog_router` for clarity;

Extra: 1) Refactor obsolete `StaticPagesSitemap.PAGES` structure with a dynamic `items` method; 2) Create placeholder 404 URLs for non-existent slugs; 3) Update and simplify docstring for `Post` class, replacing inline details with a concise translation-aware docstring.
2025-10-20 22:56:11 +03:00

130 lines
4.8 KiB
Python

from django.db.models import CASCADE, CharField, FileField, ForeignKey, ManyToManyField, BooleanField
from django.utils.translation import gettext_lazy as _
from django_extensions.db.fields import AutoSlugField
from markdown.extensions.toc import TocExtension
from markdown_field import MarkdownField
from core.abstract import NiceModel
class Post(NiceModel): # type: ignore [django-manager-missing]
__doc__ = _(
"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="vibes_auth.User", 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: MarkdownField = MarkdownField(
"content",
extensions=[
TocExtension(toc_depth=3),
"pymdownx.arithmatex",
"pymdownx.b64",
"pymdownx.betterem",
"pymdownx.blocks.admonition",
"pymdownx.blocks.caption",
"pymdownx.blocks.definition",
"pymdownx.blocks.details",
"pymdownx.blocks.html",
"pymdownx.blocks.tab",
"pymdownx.caret",
"pymdownx.critic",
"pymdownx.emoji",
"pymdownx.escapeall",
"pymdownx.extra",
"pymdownx.fancylists",
"pymdownx.highlight",
"pymdownx.inlinehilite",
"pymdownx.keys",
"pymdownx.magiclink",
"pymdownx.mark",
"pymdownx.pathconverter",
"pymdownx.progressbar",
"pymdownx.saneheaders",
"pymdownx.smartsymbols",
"pymdownx.snippets",
"pymdownx.striphtml",
"pymdownx.superfences",
"pymdownx.tasklist",
"pymdownx.tilde",
],
blank=True,
null=True,
)
file = FileField(upload_to="posts/", blank=True, null=True)
slug = AutoSlugField(populate_from="title", allow_unicode=True, unique=True, editable=False)
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}"
class Meta:
verbose_name = _("post")
verbose_name_plural = _("posts")
def save(self, **kwargs):
if self.file:
raise ValueError(_("markdown files are not supported yet - use markdown content instead"))
if not any([self.file, self.content]) or all([self.file, self.content]):
raise ValueError(_("a markdown file or markdown content must be provided - mutually exclusive"))
super().save(**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")