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.
 
 
 
 

715 lines
32 KiB

from tabnanny import verbose
from django.db import models
from django.utils.translation import gettext_lazy as _
from django.utils.text import slugify
from typing import Optional
from utils.slug import generate_smart_slug
from django.conf import settings
from utils.mixins import ColorPaletteMixin, LowercaseSlugMixin
class NarratorLayer(LowercaseSlugMixin, models.Model):
slug_source_field = 'name'
"""
Model for narrator layers/classes (Tabaqat)
Represents the classification level of narrators in hadis chains
"""
name = models.JSONField(default = list , verbose_name=_('Name'))
number = models.PositiveIntegerField(verbose_name=_('layer number'), unique=True)
description = models.JSONField(default = list , verbose_name=_('Description'))
slug = models.SlugField(max_length=255,unique=True, verbose_name=_('slug'), blank=True)
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 = _('Narrator Layer')
verbose_name_plural = _('Narrator Layers')
ordering = ['number']
def __str__(self):
name = self.name[0]['text'] if self.name else f"Layer {self.number}"
return f"{_('Layer')} {self.number} - {name}"
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_name(self,lang):
"""
Get title for a specific language
"""
if not self.name or not isinstance(self.name, list):
return None
for tr in self.name:
if isinstance(tr, dict) and tr.get('language_code') == lang:
return tr.get('text', '')
for tr in self.name:
if isinstance(tr, dict) and tr.get('language_code') == 'en':
return tr.get('text', '')
return None
def save(self, *args, **kwargs):
if not self.slug or (isinstance(self.slug, str) and self.slug.strip() == ''):
# Try to get text from name field with robust error handling
try:
if self.name and isinstance(self.name, list) and len(self.name) > 0:
first_item = self.name[0]
if isinstance(first_item, dict):
text = first_item.get('text', '').strip()
if text:
slug = slugify(text)
# Ensure uniqueness
counter = 1
base_slug = slug
while NarratorLayer.objects.filter(slug=slug).exclude(pk=self.pk).exists():
slug = f"{base_slug}-{counter}"
counter += 1
self.slug = slug
else:
# Fallback to layer number if text is empty
base_slug = f"layer-{self.number}"
slug = base_slug
counter = 1
while NarratorLayer.objects.filter(slug=slug).exclude(pk=self.pk).exists():
slug = f"{base_slug}-{counter}"
counter += 1
self.slug = slug
else:
# Fallback to layer number if name structure is invalid
base_slug = f"layer-{self.number}"
slug = base_slug
counter = 1
while NarratorLayer.objects.filter(slug=slug).exclude(pk=self.pk).exists():
slug = f"{base_slug}-{counter}"
counter += 1
self.slug = slug
else:
# Fallback to layer number if name structure is invalid
base_slug = f"layer-{self.number}"
slug = base_slug
counter = 1
while NarratorLayer.objects.filter(slug=slug).exclude(pk=self.pk).exists():
slug = f"{base_slug}-{counter}"
counter += 1
self.slug = slug
except (IndexError, KeyError, AttributeError, TypeError):
# Fallback to layer number on any error
base_slug = f"layer-{self.number}"
slug = base_slug
counter = 1
while NarratorLayer.objects.filter(slug=slug).exclude(pk=self.pk).exists():
slug = f"{base_slug}-{counter}"
counter += 1
self.slug = slug
super().save(*args, **kwargs)
class TransmitterReliability(ColorPaletteMixin, LowercaseSlugMixin, models.Model):
slug_source_field = 'title'
title = models.JSONField(default = list , verbose_name=_('Title'))
slug = models.SlugField(max_length=255, verbose_name=_('slug'), blank=True,null=True)
def __str__(self):
return self.title[0]['text'] if self.title else str(self.id)
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 = _('Transmitter Reliability')
verbose_name_plural = _('Transmitter Reliabilities')
class Transmitters(LowercaseSlugMixin, models.Model):
slug_source_field = 'full_name'
# class ReliabilityLevel(models.TextChoices):
# VERY_RELIABLE = 'very_reliable', _('Very Reliable')
# RELIABLE = 'reliable', _('Reliable')
# ACCEPTABLE = 'acceptable', _('Acceptable')
# WEAK = 'weak', _('Weak')
# VERY_WEAK = 'very_weak', _('Very Weak')
# UNKNOWN = 'unknown', _('Unknown')
class MadhhabChoices(models.TextChoices):
SHIA = 'shia', _('Shia')
SUNNI = 'sunni', _('Sunni')
# HANAFI = 'hanafi', _('Hanafi')
# MALIKI = 'maliki', _('Maliki')
# SHAFII = 'shafii', _('Shafi\'i')
# HANBALI = 'hanbali', _('Hanbali')
OTHER = 'other', _('Other')
UNKNOWN = 'unknown', _('Unknown')
# Basic Information
full_name = models.JSONField(default = list , verbose_name=_('Full Name'))
kunya = models.JSONField(default = list , verbose_name=_('Kunya'))
known_as = models.JSONField(default = list , verbose_name=_('Known as'))
nickname = models.JSONField(default = list , verbose_name=_('Nick Name'))
slug = models.SlugField(max_length=255, verbose_name=_('slug'), blank=True,unique=True)
legacy_number = models.PositiveIntegerField(unique=True, null=True, blank=True, db_index=True)
legacy_id = models.CharField(max_length=255, unique=True, null=True, blank=True, db_index=True)
relatives_raw = models.JSONField(default=dict, blank=True, verbose_name=_('Raw Relatives Data'))
father = models.ForeignKey('self', on_delete=models.SET_NULL, null=True, blank=True, related_name='children_as_father', verbose_name=_('Father'))
mother = models.ForeignKey('self', on_delete=models.SET_NULL, null=True, blank=True, related_name='children_as_mother', verbose_name=_('Mother'))
# ManyToMany for siblings allows bi-directional sibling relationships
siblings = models.ManyToManyField('self', blank=True, verbose_name=_('Siblings'))
freed_slave_of = models.JSONField(default=list, blank=True, verbose_name=_('Freed Slave Of'))
occupation = models.JSONField(default=list, blank=True, verbose_name=_('Occupation'))
features = models.JSONField(default=list, blank=True, verbose_name=_('Features'))
tags = models.JSONField(default=list, blank=True, verbose_name=_('Tags'))
# Geographic Information
origin = models.JSONField(default = list , verbose_name=_('Origin'))
lived_in = models.JSONField(default = list , verbose_name=_('Lived in'))
died_in = models.JSONField(default = list , verbose_name=_('Died in'))
# Date Information
birth_year_hijri = models.CharField(max_length=50, verbose_name=_("Birth Year (Hijri)"), null=True, blank=True)
death_year_hijri = models.CharField(max_length=50, verbose_name=_("Death Year (Hijri)"), null=True, blank=True)
age_at_death = models.PositiveIntegerField(verbose_name=_('Age at Death'), blank=True, null=True)
generation = models.PositiveIntegerField(verbose_name=_('Generation'), blank=True, null=True)
# New fields
tadlis = models.BooleanField(default=False, verbose_name=_('Tadlis'))
ikhtilat = models.BooleanField(default=False, verbose_name=_('Ikhtilat'))
companion_type = models.CharField(max_length=255, null=True, blank=True, verbose_name=_('Companion Type'))
# Religious & Academic Information
reliability = models.ForeignKey(
TransmitterReliability,
on_delete=models.CASCADE,
verbose_name=_('reliability'),
related_name='transmitters',
default=12 # ID of 'Unknown' reliability
)
madhhab = models.CharField(
max_length=20,
choices=MadhhabChoices.choices,
default=MadhhabChoices.UNKNOWN,
verbose_name=_('Madhhab/School of Thought')
)
# Presence in Famous Collections
in_sahih_muslim = models.BooleanField(
default=False,
verbose_name=_('In Sahih Muslim'),
help_text=_('Is this narrator present in Sahih Muslim?')
)
in_sahih_bukhari = models.BooleanField(
default=False,
verbose_name=_('In Sahih Bukhari'),
help_text=_('Is this narrator present in Sahih Bukhari?')
)
# Additional Information
description = models.JSONField(default = list , verbose_name=_('Description'))
thumbnail = models.ImageField(upload_to='hadis/transmitter_thumbnails/', null=True, blank=True, help_text=_('image allowed'))
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:
indexes = [
# For ordering in sync API
models.Index(fields=['id']),
]
verbose_name = _('Transmitter')
verbose_name_plural = _('Transmitters')
ordering = ('full_name',)
@property
def share_link(self):
if self.slug:
return f"{settings.DOVODI_DOMAIN}/arguments/narrators/{self.slug}"
return None
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_full_name(self, lang):
return self._get_json_field("full_name" , lang)
def get_kunya(self, lang):
return self._get_json_field("kunya" , lang)
def get_nickname(self, lang):
return self._get_json_field("nickname" , lang)
def get_origin(self, lang):
return self._get_json_field("origin" , lang)
def get_lived_in(self,lang):
return self._get_json_field("lived_in" , lang)
def get_died_in(self, lang):
return self._get_json_field("died_in" , lang)
def get_description(self, lang):
return self._get_json_field("description" , lang)
def __str__(self):
if self.full_name and len(self.full_name) > 0:
return self.full_name[0].get('text', str(self.id))
return str(self.id)
class HadisTransmitter(models.Model):
hadis = models.ForeignKey(
"hadis.Hadis",
on_delete=models.CASCADE,
verbose_name=_('hadis'),
related_name='transmitters'
)
transmitter = models.ForeignKey(
Transmitters,
on_delete=models.CASCADE,
verbose_name=_('transmitter'),
related_name='hadises'
)
chain_index = models.PositiveIntegerField(
default=0,
verbose_name=_('Chain Index'),
help_text=_('Which chain/branch this belongs to (0, 1, 2...)')
)
narrator_layer = models.ForeignKey(
NarratorLayer,
on_delete=models.SET_NULL,
verbose_name=_('narrator layer'),
related_name='transmitters',
null=True,
blank=True,
help_text=_('The layer/class (Tabaqah) this narrator belongs to')
)
status = models.ForeignKey(
TransmitterReliability,
on_delete=models.SET_NULL,
verbose_name=_('reliability status'),
related_name='hadis_transmitters',
null=True,
blank=True,
help_text=_('Reliability status of the narrator')
)
order = models.PositiveIntegerField(
default=0,
verbose_name=_('Order'),
help_text=_('Order in the chain of transmission')
)
is_gap = models.BooleanField(default=False, verbose_name=_('is gap'))
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('created at'))
class Meta:
indexes = [
# Speeds up fetching transmitters for a specific hadis in order
models.Index(fields=['hadis', 'order']),
]
verbose_name = _('Hadis Transmitter')
verbose_name_plural = _('Hadis Transmitters')
ordering = ('hadis', 'chain_index', 'order') # Update ordering
# 2. UPDATE UNIQUE TOGETHER
# A specific position in a specific chain of a specific hadith can only have one narrator
unique_together = ('hadis', 'chain_index', 'order')
def __str__(self):
return f'{self.hadis.number} - Chain {self.chain_index} - {self.transmitter} (Pos: {self.order})'
class OpinionStatus(ColorPaletteMixin, LowercaseSlugMixin, models.Model):
slug_source_field = 'title'
title = models.JSONField(default = list , verbose_name=_('Title'))
slug = models.SlugField(max_length=255, verbose_name=_('slug'), blank=True,null=True)
def __str__(self):
return self.title[0]['text'] if self.title else str(self.id)
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 = _('Opinion Status')
verbose_name_plural = _('Opinion Statuses')
class TransmitterOpinion(models.Model):
"""
Model for scholarly opinions about transmitters
"""
# class OpinionStatus(models.TextChoices):
# CONFIRMED = 'confirmed', _('Confirmed')
# MIXED = 'mixed', _('Mixed')
# REJECTED = 'rejected', _('Rejected')
transmitter = models.ForeignKey(
Transmitters,
on_delete=models.CASCADE,
verbose_name=_('transmitter'),
related_name='opinions'
)
scholar = models.ForeignKey(
'hadis.BookAuthor',
on_delete=models.SET_NULL,
verbose_name=_('Scholar'),
related_name='transmitter_opinions',
null=True,
blank=True
)
scholar_name = models.JSONField(default = list , verbose_name=_('Scholar Name'))
opinion_text = models.JSONField(default = list , verbose_name=_('Opinion Text'))
status = models.ForeignKey(
OpinionStatus,
on_delete=models.CASCADE,
verbose_name=_('opinion status'),
related_name='opinions',
# default=1,
blank = True,
null=True
)
order = models.IntegerField(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:
indexes = [
# For filtering by transmitter + ordering
models.Index(fields=['transmitter']),
]
verbose_name = _('Transmitter Opinion')
verbose_name_plural = _('Transmitter Opinions')
ordering = ('order', 'id')
def __str__(self):
scholar = self.scholar_name[0]['text'] if self.scholar_name else "Unknown Scholar"
return f"{scholar}'s opinion on {self.transmitter} ({self.status})"
def get_scholar_name(self,lang):
"""
Get title for a specific language
"""
if not self.scholar_name or not isinstance(self.scholar_name, list):
return None
for tr in self.scholar_name:
if isinstance(tr, dict) and tr.get('language_code') == lang:
return tr.get('text', '')
for tr in self.scholar_name:
if isinstance(tr, dict) and tr.get('language_code') == 'en':
return tr.get('text', '')
return None
def get_opinion_tex(self,lang):
"""
Get title for a specific language
"""
if not self.opinion_text or not isinstance(self.opinion_text, list):
return None
for tr in self.opinion_text:
if isinstance(tr, dict) and tr.get('language_code') == lang:
return tr.get('text', '')
for tr in self.opinion_text:
if isinstance(tr, dict) and tr.get('language_code') == 'en':
return tr.get('text', '')
return None
class TransmitterOriginalText(models.Model):
transmitter = models.ForeignKey(
Transmitters,
on_delete=models.CASCADE,
verbose_name=_('transmitter'),
related_name='originaltexts'
)
slug = models.SlugField(max_length=255, verbose_name=_('slug'), blank=True,unique=True)
title = models.JSONField(default = list , verbose_name=_('Title'))
text = models.JSONField(default = list , verbose_name=_('Text'))
translation = models.JSONField(verbose_name=_('translation'), default=list)
share_link = models.CharField(max_length=255, verbose_name=_('share link'), null=True, blank=True)
embedded_in = models.JSONField(default=list, blank=True)
order = models.IntegerField(default=0, verbose_name=_('order'))
class Meta:
indexes = [
# For filtering by transmitter + ordering
models.Index(fields=['transmitter']),
]
verbose_name = _('Transmitter Original Text')
verbose_name_plural = _('Transmitter Original Text')
ordering = ('order', 'id')
def __str__(self):
title = self.title[0]['text'] if self.title else "Untitled"
return f"{title} by {self.transmitter}"
def save(self, *args, **kwargs):
"""
Override save to automatically generate smart slugs.
"""
from utils.slug import build_slug_for_instance
build_slug_for_instance(self, source_field='title')
# Generate/update share_link before saving
if self.slug and self.transmitter and self.transmitter.slug:
self.share_link = f"{settings.DOVODI_DOMAIN}/arguments/narrators/{self.transmitter.slug}/original-texts/{self.slug}"
# Reset embedded_in if text or translation changes
if self.pk:
old_instance = TransmitterOriginalText.objects.get(pk=self.pk)
if (old_instance.text != self.text or
old_instance.translation != self.translation):
self.embedded_in = [] # Reset!
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_opinion_tex(self,lang):
"""
Get title for a specific language
"""
if not self.opinion_text or not isinstance(self.opinion_text, list):
return None
for tr in self.opinion_text:
if isinstance(tr, dict) and tr.get('language_code') == lang:
return tr.get('text', '')
for tr in self.opinion_text:
if isinstance(tr, dict) and tr.get('language_code') == 'en':
return tr.get('text', '')
return None
from .reference import BookReference
class OriginalTextReference(models.Model):
"""Links an Original Text (Excerpt) to a specific Book Edition"""
original_text = models.ForeignKey(
TransmitterOriginalText,
on_delete=models.CASCADE,
related_name='references'
)
book_reference = models.ForeignKey('hadis.BookReference', on_delete=models.SET_NULL, null=True, blank=True)
# 👇 فیلدهای جدید برای هماهنگی با سیستم Edition و Volume
edition = models.ForeignKey('hadis.BookEdition', on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_('Edition'))
book_volume = models.ForeignKey('hadis.BookVolume', on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_('Volume Record'))
# فیلدهای متنی قدیمی برای سادگی یا مواردی که هنوز ولوم ساخته نشده است
volume = models.CharField(max_length=100, null=True, blank=True, verbose_name=_('Volume Text'))
pages = models.CharField(max_length=100, null=True, blank=True) # 👈 تغییر page به pages
address = models.TextField(null=True, blank=True, verbose_name=_('Address'))
url = models.URLField(max_length=1000, null=True, blank=True) # 👈 مچ کردن طول با بقیه مدل‌ها (1000)
class OriginalTextReferenceImage(models.Model):
"""Stores the screenshots for a specific Original Text Book Reference"""
reference = models.ForeignKey(OriginalTextReference, related_name='images', on_delete=models.CASCADE)
image = models.ImageField(upload_to='hadis/original_text_images/')
priority = models.IntegerField(default=0)
RELATION_TYPE_MAP = {
'stepfather': [{'language_code': 'ru', 'text': 'Отчим'}, {'language_code': 'ar', 'text': 'زوج الأم'}, {'language_code': 'en', 'text': 'Stepfather'}],
'stepmother': [{'language_code': 'ru', 'text': 'Мачеха'}, {'language_code': 'ar', 'text': 'زوجة الأب'}, {'language_code': 'en', 'text': 'Stepmother'}],
'uncles_and_aunts': [{'language_code': 'ru', 'text': 'Дяди и тёти'}, {'language_code': 'ar', 'text': 'الأعمام والعمات والأخوال والخالات'}, {'language_code': 'en', 'text': 'Uncles and aunts'}],
'siblings': [{'language_code': 'ru', 'text': 'Сиблинги'}, {'language_code': 'ar', 'text': 'الإخوة'}, {'language_code': 'en', 'text': 'Siblings'}],
'parents': [{'language_code': 'ru', 'text': 'Родители'}, {'language_code': 'ar', 'text': 'الوالدان'}, {'language_code': 'en', 'text': 'Parents'}],
'nephews_and_nieces': [{'language_code': 'ru', 'text': 'Племянники'}, {'language_code': 'ar', 'text': 'أبناء الإخوة'}, {'language_code': 'en', 'text': 'Nephews and nieces'}],
'children': [{'language_code': 'ru', 'text': 'Дети'}, {'language_code': 'ar', 'text': 'الأولاد'}, {'language_code': 'en', 'text': 'Children'}],
'stepchildren': [{'language_code': 'ru', 'text': 'Пасынки и падчерицы'}, {'language_code': 'ar', 'text': 'الربائب'}, {'language_code': 'en', 'text': 'Stepchildren'}],
'spouses': [{'language_code': 'ru', 'text': 'Супруги'}, {'language_code': 'ar', 'text': 'الأزواج'}, {'language_code': 'en', 'text': 'Spouses'}],
'cousins': [{'language_code': 'ru', 'text': 'Двоюродные братья и сестры'}, {'language_code': 'ar', 'text': 'أبناء العمومة والخؤولة'}, {'language_code': 'en', 'text': 'Cousins'}],
'granduncles_and_grandaunts': [{'language_code': 'ru', 'text': 'Великие дяди и тети'}, {'language_code': 'ar', 'text': 'إخوة الأجداد'}, {'language_code': 'en', 'text': 'Granduncles and grandaunts'}],
'grandnephews_and_grandnieces': [{'language_code': 'ru', 'text': 'Внучатые племянники'}, {'language_code': 'ar', 'text': 'أحفاد الإخوة'}, {'language_code': 'en', 'text': 'Grandnephews and grandnieces'}],
'grandchildren_and_great_grandchildren': [{'language_code': 'ru', 'text': 'Внуки и правнуки'}, {'language_code': 'ar', 'text': 'الأحفاد وأبناء الأحفاد'}, {'language_code': 'en', 'text': 'Grandchildren and great-grandchildren'}],
'grandparents': [{'language_code': 'ru', 'text': 'Прародители'}, {'language_code': 'ar', 'text': 'الأجداد'}, {'language_code': 'en', 'text': 'Grandparents'}],
'foster_mother': [{'language_code': 'ru', 'text': 'Молочная мать'}, {'language_code': 'ar', 'text': 'المرضعة'}, {'language_code': 'en', 'text': 'Foster mother'}],
'tribe': [{'language_code': 'ru', 'text': 'Племя'}, {'language_code': 'ar', 'text': 'القبيلة'}, {'language_code': 'en', 'text': 'Tribe'}],
'foster_children': [{'language_code': 'ru', 'text': 'Молочные дети'}, {'language_code': 'ar', 'text': 'الرضاء'}, {'language_code': 'en', 'text': 'Foster children'}],
'in_laws': [{'language_code': 'ru', 'text': 'Свойственники'}, {'language_code': 'ar', 'text': 'الأصهار والأختان'}, {'language_code': 'en', 'text': 'In-laws'}],
'students': [{'language_code': 'ru', 'text': 'Ученики'}, {'language_code': 'ar', 'text': 'التلاميذ'}, {'language_code': 'en', 'text': 'Students'}],
'owners_and_masters': [{'language_code': 'ru', 'text': 'Хозяева и владельцы'}, {'language_code': 'ar', 'text': 'الملاك'}, {'language_code': 'en', 'text': 'Owners and masters'}],
'other_relatives': [{'language_code': 'ru', 'text': 'Другие родственники'}, {'language_code': 'ar', 'text': 'الأقارب'}, {'language_code': 'en', 'text': 'Other relatives'}],
'friends_and_companions': [{'language_code': 'ru', 'text': 'Друзья и сподвижники'}, {'language_code': 'ar', 'text': 'الأصحاب'}, {'language_code': 'en', 'text': 'Friends and companions'}],
'allies': [{'language_code': 'ru', 'text': 'Союзники'}, {'language_code': 'ar', 'text': 'الحلفاء'}, {'language_code': 'en', 'text': 'Allies'}],
'neighbors': [{'language_code': 'ru', 'text': 'Соседи'}, {'language_code': 'ar', 'text': 'الجيران'}, {'language_code': 'en', 'text': 'Neighbors'}],
'wards_and_proteges': [{'language_code': 'ru', 'text': 'Подопечные и воспитанники'}, {'language_code': 'ar', 'text': 'المحضونون'}, {'language_code': 'en', 'text': 'Wards and proteges'}],
'cousins_of_parents': [{'language_code': 'ru', 'text': 'Двоюродные дяди и тети'}, {'language_code': 'ar', 'text': 'أبناء عمومة الآباء'}, {'language_code': 'en', 'text': 'Cousins of parents'}],
'cousins_of_grandparents': [{'language_code': 'ru', 'text': 'Двоюродные дедушки и бабушки'}, {'language_code': 'ar', 'text': 'أبناء عمومة الأجداد'}, {'language_code': 'en', 'text': 'Cousins of grandparents'}],
'cousins_children': [{'language_code': 'ru', 'text': 'Двоюродные племянники'}, {'language_code': 'ar', 'text': 'أبناء أبناء العمومة'}, {'language_code': 'en', 'text': 'Cousins children'}],
'cousins_grandchildren_and_great_grandchildren': [{'language_code': 'ru', 'text': 'Двоюродные внуки и правнуки'}, {'language_code': 'ar', 'text': 'أحفاد أبناء العمومة'}, {'language_code': 'en', 'text': 'Cousins grandchildren and great-grandchildren'}],
}
class TransmitterRelative(models.Model):
class RelationTypeChoices(models.TextChoices):
STEPFATHER = 'stepfather', _('Stepfather')
STEPMOTHER = 'stepmother', _('Stepmother')
UNCLES_AND_AUNTS = 'uncles_and_aunts', _('Uncles and aunts')
SIBLINGS = 'siblings', _('Siblings')
PARENTS = 'parents', _('Parents')
NEPHEWS_AND_NIECES = 'nephews_and_nieces', _('Nephews and nieces')
CHILDREN = 'children', _('Children')
STEPCHILDREN = 'stepchildren', _('Stepchildren')
SPOUSES = 'spouses', _('Spouses')
COUSINS = 'cousins', _('Cousins')
GRANDUNCLES_AND_GRANDAUNTS = 'granduncles_and_grandaunts', _('Granduncles and grandaunts')
GRANDNEPHEWS_AND_GRANDNIECES = 'grandnephews_and_grandnieces', _('Grandnephews and grandnieces')
GRANDCHILDREN_AND_GREAT_GRANDCHILDREN = 'grandchildren_and_great_grandchildren', _('Grandchildren and great-grandchildren')
GRANDPARENTS = 'grandparents', _('Grandparents')
FOSTER_MOTHER = 'foster_mother', _('Foster mother')
TRIBE = 'tribe', _('Tribe')
FOSTER_CHILDREN = 'foster_children', _('Foster children')
IN_LAWS = 'in_laws', _('In-laws')
STUDENTS = 'students', _('Students')
OWNERS_AND_MASTERS = 'owners_and_masters', _('Owners and masters')
OTHER_RELATIVES = 'other_relatives', _('Other relatives')
FRIENDS_AND_COMPANIONS = 'friends_and_companions', _('Friends and companions')
ALLIES = 'allies', _('Allies')
NEIGHBORS = 'neighbors', _('Neighbors')
WARDS_AND_PROTEGES = 'wards_and_proteges', _('Wards and proteges')
COUSINS_OF_PARENTS = 'cousins_of_parents', _('Cousins of parents')
COUSINS_OF_GRANDPARENTS = 'cousins_of_grandparents', _('Cousins of grandparents')
COUSINS_CHILDREN = 'cousins_children', _('Cousins children')
COUSINS_GRANDCHILDREN_AND_GREAT_GRANDCHILDREN = 'cousins_grandchildren_and_great_grandchildren', _('Cousins grandchildren and great-grandchildren')
related_to = models.ForeignKey(
Transmitters,
on_delete=models.CASCADE,
verbose_name=_('Related To'),
related_name='relatives'
)
relation_type = models.CharField(
max_length=50,
choices=RelationTypeChoices.choices,
verbose_name=_('Relation Type')
)
relation_value = models.JSONField(default=list, verbose_name=_('Relation Value'))
name = models.CharField(max_length=255, verbose_name=_('Name'))
narrator = models.ForeignKey(
Transmitters,
on_delete=models.SET_NULL,
null=True,
blank=True,
verbose_name=_('Narrator'),
related_name='relative_of'
)
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 = _('Transmitter Relative')
verbose_name_plural = _('Transmitter Relatives')
ordering = ['relation_type', 'name']
def save(self, *args, **kwargs):
if self.relation_type and not self.relation_value:
self.relation_value = RELATION_TYPE_MAP.get(self.relation_type, [])
super().save(*args, **kwargs)
def __str__(self):
return f"{self.name} ({self.get_relation_type_display()} of {self.related_to})"