from django.conf import settings from django.db.models import ( CASCADE, BooleanField, CharField, FileField, ForeignKey, ManyToManyField, ) 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 engine.core.abstract import NiceModel 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: 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")