Browse Source
feat(hadis): enhance hadis management with collections and new endpoints
feat(hadis): enhance hadis management with collections and new endpoints
- Introduced `HadisCollection` and `HadisInCollection` models to manage collections of hadis. - Updated URL patterns to include new endpoints for collections and syncing hadis data. - Enhanced admin interface to support management of hadis collections and their items. - Added serializers for handling collections and syncing hadis data. - Implemented a new API view for retrieving hadis statistics. - Refactored existing serializers and views to accommodate new features and improve data handling.master
16 changed files with 866 additions and 88 deletions
-
44apps/hadis/admin/hadis.py
-
11apps/hadis/admin/transmitter.py
-
226apps/hadis/migrations/0003_bookreference_narratorlayer_and_more.py
-
52apps/hadis/migrations/0004_hadiscollection_hadisincollection.py
-
3apps/hadis/models/__init__.py
-
4apps/hadis/models/category.py
-
71apps/hadis/models/hadis.py
-
109apps/hadis/models/reference.py
-
156apps/hadis/models/transmitter.py
-
42apps/hadis/serializers/category.py
-
59apps/hadis/serializers/hadis.py
-
21apps/hadis/urls.py
-
2apps/hadis/views/__init__.py
-
77apps/hadis/views/category.py
-
47apps/hadis/views/hadis.py
-
30apps/hadis/views/info.py
@ -0,0 +1,226 @@ |
|||
# Generated by Django 5.1.8 on 2025-12-03 23:32 |
|||
|
|||
import django.db.models.deletion |
|||
from django.db import migrations, models |
|||
|
|||
|
|||
class Migration(migrations.Migration): |
|||
|
|||
dependencies = [ |
|||
('hadis', '0002_hadissect_hadisstatus_alter_hadis_options_and_more'), |
|||
] |
|||
|
|||
operations = [ |
|||
migrations.CreateModel( |
|||
name='BookReference', |
|||
fields=[ |
|||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
|||
('title', models.CharField(max_length=500, verbose_name='title')), |
|||
('description', models.TextField(blank=True, null=True, verbose_name='description')), |
|||
('language', models.CharField(blank=True, max_length=100, null=True, verbose_name='language')), |
|||
('isbn', models.CharField(blank=True, max_length=100, null=True, verbose_name='ISBN')), |
|||
('volume', models.CharField(blank=True, max_length=100, null=True, verbose_name='volume')), |
|||
('year_of_publication', models.CharField(blank=True, max_length=50, null=True, verbose_name='year of publication')), |
|||
('number_page', models.PositiveIntegerField(blank=True, null=True, verbose_name='number of pages')), |
|||
('rate', models.DecimalField(blank=True, decimal_places=2, help_text='Rating from 0 to 5', max_digits=3, null=True, verbose_name='rate')), |
|||
('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': 'Book Reference', |
|||
'verbose_name_plural': 'Book References', |
|||
'ordering': ('-created_at',), |
|||
}, |
|||
), |
|||
migrations.CreateModel( |
|||
name='NarratorLayer', |
|||
fields=[ |
|||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
|||
('name', models.CharField(max_length=255, verbose_name='name')), |
|||
('number', models.PositiveIntegerField(unique=True, verbose_name='layer number')), |
|||
('description', models.TextField(blank=True, null=True, verbose_name='description')), |
|||
('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': 'Narrator Layer', |
|||
'verbose_name_plural': 'Narrator Layers', |
|||
'ordering': ['number'], |
|||
}, |
|||
), |
|||
migrations.AlterUniqueTogether( |
|||
name='hadisreference', |
|||
unique_together=set(), |
|||
), |
|||
migrations.RemoveField( |
|||
model_name='hadistransmitter', |
|||
name='is_gap', |
|||
), |
|||
migrations.AddField( |
|||
model_name='hadis', |
|||
name='title_narrator', |
|||
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='title narrator'), |
|||
), |
|||
migrations.AddField( |
|||
model_name='hadiscategory', |
|||
name='description', |
|||
field=models.TextField(blank=True, null=True, verbose_name='Description'), |
|||
), |
|||
migrations.AddField( |
|||
model_name='hadissect', |
|||
name='description', |
|||
field=models.TextField(blank=True, null=True, verbose_name='Description'), |
|||
), |
|||
migrations.AddField( |
|||
model_name='transmitters', |
|||
name='age_at_death', |
|||
field=models.PositiveIntegerField(blank=True, null=True, verbose_name='Age at Death'), |
|||
), |
|||
migrations.AddField( |
|||
model_name='transmitters', |
|||
name='died_in', |
|||
field=models.CharField(blank=True, help_text='Place of death', max_length=255, null=True, verbose_name='Died In'), |
|||
), |
|||
migrations.AddField( |
|||
model_name='transmitters', |
|||
name='in_sahih_bukhari', |
|||
field=models.BooleanField(default=False, help_text='Is this narrator present in Sahih Bukhari?', verbose_name='In Sahih Bukhari'), |
|||
), |
|||
migrations.AddField( |
|||
model_name='transmitters', |
|||
name='in_sahih_muslim', |
|||
field=models.BooleanField(default=False, help_text='Is this narrator present in Sahih Muslim?', verbose_name='In Sahih Muslim'), |
|||
), |
|||
migrations.AddField( |
|||
model_name='transmitters', |
|||
name='known_as', |
|||
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Known As'), |
|||
), |
|||
migrations.AddField( |
|||
model_name='transmitters', |
|||
name='kunya', |
|||
field=models.CharField(blank=True, help_text='e.g., Abu Abdullah', max_length=255, null=True, verbose_name='Kunya'), |
|||
), |
|||
migrations.AddField( |
|||
model_name='transmitters', |
|||
name='lived_in', |
|||
field=models.CharField(blank=True, help_text='Places where they lived', max_length=255, null=True, verbose_name='Lived In'), |
|||
), |
|||
migrations.AddField( |
|||
model_name='transmitters', |
|||
name='madhhab', |
|||
field=models.CharField(choices=[('shia', 'Shia'), ('sunni', 'Sunni'), ('hanafi', 'Hanafi'), ('maliki', 'Maliki'), ('shafii', "Shafi'i"), ('hanbali', 'Hanbali'), ('other', 'Other'), ('unknown', 'Unknown')], default='unknown', max_length=20, verbose_name='Madhhab/School of Thought'), |
|||
), |
|||
migrations.AddField( |
|||
model_name='transmitters', |
|||
name='nickname', |
|||
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Nickname/Laqab'), |
|||
), |
|||
migrations.AddField( |
|||
model_name='transmitters', |
|||
name='origin', |
|||
field=models.CharField(blank=True, help_text='Place of origin', max_length=255, null=True, verbose_name='Origin'), |
|||
), |
|||
migrations.AddField( |
|||
model_name='transmitters', |
|||
name='reliability', |
|||
field=models.CharField(choices=[('very_reliable', 'Very Reliable'), ('reliable', 'Reliable'), ('acceptable', 'Acceptable'), ('weak', 'Weak'), ('very_weak', 'Very Weak'), ('unknown', 'Unknown')], default='unknown', max_length=20, verbose_name='Reliability Level'), |
|||
), |
|||
migrations.AlterField( |
|||
model_name='hadiscategory', |
|||
name='source_type', |
|||
field=models.CharField(choices=[('quran', 'Quran'), ('hadith', 'Hadith'), ('history', 'History'), ('fatwa', 'Fatwa'), ('quote', 'Quote')], max_length=10, verbose_name='Source Type'), |
|||
), |
|||
migrations.AlterField( |
|||
model_name='hadistransmitter', |
|||
name='status', |
|||
field=models.CharField(choices=[('reliable', 'Reliable'), ('weak', 'Weak'), ('unknown', 'Unknown')], default='unknown', help_text='Reliability status of the narrator', max_length=20, verbose_name='reliability status'), |
|||
), |
|||
migrations.AlterField( |
|||
model_name='hadistransmitter', |
|||
name='transmitter', |
|||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='hadises', to='hadis.transmitters', verbose_name='transmitter'), |
|||
), |
|||
migrations.CreateModel( |
|||
name='BookAuthor', |
|||
fields=[ |
|||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
|||
('name', models.CharField(max_length=255, verbose_name='name')), |
|||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='created at')), |
|||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='updated at')), |
|||
('book_references', models.ManyToManyField(blank=True, related_name='authors', to='hadis.bookreference', verbose_name='book references')), |
|||
], |
|||
options={ |
|||
'verbose_name': 'Book Author', |
|||
'verbose_name_plural': 'Book Authors', |
|||
'ordering': ['name'], |
|||
}, |
|||
), |
|||
migrations.CreateModel( |
|||
name='BookAttribute', |
|||
fields=[ |
|||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
|||
('title', models.CharField(max_length=255, verbose_name='title')), |
|||
('value', models.CharField(max_length=500, verbose_name='value')), |
|||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='created at')), |
|||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='updated at')), |
|||
('book_references', models.ManyToManyField(blank=True, related_name='attributes', to='hadis.bookreference', verbose_name='book references')), |
|||
], |
|||
options={ |
|||
'verbose_name': 'Book Attribute', |
|||
'verbose_name_plural': 'Book Attributes', |
|||
'ordering': ['title'], |
|||
}, |
|||
), |
|||
migrations.AddField( |
|||
model_name='hadisreference', |
|||
name='book_reference', |
|||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='hadis_references', to='hadis.bookreference', verbose_name='book reference'), |
|||
), |
|||
migrations.CreateModel( |
|||
name='BookReferenceImage', |
|||
fields=[ |
|||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
|||
('image', models.ImageField(upload_to='hadis/book_reference_images/', verbose_name='image')), |
|||
('order', models.PositiveIntegerField(default=0, verbose_name='order')), |
|||
('description', models.CharField(blank=True, max_length=255, null=True, verbose_name='description')), |
|||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='created at')), |
|||
('book_reference', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='images', to='hadis.bookreference', verbose_name='book reference')), |
|||
], |
|||
options={ |
|||
'verbose_name': 'Book Reference Image', |
|||
'verbose_name_plural': 'Book Reference Images', |
|||
'ordering': ['order', '-created_at'], |
|||
}, |
|||
), |
|||
migrations.AddField( |
|||
model_name='hadistransmitter', |
|||
name='narrator_layer', |
|||
field=models.ForeignKey(blank=True, help_text='The layer/class (Tabaqah) this narrator belongs to', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='transmitters', to='hadis.narratorlayer', verbose_name='narrator layer'), |
|||
), |
|||
migrations.CreateModel( |
|||
name='TransmitterOpinion', |
|||
fields=[ |
|||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
|||
('scholar_name', models.CharField(help_text='Name of the scholar who gave this opinion', max_length=255, verbose_name='Scholar Name')), |
|||
('opinion_text', models.TextField(help_text="The scholar's opinion about this transmitter", verbose_name='Opinion Text')), |
|||
('status', models.CharField(choices=[('confirmed', 'Confirmed'), ('mixed', 'Mixed'), ('rejected', 'Rejected')], default='confirmed', help_text='Status of the opinion', max_length=20, verbose_name='Opinion Status')), |
|||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='created at')), |
|||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='updated at')), |
|||
('transmitter', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='opinions', to='hadis.transmitters', verbose_name='transmitter')), |
|||
], |
|||
options={ |
|||
'verbose_name': 'Transmitter Opinion', |
|||
'verbose_name_plural': 'Transmitter Opinions', |
|||
'ordering': ('-created_at',), |
|||
}, |
|||
), |
|||
migrations.RemoveField( |
|||
model_name='hadisreference', |
|||
name='book', |
|||
), |
|||
migrations.RemoveField( |
|||
model_name='hadisreference', |
|||
name='description', |
|||
), |
|||
] |
|||
@ -0,0 +1,52 @@ |
|||
# Generated by Django 3.2.4 on 2025-12-05 17:07 |
|||
|
|||
from django.conf import settings |
|||
from django.db import migrations, models |
|||
import django.db.models.deletion |
|||
import filer.fields.image |
|||
|
|||
|
|||
class Migration(migrations.Migration): |
|||
|
|||
dependencies = [ |
|||
migrations.swappable_dependency(settings.FILER_IMAGE_MODEL), |
|||
('hadis', '0003_bookreference_narratorlayer_and_more'), |
|||
] |
|||
|
|||
operations = [ |
|||
migrations.CreateModel( |
|||
name='HadisCollection', |
|||
fields=[ |
|||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
|||
('title', models.CharField(max_length=255, verbose_name='title')), |
|||
('slug', models.SlugField(blank=True, max_length=255, unique=True, verbose_name='slug')), |
|||
('summary', models.TextField(blank=True, null=True, verbose_name='summary')), |
|||
('status', models.BooleanField(default=True, verbose_name='status')), |
|||
('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')), |
|||
('thumbnail', filer.fields.image.FilerImageField(blank=True, help_text='thumbnail image', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to=settings.FILER_IMAGE_MODEL, verbose_name='thumbnail')), |
|||
], |
|||
options={ |
|||
'verbose_name': 'hadis collection', |
|||
'verbose_name_plural': 'hadis collections', |
|||
'ordering': ('order',), |
|||
}, |
|||
), |
|||
migrations.CreateModel( |
|||
name='HadisInCollection', |
|||
fields=[ |
|||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
|||
('order', models.IntegerField(default=0, verbose_name='order')), |
|||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='created at')), |
|||
('collection', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='hadis_items', to='hadis.hadiscollection', verbose_name='collection')), |
|||
('hadis', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='collection_items', to='hadis.hadis', verbose_name='hadis')), |
|||
], |
|||
options={ |
|||
'verbose_name': 'hadis in collection', |
|||
'verbose_name_plural': 'hadis in collections', |
|||
'ordering': ('order',), |
|||
'unique_together': {('hadis', 'collection')}, |
|||
}, |
|||
), |
|||
] |
|||
@ -1,3 +1,4 @@ |
|||
from .category import * |
|||
from .hadis import * |
|||
from .transmitter import * |
|||
from .transmitter import * |
|||
from .reference import * |
|||
@ -0,0 +1,109 @@ |
|||
from django.db import models |
|||
from django.utils.translation import gettext_lazy as _ |
|||
|
|||
|
|||
class BookReference(models.Model): |
|||
""" |
|||
Model for hadis book references with detailed information |
|||
This is different from library books - these are reference books for hadis |
|||
""" |
|||
title = models.CharField(max_length=500, verbose_name=_('title')) |
|||
description = models.TextField(verbose_name=_('description'), blank=True, null=True) |
|||
language = models.CharField(max_length=100, verbose_name=_('language'), blank=True, null=True) |
|||
isbn = models.CharField(max_length=100, verbose_name=_('ISBN'), blank=True, null=True) |
|||
volume = models.CharField(max_length=100, verbose_name=_('volume'), blank=True, null=True) |
|||
year_of_publication = models.CharField(max_length=50, verbose_name=_('year of publication'), blank=True, null=True) |
|||
number_page = models.PositiveIntegerField(verbose_name=_('number of pages'), blank=True, null=True) |
|||
rate = models.DecimalField( |
|||
max_digits=3, |
|||
decimal_places=2, |
|||
verbose_name=_('rate'), |
|||
blank=True, |
|||
null=True, |
|||
help_text=_('Rating from 0 to 5') |
|||
) |
|||
|
|||
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 = _('Book Reference') |
|||
verbose_name_plural = _('Book References') |
|||
ordering = ('-created_at',) |
|||
|
|||
def __str__(self): |
|||
return self.title |
|||
|
|||
|
|||
class BookReferenceImage(models.Model): |
|||
""" |
|||
Model for book reference images - multiple images per book reference |
|||
""" |
|||
book_reference = models.ForeignKey( |
|||
BookReference, |
|||
on_delete=models.CASCADE, |
|||
related_name='images', |
|||
verbose_name=_('book reference') |
|||
) |
|||
image = models.ImageField(upload_to='hadis/book_reference_images/', verbose_name=_('image')) |
|||
order = models.PositiveIntegerField(default=0, verbose_name=_('order')) |
|||
description = models.CharField(max_length=255, verbose_name=_('description'), blank=True, null=True) |
|||
|
|||
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('created at')) |
|||
|
|||
class Meta: |
|||
verbose_name = _('Book Reference Image') |
|||
verbose_name_plural = _('Book Reference Images') |
|||
ordering = ['order', '-created_at'] |
|||
|
|||
def __str__(self): |
|||
return f"{self.book_reference.title} - Image {self.order}" |
|||
|
|||
|
|||
class BookAuthor(models.Model): |
|||
""" |
|||
Model for book reference authors |
|||
""" |
|||
name = models.CharField(max_length=255, verbose_name=_('name')) |
|||
book_references = models.ManyToManyField( |
|||
BookReference, |
|||
related_name='authors', |
|||
verbose_name=_('book references'), |
|||
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 = _('Book Author') |
|||
verbose_name_plural = _('Book Authors') |
|||
ordering = ['name'] |
|||
|
|||
def __str__(self): |
|||
return self.name |
|||
|
|||
|
|||
class BookAttribute(models.Model): |
|||
""" |
|||
Model for book reference attributes - custom key-value pairs |
|||
""" |
|||
title = models.CharField(max_length=255, verbose_name=_('title')) |
|||
value = models.CharField(max_length=500, verbose_name=_('value')) |
|||
book_references = models.ManyToManyField( |
|||
BookReference, |
|||
related_name='attributes', |
|||
verbose_name=_('book references'), |
|||
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 = _('Book Attribute') |
|||
verbose_name_plural = _('Book Attributes') |
|||
ordering = ['title'] |
|||
|
|||
def __str__(self): |
|||
return f"{self.title}: {self.value}" |
|||
@ -1,16 +1,15 @@ |
|||
from django.urls import path |
|||
from .views.category import HadisSectListView, HadisCategoryTreeView |
|||
from .views.hadis import HadisListView, HadisDetailView |
|||
from .views.category import HadisCategorySectListView, HadisCategoryTreeView |
|||
from .views.hadis import HadisCollectionListView, HadisListView, HadisDetailView, HadisSyncView |
|||
from .views.info import HadisInfoView |
|||
|
|||
|
|||
urlpatterns = [ |
|||
# Hadis Sect endpoints |
|||
path('categories/', HadisSectListView.as_view(), name='hadis-sect-list'), |
|||
|
|||
# Hadis Category endpoints |
|||
path('categories/<int:sect_id>/', HadisCategoryTreeView.as_view(), name='hadis-category-tree'), |
|||
|
|||
# Hadis endpoints |
|||
path('<int:category_id>/hadis/', HadisListView.as_view(), name='hadis-list'), |
|||
path('hadis/<int:hadis_id>/', HadisDetailView.as_view(), name='hadis-detail'), |
|||
path('collections/', HadisCollectionListView.as_view(), name='hadis-collection-list'), |
|||
path('sync/sects/', HadisCategorySectListView.as_view(), name='hadis-sect-list'), |
|||
path('sync/categories/tree/', HadisCategoryTreeView.as_view(), name='hadis-category-tree'), |
|||
path('sync/hadis/', HadisSyncView.as_view(), name='hadis-sync'), |
|||
path('info/', HadisInfoView.as_view(), name='hadis-info'), |
|||
path('category/<int:category_id>/', HadisListView.as_view(), name='hadis-list'), |
|||
path('<int:hadis_id>/', HadisDetailView.as_view(), name='hadis-detail'), |
|||
] |
|||
@ -1,3 +1,3 @@ |
|||
from .category import * |
|||
from .hadis import * |
|||
# from .transmitter import * |
|||
from .info import * |
|||
@ -0,0 +1,30 @@ |
|||
from rest_framework.views import APIView |
|||
from rest_framework.response import Response |
|||
from rest_framework import status |
|||
|
|||
from ..models import HadisSect, BookReference, Transmitters |
|||
from apps.bookmark.models import Bookmark |
|||
|
|||
|
|||
class HadisInfoView(APIView): |
|||
""" |
|||
API view to get hadis statistics |
|||
""" |
|||
|
|||
def get(self, request, *args, **kwargs): |
|||
category_count = HadisSect.objects.filter(is_active=True).count() |
|||
reference_count = BookReference.objects.count() |
|||
bookmark_count = Bookmark.objects.filter( |
|||
service=Bookmark.ServiceChoices.HADITH, |
|||
status=True |
|||
).count() |
|||
narrator_count = Transmitters.objects.count() |
|||
|
|||
data = { |
|||
'category_count': category_count, |
|||
'reference_count': reference_count, |
|||
'bookmark_count': bookmark_count, |
|||
'narrator_count': narrator_count |
|||
} |
|||
|
|||
return Response(data, status=status.HTTP_200_OK) |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue