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.
 
 

490 lines
18 KiB

from enum import unique
from typing import Optional
from django.db import models
from django.db.models import F, ForeignKey
from django.utils.translation import gettext_lazy as _
from django.conf import settings
from django.utils.text import slugify
from filer.fields.image import FilerImageField
from .reference import BookReference
from utils.slug import generate_smart_slug
class HadisCollection(models.Model):
title = models.JSONField(default = list , verbose_name=_('Title'))
slug = models.SlugField(max_length=255, unique=True, verbose_name=_('slug'), blank=True)
summary = models.JSONField(default = list , verbose_name=_('Summary'))
status = models.BooleanField(default=True, verbose_name=_('status'))
order = models.IntegerField(default=0, verbose_name=_('order'))
thumbnail = FilerImageField(
related_name="+",
on_delete=models.CASCADE,
help_text=_('thumbnail image'),
null=True,
blank=True,
verbose_name=_('thumbnail')
)
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('created at'))
updated_at = models.DateTimeField(auto_now=True, verbose_name=_('updated at'))
def save(self, *args, **kwargs):
if not self.slug:
base_slug = slugify(self.title[0]['text'], allow_unicode=True)
slug = base_slug
counter = 1
while HadisCollection.objects.filter(slug=slug).exclude(pk=self.pk).exists():
slug = f"{base_slug}-{counter}"
counter += 1
self.slug = slug
super().save(*args, **kwargs)
def __str__(self):
return self.title[0]['text']
def get_title(self,lang):
"""
Get title for a specific language
"""
if not self.title or not isinstance(self.title, list):
return None
for tr in self.title:
if isinstance(tr, dict) and tr.get('language_code') == lang:
return tr.get('text', '')
for tr in self.title:
if isinstance(tr, dict) and tr.get('language_code') == 'en':
return tr.get('text', '')
return None
def get_summary(self,lang):
"""
Get translation for a specific language
"""
if not self.summary or not isinstance(self.summary, list):
return None
for tr in self.summary:
if isinstance(tr, dict) and tr.get('language_code') == lang:
return tr.get('text', '')
for tr in self.summary:
if isinstance(tr, dict) and tr.get('language_code') == 'en':
return tr.get('text', '')
return None
class Meta:
verbose_name = _('hadis collection')
verbose_name_plural = _('hadis collections')
ordering = ('order',)
class HadisInCollection(models.Model):
hadis = models.ForeignKey('Hadis', on_delete=models.CASCADE, verbose_name=_('hadis'), related_name='collection_items')
collection = models.ForeignKey(HadisCollection, on_delete=models.CASCADE, verbose_name=_('collection'), related_name='hadis_items')
order = models.IntegerField(default=0, verbose_name=_('order'))
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('created at'))
class Meta:
verbose_name = _('hadis in collection')
verbose_name_plural = _('hadis in collections')
ordering = ('order',)
unique_together = ('hadis', 'collection')
def __str__(self):
return f"{self.collection.title[0]['text']} - {self.hadis.number}"
class HadisTag(models.Model):
title = models.JSONField(default = list , verbose_name=_('Title'))
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 f"{self.title[0]['text']}"
def get_title(self,lang):
"""
Get title for a specific language
"""
if not self.title or not isinstance(self.title, list):
return None
for tr in self.title:
if isinstance(tr, dict) and tr.get('language_code') == lang:
return tr.get('text', '')
for tr in self.title:
if isinstance(tr, dict) and tr.get('language_code') == 'en':
return tr.get('text', '')
return None
class HadisStatus(models.Model):
class ColorChoices(models.TextChoices):
RED = 'red', _('Red')
GREEN = 'green', _('Green')
BLUE = 'blue', _('Blue')
YELLOW = 'yellow', _('Yellow')
ORANGE = 'orange', _('Orange')
PURPLE = 'purple', _('Purple')
GRAY = 'gray', _('Gray')
title = models.JSONField(default = list , verbose_name=_('Title'))
slug= models.SlugField(max_length=255, verbose_name=_('slug'), blank=True,unique = True)
color = models.CharField(max_length=20, choices=ColorChoices.choices, verbose_name=_('color'))
order = models.IntegerField(default=0, verbose_name=_('order'))
def save(self, *args, **kwargs):
if not self.slug:
slug = slugify(self.title[0]['text'])
self.slug = slug
super().save(*args, **kwargs)
def __str__(self):
return self.title[0]['text']
def get_title(self,lang):
"""
Get title for a specific language
"""
if not self.title or not isinstance(self.title, list):
return None
for tr in self.title:
if isinstance(tr, dict) and tr.get('language_code') == lang:
return tr.get('text', '')
for tr in self.title:
if isinstance(tr, dict) and tr.get('language_code') == 'en':
return tr.get('text', '')
return None
class Meta:
verbose_name = _('hadis status')
verbose_name_plural = _('hadis statuses')
ordering = ('order',)
class Hadis(models.Model):
category = models.ForeignKey("hadis.HadisCategory", on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_('category'))
number = models.PositiveIntegerField(verbose_name=_('number'), default=1)
slug = models.SlugField(max_length=255, verbose_name=_('slug'), blank=True,unique=True)
title_narrator = models.JSONField(default = list , verbose_name=_('Title Narrator'))
title = models.JSONField(default = list , verbose_name=_('Title'))
description = models.JSONField(default = list , verbose_name=_('Description'))
text = models.TextField(verbose_name=_('text'))
translation = models.JSONField(verbose_name=_('translation'), default=list)
status = models.BooleanField(default=True, verbose_name=_('visibility'))
hadis_status = models.ForeignKey(HadisStatus, on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_('hadis status'))
hadis_status_text = models.JSONField(default = list , verbose_name=_('Status text'))
address = models.JSONField(default = list , verbose_name=_('Address'))
links = models.JSONField(verbose_name=_('links'), null=True, blank=True, default=dict)
tags = models.ManyToManyField("HadisTag", related_name="hadis_overview", verbose_name=_('tags'), blank=True)
share_link = models.CharField(max_length=255, verbose_name=_('share link'), null=True, blank=True)
explanation = models.JSONField(default = list , verbose_name=_('Explanation'))
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.number} - {self.title[0]['text']}" if self.title else f"Hadis {self.number}"
def save(self, *args, **kwargs):
"""
Override save to automatically generate smart slugs.
- If slug is empty, generates a new one
- Uses the first 8 words from the title (configurable)
- Ensures uniqueness with counters (-1, -2, etc.)
- Max length: 100 characters (configurable)
Examples:
>>> hadis = Hadis.objects.create(
... number=1877,
... title=[
... {
... "text": "Fatwa on Combining Prayers While Traveling and Missing Prayer",
... "language_code": "en"
... }
... ]
... )
>>> print(hadis.slug)
'fatwa-on-combining-prayers-while'
"""
# Generate slug if not already set
if not self.slug and self.title:
# Extract title text
title_text = None
if isinstance(self.title, list) and self.title:
first_item = self.title[0]
if isinstance(first_item, dict):
title_text = first_item.get("text")
# Generate smart slug
if title_text:
self.slug = generate_smart_slug(
text=title_text,
model_class=Hadis,
max_length=100, # ← Adjust max length here
keep_words=8, # ← Limit to 8 words (your requirement)
instance=self,
)
else:
# Fallback if title is empty
self.slug = f"hadis-{self.number or 'unknown'}"
# Call parent save
super().save(*args, **kwargs)
def _get_json_field(self, field_name: str, lang: Optional[str]=None , fallback: str = "en"):
"""
Generic getter for JSONField in our [{text, language_code}] format.
Usage: self._get_json_field('title', 'fa')
"""
if lang is None:
lang = fallback
value = getattr(self, field_name, None)
if not value or not isinstance(value, list):
return None
# 1) exact language
for item in value:
if isinstance(item, dict) and item.get("language_code") == lang:
return item.get("text", "")
# 2) fallback language
if fallback and fallback != lang:
for item in value:
if isinstance(item, dict) and item.get("language_code") == fallback:
return item.get("text", "")
# 3) first available
item = value[0]
print(item)
return item.get("text", "") if isinstance(item, dict) else None
def get_translation(self, lang):
return self._get_json_field("translation" , lang)
def get_title(self,lang):
return self._get_json_field("title" , lang)
def get_description(self, lang):
return self._get_json_field("description" , lang)
def get_hadis_status_text(self, lang):
return self._get_json_field("hadis_status_text" , lang)
def get_address(self, lang):
return self._get_json_field("address" , lang)
def get_explanation(self, lang):
return self._get_json_field("explanation" , lang)
def save(self, *args, **kwargs):
# ساخت share_link قبل از ذخیره
if not self.share_link:
self.share_link = f"{settings.SITE_DOMAIN}/hadis/{self.slug}"
super().save(*args, **kwargs)
class Meta:
indexes = [
# Optimizes: Hadis.objects.filter(status=True).order_by('id')
models.Index(fields=['status', 'id']),
]
verbose_name = _('hadis')
verbose_name_plural = _('hadises')
ordering = ('category', 'number')
class HadisReference(models.Model):
hadis = models.ForeignKey(
Hadis,
on_delete=models.CASCADE,
verbose_name=_('hadis'),
related_name='references'
)
book_reference = models.ForeignKey(
BookReference,
on_delete=models.SET_NULL,
null=True,
blank=True,
verbose_name=_('book reference'),
related_name='hadis_references'
)
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('created at'))
description = models.JSONField(default = list , verbose_name=_('Description'))
class Meta:
indexes = [
# For fetching hadises by book
models.Index(fields=['book_reference']),
]
verbose_name = _('Hadis Reference')
verbose_name_plural = _('Hadis References')
# unique_together = ('hadis', 'book_reference')
def get_description(self,lang):
"""
Get title for a specific language
"""
if not self.description or not isinstance(self.description, list):
return None
for tr in self.description:
if isinstance(tr, dict) and tr.get('language_code') == lang:
return tr.get('text', '')
for tr in self.description:
if isinstance(tr, dict) and tr.get('language_code') == 'en':
return tr.get('text', '')
return None
def __str__(self):
return f'{self.hadis.number}-{self.book_reference.title[0]["text"] if self.book_reference else "No Book Reference"}'
class ReferenceImage(models.Model):
reference = models.ForeignKey(HadisReference,related_name = 'images', verbose_name="Hadis Reference", on_delete=models.CASCADE)
thumbnail = models.ImageField(upload_to='hadis/reference_images/', null=True, blank=True, verbose_name=_('thumbnail'))
priority = models.IntegerField(
default=0,
verbose_name=_("Priority"),
help_text=_("Priority of the image, lower values mean higher priority.")
)
class Meta:
indexes = [
# Speeds up fetching images for a reference in priority order
models.Index(fields=['reference', 'priority']),
]
verbose_name = _('Reference Image')
verbose_name_plural = _('Reference Images')
def __str__(self):
return f'{self.reference.title[0]["text"]}-{self.id}'
def save(self, *args, **kwargs):
if ReferenceImage.objects.filter(reference=self.reference, priority=self.priority).exists():
ReferenceImage.objects.filter(
reference=self.reference,
priority__gte=self.priority
).update(priority=F('priority') + 1)
super().save(*args, **kwargs)
class HadisCorrection(models.Model):
hadis = models.ForeignKey(Hadis, verbose_name=_("hadis correction"), on_delete=models.CASCADE)
title = models.JSONField(default = list , verbose_name=_('Title'))
slug = models.SlugField(max_length=255, verbose_name=_('slug'), blank=True,unique=True)
description =models.JSONField(default = list , verbose_name=_('Description'))
translation = models.JSONField(verbose_name=_("translation"), default=list)
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("created at"))
updated_at = models.DateTimeField(auto_now=True, verbose_name=_("updated at"))
share_link = models.CharField(max_length=255, verbose_name=_('share link'), null=True, blank=True)
class Meta:
verbose_name = _("Hadis Correction")
verbose_name_plural = _("Hadis Corrections")
ordering = ("-created_at",)
def __str__(self):
return f"{self.hadis.number} - {self.title[0]['text']}"
def save(self, *args, **kwargs):
"""
Override save to automatically generate smart slugs.
"""
# Generate slug if not already set
if not self.slug and self.title:
# Extract title text
title_text = None
if isinstance(self.title, list) and self.title:
first_item = self.title[0]
if isinstance(first_item, dict):
title_text = first_item.get("text")
# Generate smart slug
if title_text:
self.slug = generate_smart_slug(
text=title_text,
model_class=HadisCorrection,
max_length=100, # ← Adjust max length here
keep_words=8, # ← Limit to 8 words (your requirement)
instance=self,
)
else:
# Fallback if title is empty
self.slug = f"correction-{self.hadis.slug}-{self.id}"
# Call parent save
if not self.share_link:
self.share_link = f"{settings.SITE_DOMAIN}/hadis/{self.hadis.slug}/corrections/{self.slug}"
super().save(*args, **kwargs)
def get_title(self,lang):
"""
Get title for a specific language
"""
if not self.title or not isinstance(self.title, list):
return None
for tr in self.title:
if isinstance(tr, dict) and tr.get('language_code') == lang:
return tr.get('text', '')
for tr in self.title:
if isinstance(tr, dict) and tr.get('language_code') == 'en':
return tr.get('text', '')
return None
def get_description(self,lang):
"""
Get title for a specific language
"""
if not self.description or not isinstance(self.description, list):
return None
for tr in self.description:
if isinstance(tr, dict) and tr.get('language_code') == lang:
return tr.get('text', '')
for tr in self.description:
if isinstance(tr, dict) and tr.get('language_code') == 'en':
return tr.get('text', '')
return None
def get_translation(self,lang):
"""
Get title for a specific language
"""
if not self.translation or not isinstance(self.translation, list):
return None
for tr in self.translation:
if isinstance(tr, dict) and tr.get('language_code') == lang:
return tr.get('text', '')
for tr in self.translation:
if isinstance(tr, dict) and tr.get('language_code') == 'en':
return tr.get('text', '')
return None