from django.db import models from django.utils.translation import gettext_lazy as _ from utils import generate_slug_for_model, generate_language_slugs from dj_language.models import Language from unfold.contrib.forms.widgets import ArrayWidget from dj_language.field import LanguageField class Blog(models.Model): """ Blog model with title, thumbnail, slogan, summary, views count and timestamps """ title = models.JSONField(default=list, null=False, blank=False, verbose_name=_('Title')) # [{"title": "", "language_code": "en"},{"title": "", "language_code": "fa"},...] thumbnail = models.ImageField( upload_to='blog/thumbnails/%Y/%m/', verbose_name=_('Thumbnail'), help_text=_('Blog thumbnail image') ) slogan = models.JSONField(default=list, null=False, blank=False, verbose_name=_('Slogan')) summary = models.JSONField(default=list, null=True, blank=True, verbose_name=_('Summary')) views_count = models.PositiveIntegerField( default=0, verbose_name=_('Views Count'), help_text=_('Number of times this blog was viewed') ) slug = models.JSONField(default=list, null=True, blank=True, verbose_name=_('Slug'), help_text=_('URL slug for the blog')) 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: ordering = ['-created_at'] verbose_name = _('Blog') verbose_name_plural = _('Blogs') def __str__(self): text = self._extract_text_from_json(self.title) if text: return text return f"Blog #{self.pk}" if self.pk else "Blog" @staticmethod def _extract_text_from_json(value): if not value: return "" # cases: list of dicts, list of strings, dict mapping, plain string if isinstance(value, list): for item in value: if isinstance(item, dict): text = item.get('title') or item.get('value') or item.get('text') if text: return str(text) else: if item: return str(item) return "" if isinstance(value, dict): # Prefer common language codes if present for lang in ("fa", "en", "ru"): if lang in value and value[lang]: v = value[lang] if isinstance(v, dict): return str(v.get('title') or v.get('value') or v.get('text') or "") return str(v) # Fallback to first non-empty value for v in value.values(): if isinstance(v, dict): txt = v.get('title') or v.get('value') or v.get('text') if txt: return str(txt) elif v: return str(v) return "" if isinstance(value, (str, int, float)): return str(value) return "" def increment_view_count(self): """Increment the view count by 1""" self.views_count += 1 self.save(update_fields=['views_count']) return self.views_count def get_seo_for_language(self, language_code): try: seo_field_object = self.seos.filter(language__code=language_code).first() if seo_field_object: return { "title": seo_field_object.title, "description": seo_field_object.description, } return None except Exception: return None def get_blog_filed(self, lang, blog_field): try: if isinstance(blog_field, list) and blog_field: for tr in blog_field: if isinstance(tr, dict) and tr.get('language_code') == lang: return tr.get('title') or tr.get('text') or tr.get('value') return None except Exception as exp: print(f'---> Error in get_blog_filed: {exp}') return None def save(self, *args, **kwargs): try: self.slug = generate_language_slugs(self.title) except Exception: self.slug = [] super().save(*args, **kwargs) class BlogContent(models.Model): """ BlogContent model related to Blog with title, content, slug, image, order and timestamps """ blog = models.ForeignKey( Blog, on_delete=models.CASCADE, related_name='contents', verbose_name=_('Blog') ) title = models.JSONField(default=list, null=True, blank=True, verbose_name=_('Content Title'), help_text=_('Title of this content section')) content = models.JSONField(default=list, null=True, blank=True, verbose_name=_('Content'), help_text=_('The main content text')) slug = models.JSONField(default=list, null=True, blank=True, verbose_name=_('Slug'), help_text=_('URL slug for this content (optional)')) image = models.ImageField( upload_to='blog/content_images/%Y/%m/', null=True, blank=True, verbose_name=_('Image'), help_text=_('Optional image for this content section') ) order = models.PositiveIntegerField( default=0, verbose_name=_('Order'), help_text=_('Order of this content within the blog') ) 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: ordering = ['order', 'created_at'] verbose_name = _('Blog Content') verbose_name_plural = _('Blog Contents') # unique_together = ['blog', 'order'] def __str__(self): title_text = Blog._extract_text_from_json(self.title) if title_text: return title_text blog_text = Blog._extract_text_from_json(self.blog.title) if self.blog_id else "Blog" return f"{blog_text} - Content #{self.pk or ''}".strip() def save(self, *args, **kwargs): try: self.slug = generate_language_slugs(self.slug) except Exception: pass super().save(*args, **kwargs) class BlogSeo(models.Model): blog = models.ForeignKey(Blog, on_delete=models.CASCADE, related_name='seos', verbose_name=_('Blog')) title = models.CharField( _('SEO Title'), max_length=140, null=True, blank=True, help_text=_('maximum length of page title is 70 characters and minimum length is 30'), ) keywords = models.CharField( _('Keywords'), max_length=700, null=True, blank=True, help_text=_('keywords in the content that make it possible for people to find the site via search engines') ) description = models.CharField( _('Description'), max_length=170, null=True, blank=True, help_text=_('describes and summarizes the contents of the page for the benefit of users and search engines'), ) language = LanguageField(null=True, verbose_name=_('Language')) class Meta: verbose_name = _('Blog SEO') verbose_name_plural = _('Blog SEOs') def __str__(self): lang = getattr(self.language, 'code', None) if self.language else None return f"SEO({lang or '-'}) - {self.title or ''}"