from django.db import models from django.utils.translation import gettext_lazy as _ from utils import generate_slug_for_model class ArticleCategory(models.Model): title = models.CharField(max_length=255, verbose_name=_('title')) slug = models.SlugField(allow_unicode=True, unique=True, verbose_name=_('slug')) status = models.BooleanField(default=True, verbose_name=_('status')) order = models.PositiveIntegerField(default=0, verbose_name=_('order')) created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('created at')) updated_at = models.DateTimeField(auto_now=True, verbose_name=_('updated at')) def __str__(self): return self.title def save(self, *args, **kwargs): if not self.slug: self.slug = generate_slug_for_model(ArticleCategory, self.title) super().save(*args, **kwargs) class Meta: verbose_name = _('Article Category') verbose_name_plural = _('Article Categories') ordering = ['order'] class ArticleCollection(models.Model): class DisplayPosition(models.TextChoices): PINNED = 'pinned', _('Pinned') MIDDLE = 'middle', _('Middle Section') title = models.CharField(max_length=255, help_text="This title will not be displayed anywhere") slug = models.SlugField(max_length=255, unique=True) summary = models.CharField(max_length=512, null=True, blank=True, help_text=_('could be null')) pin_top = models.BooleanField(_('pin top'), default=True) thumbnail = models.ImageField(upload_to='article/collection/', null=True, blank=True, help_text=_('image allowed')) order = models.IntegerField(default=0, verbose_name=_('order')) status = models.BooleanField(default=True, verbose_name=_('status')) display_position = models.CharField( max_length=20, choices=DisplayPosition.choices, default=DisplayPosition.PINNED, verbose_name=_('Display Position') ) created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('created at')) updated_at = models.DateTimeField(auto_now=True, verbose_name=_('updated at')) articles = models.ManyToManyField( 'Article', through='ArticleInCollection', related_name='related_collections_article', verbose_name=_('articles'), ) def __str__(self): return f'Collection #{self.id}/{self.title}' def save(self, *args, **kwargs): if not self.slug: self.slug = generate_slug_for_model(ArticleCollection, self.title) super().save(*args, **kwargs) class Meta: verbose_name = _('Article Collection') verbose_name_plural = _('Articles Collections') class PinnedArticleCollection(ArticleCollection): class Meta: proxy = True verbose_name = _('Pinned Collection (Top Section)') verbose_name_plural = _('Pinned Collections (Top Section)') class MiddleArticleCollection(ArticleCollection): class Meta: proxy = True verbose_name = _('Regular Collection (Middle Section)') verbose_name_plural = _('Regular Collections (Middle Section)') class Article(models.Model): title = models.CharField(max_length=255, null=True) slug = models.SlugField(allow_unicode=True, unique=True) thumbnail = models.ImageField(upload_to='article_thumbnails/', null=True, blank=True, help_text=_('image allowed')) description = models.TextField(null=True) content = models.TextField(null=True) article_file = models.FileField(upload_to='article/files/', null=True, blank=True, help_text=_('PDF or other document files')) categories = models.ManyToManyField(ArticleCategory, related_name='articles', verbose_name=_('categories'), blank=True) collections = models.ManyToManyField( ArticleCollection, through='ArticleInCollection', related_name='related_articles', verbose_name=_('collections'), blank=True ) download_count = models.PositiveBigIntegerField(default=0, verbose_name=_('view count')) view_count = models.PositiveBigIntegerField(default=0, verbose_name=_('view count')) status = models.BooleanField(default=True, verbose_name=_('status')) created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('created at')) updated_at = models.DateTimeField(auto_now=True, verbose_name=_('updated at')) def __str__(self): return self.title def increment_view_count(self): self.view_count += 1 self.save(update_fields=['view_count']) return self.view_count def save(self, *args, **kwargs): if not self.slug: self.slug = generate_slug_for_model(Article, self.title) super().save(*args, **kwargs) class Meta: verbose_name = _('Article') verbose_name_plural = _('Articles') class ArticleInCollection(models.Model): collection = models.ForeignKey( ArticleCollection, on_delete=models.CASCADE, related_name='collection_articles', verbose_name=_('collection') ) article = models.ForeignKey( Article, on_delete=models.CASCADE, related_name='article_collections', verbose_name=_('article') ) order = models.PositiveIntegerField(default=0, verbose_name=_('order')) created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('created at')) updated_at = models.DateTimeField(auto_now=True, verbose_name=_('updated at')) def __str__(self): return f"{self.collection.title} - {self.article.title}" class Meta: verbose_name = _('Article in Collection') verbose_name_plural = _('Articles in Collections') ordering = ['order'] unique_together = ['collection', 'article'] class ArticleContent(models.Model): """ Model for structured content sections within an article """ article = models.ForeignKey( Article, on_delete=models.CASCADE, related_name='contents', verbose_name=_('article') ) title = models.CharField(max_length=255, verbose_name=_('title')) content = models.TextField(verbose_name=_('content'), blank=True) priority = models.PositiveIntegerField(default=0, verbose_name=_('priority')) status = models.BooleanField(default=True, verbose_name=_('status')) created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('created at')) updated_at = models.DateTimeField(auto_now=True, verbose_name=_('updated at')) class Meta: verbose_name = _('Article Content') verbose_name_plural = _('Article Contents') ordering = ['priority'] def __str__(self): return f"{self.article.title} - {self.title}" class ContentPart(models.Model): """ Model for content parts - each part can have multiple text sections """ article_content = models.ForeignKey( ArticleContent, on_delete=models.CASCADE, related_name='parts', verbose_name=_('article content') ) order = models.PositiveIntegerField(default=0, verbose_name=_('order')) created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('created at')) updated_at = models.DateTimeField(auto_now=True, verbose_name=_('updated at')) class Meta: verbose_name = _('Content Part') verbose_name_plural = _('Content Parts') ordering = ['order'] def __str__(self): return f"{self.article_content.title} - Part {self.order}" class TextSection(models.Model): """ Model for bilingual text sections (Arabic text and translation) within a content part """ content_part = models.ForeignKey( ContentPart, on_delete=models.CASCADE, related_name='text_sections', verbose_name=_('content part') ) arabic_text = models.TextField(verbose_name=_('Arabic text')) translation = models.TextField(verbose_name=_('Translation')) order = models.PositiveIntegerField(default=0, verbose_name=_('order')) created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('created at')) updated_at = models.DateTimeField(auto_now=True, verbose_name=_('updated at')) class Meta: verbose_name = _('Text Section') verbose_name_plural = _('Text Sections') ordering = ['order'] def __str__(self): return f"{self.content_part} - Section {self.order}"