Browse Source

feat(hadis): add gender option to Transmitters model, serializers, admin, and XMind view

- Add GenderChoices (male/female) field to Transmitters model with migration 0052
- Include gender across public and admin transmitter serializers
- Expose gender in TransmittersAdmin filter and list display
- Propagate transmitter gender in HadisCategoryXMindView flowchart payload
- Add split_transmitters_gender management command
master
Mohsen Taba 2 weeks ago
parent
commit
900b547728
  1. 6
      apps/hadis/admin/transmitter.py
  2. 40
      apps/hadis/management/commands/split_transmitters_gender.py
  3. 18
      apps/hadis/migrations/0052_transmitters_gender.py
  4. 10
      apps/hadis/models/transmitter.py
  5. 13
      apps/hadis/serializers/hadis.py
  6. 2
      apps/hadis/serializers/serializers_admin.py
  7. 3
      apps/hadis/views/category.py

6
apps/hadis/admin/transmitter.py

@ -438,15 +438,15 @@ class TransmitterOriginalTextInline(TabularInline):
class TransmittersAdmin(ModelAdmin):
"""Admin for Transmitters model"""
form = TransmittersAdminForm
list_display = ('display_header', 'birth_year_hijri', 'death_year_hijri')
list_filter = ('birth_year_hijri', 'death_year_hijri')
list_display = ('display_header', 'gender', 'birth_year_hijri', 'death_year_hijri')
list_filter = ('gender', 'birth_year_hijri', 'death_year_hijri')
search_fields = ('full_name', 'description')
readonly_fields = ('created_at', 'updated_at')
inlines = [HadisTransmitterInline, TransmitterOpinionInline, TransmitterOriginalTextInline]
fieldsets = (
(None, {
'fields': ('full_name', 'birth_year_hijri', 'death_year_hijri','known_as','nickname')
'fields': ('full_name', 'gender', 'birth_year_hijri', 'death_year_hijri','known_as','nickname')
}),
(_('Additional Information'), {
'fields': ('description','origin','lived_in','died_in','kunya'),

40
apps/hadis/management/commands/split_transmitters_gender.py

@ -0,0 +1,40 @@
import random
from django.core.management.base import BaseCommand
from django.core.cache import cache
from apps.hadis.models import Transmitters
class Command(BaseCommand):
help = "Randomly split all existing Transmitters 50/50 between male and female for testing."
def handle(self, *args, **options):
transmitters = list(Transmitters.objects.all().order_by("id"))
total = len(transmitters)
if total == 0:
self.stdout.write(self.style.WARNING("No transmitters found in the database."))
return
random.shuffle(transmitters)
half = total // 2
female_ids = [t.id for t in transmitters[:half]]
male_ids = [t.id for t in transmitters[half:]]
female_updated = Transmitters.objects.filter(id__in=female_ids).update(
gender="female"
)
male_updated = Transmitters.objects.filter(id__in=male_ids).update(
gender="male"
)
# Clear cache so XMind and sync endpoints reflect updated genders immediately
try:
cache.clear()
except Exception:
pass
self.stdout.write(
self.style.SUCCESS(
f"Successfully updated {total} transmitters: {male_updated} male, {female_updated} female."
)
)

18
apps/hadis/migrations/0052_transmitters_gender.py

@ -0,0 +1,18 @@
# Generated by Django 4.2.30 on 2026-10-18 10:49
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hadis', '0051_add_category_is_topic'),
]
operations = [
migrations.AddField(
model_name='transmitters',
name='gender',
field=models.CharField(choices=[('male', 'Male'), ('female', 'Female')], default='male', max_length=10, verbose_name='Gender'),
),
]

10
apps/hadis/models/transmitter.py

@ -139,11 +139,21 @@ class Transmitters(LowercaseSlugMixin, models.Model):
SHIA = 'shia', _('Shia')
SUNNI = 'sunni', _('Sunni')
class GenderChoices(models.TextChoices):
MALE = 'male', _('Male')
FEMALE = 'female', _('Female')
# 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'))
gender = models.CharField(
max_length=10,
choices=GenderChoices.choices,
default=GenderChoices.MALE,
verbose_name=_('Gender')
)
slug = models.SlugField(max_length=255, verbose_name=_('slug'), blank=True,unique=True, allow_unicode=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)

13
apps/hadis/serializers/hadis.py

@ -231,6 +231,7 @@ class HadisSyncSerializer(serializers.ModelSerializer):
'id': ut.id,
'name': get_localized_text(ut.full_name, request),
'slug': ut.slug,
'gender': ut.gender,
'known_as': get_localized_text(ut.known_as, request),
'nickname': get_localized_text(ut.nickname, request),
'birth_year_hijri': ut.birth_year_hijri,
@ -242,6 +243,7 @@ class HadisSyncSerializer(serializers.ModelSerializer):
'id': t.id if t else None,
'name': get_localized_text(t.full_name, request) if t else None,
'slug': t.slug if t else None,
'gender': t.gender if t else None,
'known_as': get_localized_text(t.known_as, request) if t else None,
'nickname': get_localized_text(t.nickname, request) if t else None,
'reliability': reliability_block,
@ -450,7 +452,7 @@ class TransmitterSerializer(serializers.ModelSerializer):
model = Transmitters
fields = [
'id', 'full_name', 'arabic_full_name', 'slug','birth_year_hijri', 'death_year_hijri',
"known_as", 'arabic_known_as', 'nickname', 'arabic_nickname', 'reliability','madhhab','generation','share_link'
"known_as", 'arabic_known_as', 'nickname', 'arabic_nickname', 'reliability','madhhab','generation','share_link', 'gender'
]
def get_reliability(self, obj):
@ -479,7 +481,7 @@ class TransmitterShortSerializer(serializers.ModelSerializer):
model = Transmitters
fields = [
'id', 'full_name', 'arabic_full_name', 'slug','birth_year_hijri', 'death_year_hijri',
"known_as", 'arabic_known_as', 'nickname', 'arabic_nickname', 'reliability'
"known_as", 'arabic_known_as', 'nickname', 'arabic_nickname', 'reliability', 'gender'
]
def get_reliability(self, obj):
@ -688,7 +690,7 @@ class TransmitterDetailSerializer(serializers.ModelSerializer):
'madhhab',"in_sahih_muslim","in_sahih_bukhari",
"description",'generation','share_link',
'tadlis', 'ikhtilat', 'companion_type', 'arabic_companion_type', 'relatives',
'father', 'sons'
'father', 'sons', 'gender'
]
def get_father(self, obj):
@ -818,7 +820,7 @@ class TransmitterSyncSerializer(serializers.ModelSerializer):
class Meta:
model = Transmitters
fields = [
'id', 'full_name', 'arabic_full_name', 'slug', 'biographical', 'scholars_opinions', 'original_texts','hadis','relations'
'id', 'full_name', 'arabic_full_name', 'slug', 'gender', 'biographical', 'scholars_opinions', 'original_texts','hadis','relations'
]
def get_biographical(self, obj):
@ -837,6 +839,7 @@ class TransmitterSyncSerializer(serializers.ModelSerializer):
return {
'full_name': get_localized_text(obj.full_name, request),
'arabic_full_name': get_arabic_localized_text(obj.full_name),
'gender': obj.gender,
'kunya': get_localized_text(obj.kunya, request),
'arabic_kunya': get_arabic_localized_text(obj.kunya),
'known_as': get_localized_text(obj.known_as, request),
@ -1550,7 +1553,7 @@ class NarratorRelationSerializer(serializers.ModelSerializer):
class Meta:
model = Transmitters
fields = ['id', 'slug', 'full_name', 'known_as', 'birth_year_hijri', 'death_year_hijri','reliability']
fields = ['id', 'slug', 'full_name', 'known_as', 'birth_year_hijri', 'death_year_hijri','reliability', 'gender']
def get_reliability(self, obj):
"""Serialize the reliability foreign key"""

2
apps/hadis/serializers/serializers_admin.py

@ -1903,6 +1903,7 @@ class AdminTransmitterListSerializer(serializers.ModelSerializer):
"id",
"full_name",
"slug",
"gender",
"birth_year_hijri",
"death_year_hijri",
"known_as",
@ -1957,6 +1958,7 @@ class AdminTransmitterDetailSerializer(serializers.ModelSerializer):
"kunya",
"known_as",
"nickname",
"gender",
"slug",
"legacy_number",
"legacy_id",

3
apps/hadis/views/category.py

@ -800,6 +800,7 @@ class HadisCategoryXMindView(APIView):
"id": ut.id,
"transmitter_id": ut.id,
"slug": ut.slug,
"gender": ut.gender,
"name": self.get_localized(ut.full_name, lang) or ut.slug,
"title": self.get_localized(ut.full_name, lang) or ut.slug,
"full_name": self.get_localized(ut.full_name, lang),
@ -826,6 +827,7 @@ class HadisCategoryXMindView(APIView):
"node_type": node_type_str,
"tag": node_type_str,
"slug": tr.slug if tr else (uncertain_candidates[0]["slug"] if uncertain_candidates else None),
"gender": tr.gender if tr else None,
"title": "Uncertain narrator" if is_node_uncertain else (self.get_localized(tr.full_name, lang) or tr.slug if tr else "Narrator"),
"full_name": "Uncertain narrator" if is_node_uncertain else (self.get_localized(tr.full_name, lang) if tr else "Narrator"),
"arabic_full_name": self.get_arabic(tr.full_name) if tr else None,
@ -853,6 +855,7 @@ class HadisCategoryXMindView(APIView):
"name": self.get_localized(tr.full_name, lang) or tr.slug,
"full_name": self.get_localized(tr.full_name, lang),
"slug": tr.slug,
"gender": tr.gender,
"reliability": rel_data,
} if tr else None,
"layer": layer_data,

Loading…
Cancel
Save