Browse Source

feat(hadis): add Country and Publisher relational models, migrations, and admin APIs

- Add Country model for transmitters and reference publishers
- Add Publisher model linked to Country and City
- Add migration 0065 for Country and Publisher models
- Expose AdminCountrySerializer, AdminPublisherSerializer, and respective viewsets in admin URLs
master
Mohsen Taba 1 week ago
parent
commit
1ecca0dc30
  1. 61
      apps/hadis/migrations/0065_country_and_publisher_models.py
  2. 65
      apps/hadis/models/reference.py
  3. 41
      apps/hadis/models/transmitter.py
  4. 55
      apps/hadis/serializers/serializers_admin.py
  5. 6
      apps/hadis/urls.py
  6. 54
      apps/hadis/views_admin.py

61
apps/hadis/migrations/0065_country_and_publisher_models.py

@ -0,0 +1,61 @@
# Generated by Django 4.2.30 on 2026-10-21 10:38
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('hadis', '0064_transmitter_profession_and_relations'),
]
operations = [
migrations.CreateModel(
name='Country',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.JSONField(default=list, verbose_name='Title')),
('slug', models.SlugField(allow_unicode=True, blank=True, max_length=255, null=True, verbose_name='slug')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='created at')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='updated at')),
],
options={
'verbose_name': 'Country',
'verbose_name_plural': 'Countries',
'ordering': ('-created_at', '-id'),
},
),
migrations.CreateModel(
name='BookPublisher',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.JSONField(default=list, verbose_name='Title')),
('slug', models.SlugField(allow_unicode=True, blank=True, max_length=255, null=True, verbose_name='slug')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='created at')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='updated at')),
('city', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='publishers', to='hadis.city', verbose_name='City')),
('country', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='publishers', to='hadis.country', verbose_name='Country')),
],
options={
'verbose_name': 'Book Publisher',
'verbose_name_plural': 'Book Publishers',
'ordering': ('-created_at', '-id'),
},
),
migrations.AddField(
model_name='bookedition',
name='country_of_publication_country',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='publication_editions', to='hadis.country', verbose_name='Country of Publication (Country)'),
),
migrations.AddField(
model_name='bookedition',
name='publisher_item',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='editions', to='hadis.bookpublisher', verbose_name='Publisher Entity'),
),
migrations.AddField(
model_name='city',
name='country',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='cities', to='hadis.country', verbose_name='Country'),
),
]

65
apps/hadis/models/reference.py

@ -239,12 +239,69 @@ class BookReference(LowercaseSlugMixin, models.Model):
class BookPublisher(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, allow_unicode=True)
city = models.ForeignKey(
'hadis.City',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='publishers',
verbose_name=_('City')
)
country = models.ForeignKey(
'hadis.Country',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='publishers',
verbose_name=_('Country')
)
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', '-id')
verbose_name = _('Book Publisher')
verbose_name_plural = _('Book Publishers')
def __str__(self):
if self.title and isinstance(self.title, list) and len(self.title) > 0 and isinstance(self.title[0], dict):
return self.title[0].get('text') or self.title[0].get('title') or str(self.id)
return str(self.id)
def get_title(self, lang: str = 'en') -> Optional[str]:
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', '') or tr.get('title', '')
for tr in self.title:
if isinstance(tr, dict) and tr.get('language_code') == 'en':
return tr.get('text', '') or tr.get('title', '')
if isinstance(self.title[0], dict):
return self.title[0].get('text', '') or self.title[0].get('title', '')
return None
class BookEdition(models.Model): class BookEdition(models.Model):
""" """
Model representing a specific edition or print of a book reference. Model representing a specific edition or print of a book reference.
""" """
book_reference = models.ForeignKey(BookReference, on_delete=models.CASCADE, related_name='editions', verbose_name=_('book reference')) book_reference = models.ForeignKey(BookReference, on_delete=models.CASCADE, related_name='editions', verbose_name=_('book reference'))
publisher = models.JSONField(default=list, blank=True, verbose_name=_('Publisher')) publisher = models.JSONField(default=list, blank=True, verbose_name=_('Publisher'))
publisher_item = models.ForeignKey(
'hadis.BookPublisher',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='editions',
verbose_name=_('Publisher Entity')
)
city_of_publication = models.JSONField(default=list, blank=True, verbose_name=_('City of Publication')) city_of_publication = models.JSONField(default=list, blank=True, verbose_name=_('City of Publication'))
city_of_publication_city = models.ForeignKey( city_of_publication_city = models.ForeignKey(
'hadis.City', 'hadis.City',
@ -255,6 +312,14 @@ class BookEdition(models.Model):
verbose_name=_('City of Publication (City)'), verbose_name=_('City of Publication (City)'),
) )
country_of_publication = models.JSONField(default=list, blank=True, verbose_name=_('Country of Publication')) country_of_publication = models.JSONField(default=list, blank=True, verbose_name=_('Country of Publication'))
country_of_publication_country = models.ForeignKey(
'hadis.Country',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='publication_editions',
verbose_name=_('Country of Publication (Country)'),
)
edition_number = models.CharField(max_length=100, blank=True, null=True, verbose_name=_('Edition Number')) edition_number = models.CharField(max_length=100, blank=True, null=True, verbose_name=_('Edition Number'))
year_of_publication = models.CharField(max_length=50, blank=True, null=True, verbose_name=_('year of publication')) year_of_publication = models.CharField(max_length=50, blank=True, null=True, verbose_name=_('year of publication'))
number_of_volumes = models.PositiveIntegerField(null=True, blank=True, verbose_name=_('Total Number of Volumes')) number_of_volumes = models.PositiveIntegerField(null=True, blank=True, verbose_name=_('Total Number of Volumes'))

