You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

251 lines
8.9 KiB

from django.db import models
from django.utils.translation import gettext_lazy as _
from django.conf import settings
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'))
embedded_in = models.JSONField(default=list, blank=True)
def __str__(self):
return self.title
@property
def share_link(self):
if self.slug:
return f"{settings.DOVODI_DOMAIN}/articles/{self.slug}"
return None
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)
# Reset embedded_in if text or translation changes
if self.pk:
old_instance = Article.objects.get(pk=self.pk)
if (old_instance.description != self.description or
old_instance.content != self.content):
self.embedded_in = [] # Reset!
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}"