from django.db.models import CASCADE, 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 core.abstract import NiceModel class Post(NiceModel): """ Represents a blog post model extending NiceModel. 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. This model can be used in a blogging platform to manage posts created by users. Attributes: is_publicly_visible (bool): Specifies whether the post is visible to the public. author (ForeignKey): A reference to the user who authored the post. title (CharField): The title of the post, must be unique and non-empty. content (MarkdownField): The content of the post written in markdown format. file (FileField): An optional file attachment for the post. slug (AutoSlugField): A unique, automatically generated slug based on the title. tags (ManyToManyField): Tags associated with the post for categorization. """ is_publicly_visible = True author: ForeignKey = ForeignKey( to="vibes_auth.User", on_delete=CASCADE, blank=False, null=False, related_name="posts" ) title: CharField = 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 = FileField(upload_to="posts/", blank=True, null=True) slug: AutoSlugField = AutoSlugField(populate_from="title", allow_unicode=True, unique=True, editable=False) tags: ManyToManyField = ManyToManyField(to="blog.PostTag", blank=True, related_name="posts") 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 tag. It is a required field with a maximum length of 255 characters. name (CharField): A user-friendly, unique display name for the post 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 tag_name: CharField = CharField( blank=False, null=False, max_length=255, help_text=_("internal tag identifier for the post tag"), verbose_name=_("tag name"), ) name: CharField = 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")