41
apps/hadis/models/transmitter.py

@ -143,6 +143,15 @@ class City(LowercaseSlugMixin, models.Model):
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('created at')) created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('created at'))
updated_at = models.DateTimeField(auto_now=True, verbose_name=_('updated at')) updated_at = models.DateTimeField(auto_now=True, verbose_name=_('updated at'))
country = models.ForeignKey(
'hadis.Country',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='cities',
verbose_name=_('Country')
)
class Meta: class Meta:
ordering = ('-created_at', '-id') ordering = ('-created_at', '-id')
verbose_name = _('City') verbose_name = _('City')
@ -166,6 +175,38 @@ class City(LowercaseSlugMixin, models.Model):
return self.title[0].get('text', '') or self.title[0].get('title', '') return self.title[0].get('text', '') or self.title[0].get('title', '')
return None return None
class Country(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, allow_unicode=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:
ordering = ('-created_at', '-id')
verbose_name = _('Country')
verbose_name_plural = _('Countries')
def __str__(self):
if self.title and isinstance(self.title, list) and len(self.title) > 0 and isinstance(self.title[0], dict):
return self.title[0].get('text') or self.title[0].get('title') or str(self.id)
return str(self.id)
def get_title(self, lang: str = 'en') -> Optional[str]:
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', '') or tr.get('title', '')
for tr in self.title:
if isinstance(tr, dict) and tr.get('language_code') == 'en':
return tr.get('text', '') or tr.get('title', '')
if isinstance(self.title[0], dict):
return self.title[0].get('text', '') or self.title[0].get('title', '')
return None
class TransmitterProfession(LowercaseSlugMixin, models.Model): class TransmitterProfession(LowercaseSlugMixin, models.Model):
slug_source_field = 'title' slug_source_field = 'title'

55
apps/hadis/serializers/serializers_admin.py

@ -8,9 +8,9 @@ from utils.image_compression import maybe_compress_uploaded_file
from utils import absolute_https_url from utils import absolute_https_url
from ..models import ( from ..models import (
Hadis, HadisCategory, HadisStatus, HadisTag, HadisCorrection, HadisInterpretation, HadisCollection, HadisSect, HadisInCollection, HadisTransmitter, HadisReference, ReferenceImage, Hadis, HadisCategory, HadisStatus, HadisTag, HadisCorrection, HadisInterpretation, HadisCollection, HadisSect, HadisInCollection, HadisTransmitter, HadisReference, ReferenceImage,
BookReference, BookReferenceImage, BookReferenceDocument, BookAuthor, BookAuthorRutba, BookAttribute, BookSubjectArea, BookType, BookTag, BookEdition, BookVolume, BookResearcher, BookEditor,
BookReference, BookReferenceImage, BookReferenceDocument, BookAuthor, BookPublisher, BookAuthorRutba, BookAttribute, BookSubjectArea, BookType, BookTag, BookEdition, BookVolume, BookResearcher, BookEditor,
Transmitters, TransmitterReliability, NarratorLayer, OpinionStatus, ScholarlyOpinion, TransmitterOpinion, TransmitterOriginalText, Transmitters, TransmitterReliability, NarratorLayer, OpinionStatus, ScholarlyOpinion, TransmitterOpinion, TransmitterOriginalText,
City, Kunya, TransmitterFeature, TransmitterProfession, SahabiStatus, Madhhab, RelationType, YearValue, NarratorDate,
City, Country, Kunya, TransmitterFeature, TransmitterProfession, SahabiStatus, Madhhab, RelationType, YearValue, NarratorDate,
CorrectionReference, CorrectionReferenceImage, InterpretationReference, InterpretationReferenceImage, CorrectionReference, CorrectionReferenceImage, InterpretationReference, InterpretationReferenceImage,
OriginalTextReference, OriginalTextReferenceImage, OriginalTextReference, OriginalTextReferenceImage,
ContentRelease ContentRelease
@ -2017,15 +2017,46 @@ class AdminYearValueSerializer(serializers.ModelSerializer):
return instance return instance
class AdminCitySerializer(serializers.ModelSerializer):
class AdminCountrySerializer(serializers.ModelSerializer):
title = serializers.JSONField() title = serializers.JSONField()
class Meta: class Meta:
model = City
model = Country
fields = ["id", "title", "slug", "created_at", "updated_at"] fields = ["id", "title", "slug", "created_at", "updated_at"]
read_only_fields = ["id", "slug", "created_at", "updated_at"] read_only_fields = ["id", "slug", "created_at", "updated_at"]
class AdminCitySerializer(serializers.ModelSerializer):
title = serializers.JSONField()
country_detail = AdminCountrySerializer(source="country", read_only=True)
class Meta:
model = City
fields = ["id", "title", "slug", "country", "country_detail", "created_at", "updated_at"]
read_only_fields = ["id", "slug", "country_detail", "created_at", "updated_at"]
class AdminBookPublisherSerializer(serializers.ModelSerializer):
title = serializers.JSONField()
city_detail = AdminCitySerializer(source="city", read_only=True)
country_detail = AdminCountrySerializer(source="country", read_only=True)
class Meta:
model = BookPublisher
fields = [
"id",
"title",
"slug",
"city",
"city_detail",
"country",
"country_detail",
"created_at",
"updated_at",
]
read_only_fields = ["id", "slug", "city_detail", "country_detail", "created_at", "updated_at"]
class AdminKunyaSerializer(serializers.ModelSerializer): class AdminKunyaSerializer(serializers.ModelSerializer):
title = serializers.JSONField() title = serializers.JSONField()
@ -3645,6 +3676,12 @@ class AdminBookEditorSerializer(serializers.ModelSerializer):
class AdminBookEditionSerializer(serializers.ModelSerializer): class AdminBookEditionSerializer(serializers.ModelSerializer):
publisher = serializers.JSONField(required=False, default=list) publisher = serializers.JSONField(required=False, default=list)
city_of_publication = serializers.JSONField(required=False, default=list) city_of_publication = serializers.JSONField(required=False, default=list)
publisher_item = serializers.PrimaryKeyRelatedField(
queryset=BookPublisher.objects.all(),
required=False,
allow_null=True,
)
publisher_item_detail = AdminBookPublisherSerializer(source="publisher_item", read_only=True)
city_of_publication_city = serializers.PrimaryKeyRelatedField( city_of_publication_city = serializers.PrimaryKeyRelatedField(
queryset=City.objects.all(), queryset=City.objects.all(),
required=False, required=False,
@ -3652,6 +3689,12 @@ class AdminBookEditionSerializer(serializers.ModelSerializer):
) )
city_of_publication_city_detail = AdminCitySerializer(source="city_of_publication_city", read_only=True) city_of_publication_city_detail = AdminCitySerializer(source="city_of_publication_city", read_only=True)
country_of_publication = serializers.JSONField(required=False, default=list) country_of_publication = serializers.JSONField(required=False, default=list)
country_of_publication_country = serializers.PrimaryKeyRelatedField(
queryset=Country.objects.all(),
required=False,
allow_null=True,
)
country_of_publication_country_detail = AdminCountrySerializer(source="country_of_publication_country", read_only=True)
notes = serializers.JSONField(required=False, default=list) notes = serializers.JSONField(required=False, default=list)
tag = serializers.PrimaryKeyRelatedField( tag = serializers.PrimaryKeyRelatedField(
queryset=BookTag.objects.all(), queryset=BookTag.objects.all(),
@ -3672,10 +3715,14 @@ class AdminBookEditionSerializer(serializers.ModelSerializer):
"id", "id",
"book_reference", "book_reference",
"publisher", "publisher",
"publisher_item",
"publisher_item_detail",
"city_of_publication", "city_of_publication",
"city_of_publication_city", "city_of_publication_city",
"city_of_publication_city_detail", "city_of_publication_city_detail",
"country_of_publication", "country_of_publication",
"country_of_publication_country",
"country_of_publication_country_detail",
"edition_number", "edition_number",
"year_of_publication", "year_of_publication",
"number_of_volumes", "number_of_volumes",

6
apps/hadis/urls.py

@ -45,6 +45,8 @@ from .views_admin import (
AdminContentReleaseViewSet, AdminContentReleaseViewSet,
DovodiCityViewSet, DovodiCityViewSet,
DovodiCountryViewSet,
DovodiBookPublisherViewSet,
DovodiKunyaViewSet, DovodiKunyaViewSet,
DovodiTransmitterFeatureViewSet, DovodiTransmitterFeatureViewSet,
DovodiTransmitterProfessionViewSet, DovodiTransmitterProfessionViewSet,
@ -149,6 +151,10 @@ admin_router.register(r'content-version', AdminContentReleaseViewSet, basename='
admin_router.register(r'dovodi-cities', DovodiCityViewSet, basename='dovodi-cities') admin_router.register(r'dovodi-cities', DovodiCityViewSet, basename='dovodi-cities')
admin_router.register(r'cities', DovodiCityViewSet, basename='admin-cities') admin_router.register(r'cities', DovodiCityViewSet, basename='admin-cities')
admin_router.register(r'dovodi-countries', DovodiCountryViewSet, basename='dovodi-countries')
admin_router.register(r'countries', DovodiCountryViewSet, basename='admin-countries')
admin_router.register(r'dovodi-publishers', DovodiBookPublisherViewSet, basename='dovodi-publishers')
admin_router.register(r'publishers', DovodiBookPublisherViewSet, basename='admin-publishers')
admin_router.register(r'dovodi-kunyas', DovodiKunyaViewSet, basename='dovodi-kunyas') admin_router.register(r'dovodi-kunyas', DovodiKunyaViewSet, basename='dovodi-kunyas')
admin_router.register(r'kunyas', DovodiKunyaViewSet, basename='admin-kunyas') admin_router.register(r'kunyas', DovodiKunyaViewSet, basename='admin-kunyas')
admin_router.register(r'dovodi-transmitter-features', DovodiTransmitterFeatureViewSet, basename='dovodi-transmitter-features') admin_router.register(r'dovodi-transmitter-features', DovodiTransmitterFeatureViewSet, basename='dovodi-transmitter-features')

54
apps/hadis/views_admin.py

@ -37,6 +37,8 @@ from .models import (
CategoryQuranVerse, CategoryQuranVerse,
City, City,
Country,
BookPublisher,
Kunya, Kunya,
TransmitterFeature, TransmitterFeature,
@ -130,6 +132,8 @@ from .serializers.serializers_admin import (
AdminContentReleaseSerializer, AdminContentReleaseSerializer,
AdminCitySerializer, AdminCitySerializer,
AdminCountrySerializer,
AdminBookPublisherSerializer,
AdminKunyaSerializer, AdminKunyaSerializer,
AdminTransmitterFeatureSerializer, AdminTransmitterFeatureSerializer,
@ -2878,6 +2882,50 @@ class AdminContentReleaseViewSet(ModelViewSet):
class DovodiCountryViewSet(ModelViewSet):
serializer_class = AdminCountrySerializer
permission_classes = [IsAuthenticated, IsSuperAdmin]
authentication_classes = [TokenAuthentication]
pagination_class = OptionalPagination
parser_classes = (JSONParser, MultiPartParser, FormParser)
def get_queryset(self):
queryset = Country.objects.all().order_by("slug")
search_query = self.request.query_params.get("search")
if search_query:
queryset = queryset.filter(
Q(title__icontains=search_query)
| Q(slug__icontains=search_query)
)
return queryset
class DovodiBookPublisherViewSet(ModelViewSet):
serializer_class = AdminBookPublisherSerializer
permission_classes = [IsAuthenticated, IsSuperAdmin]
authentication_classes = [TokenAuthentication]
pagination_class = OptionalPagination
parser_classes = (JSONParser, MultiPartParser, FormParser)
def get_queryset(self):
queryset = BookPublisher.objects.select_related("city", "country").all().order_by("slug")
search_query = self.request.query_params.get("search")
if search_query:
queryset = queryset.filter(
Q(title__icontains=search_query)
| Q(slug__icontains=search_query)
| Q(city__title__icontains=search_query)
| Q(country__title__icontains=search_query)
)
city_id = self.request.query_params.get("city")
if city_id and city_id != "all":
queryset = queryset.filter(city_id=city_id)
country_id = self.request.query_params.get("country")
if country_id and country_id != "all":
queryset = queryset.filter(country_id=country_id)
return queryset
class DovodiCityViewSet(ModelViewSet): class DovodiCityViewSet(ModelViewSet):
serializer_class = AdminCitySerializer serializer_class = AdminCitySerializer
@ -2894,7 +2942,7 @@ class DovodiCityViewSet(ModelViewSet):
def get_queryset(self): def get_queryset(self):
queryset = City.objects.all().order_by("slug")
queryset = City.objects.all().select_related("country").order_by("slug")
search_query = self.request.query_params.get("search") search_query = self.request.query_params.get("search")
@ -2908,6 +2956,10 @@ class DovodiCityViewSet(ModelViewSet):
) )
country_id = self.request.query_params.get("country") or self.request.query_params.get("country_id")
if country_id:
queryset = queryset.filter(country_id=country_id)
return queryset return queryset

Loading…
Cancel
Save