Browse Source

feat(article): implement structured content model for articles with bilingual text sections

- Added new models for `ContentPart` and `TextSection` to support structured article content, allowing for bilingual Arabic text and translations.
- Updated serializers to include nested text sections within content parts, enhancing the API response structure.
- Introduced a management command to seed article data with Russian content about Imams and Prophets, including categories, collections, and structured content sections.
- Enhanced the admin interface to manage text sections within content parts effectively.
- Created a sample API response for articles to demonstrate the new structure.
master
mortezaei 6 months ago
parent
commit
f0666319a3
  1. 191
      apps/api/views/documentation.py
  2. 62
      apps/article/admin.py
  3. 0
      apps/article/management/__init__.py
  4. 0
      apps/article/management/commands/__init__.py
  5. 445
      apps/article/management/commands/seed_article_data.py
  6. 47
      apps/article/migrations/0003_alter_middlearticlecollection_options_and_more.py
  7. 36
      apps/article/models.py
  8. 13
      apps/article/serializers.py
  9. 1
      apps/hadis/models/category.py

191
apps/api/views/documentation.py

@ -831,5 +831,196 @@ class CustomAPIDocumentationView(View):
}
}
]
},
'article': {
'name': 'Articles',
'description': 'Articles with structured content including Arabic texts and translations',
'endpoints': [
{
'name': 'Article Categories',
'method': 'GET',
'url': '/api/article/categories/',
'description': 'Get list of all active article categories',
'parameters': [],
'response_examples': {
'success': json.dumps([
{
"id": 1,
"title": "Категория статей",
"slug": "category-1",
"acticle_count": 12
}
], indent=2)
}
},
{
'name': 'Pinned Collections',
'method': 'GET',
'url': '/api/article/pinned-collections/',
'description': 'Get pinned article collections',
'parameters': [],
'response_examples': {
'success': json.dumps({
"count": 3,
"info": {
"categories_count": 4,
"bookmarks_count": 12
},
"results": [
{
"id": 1,
"title": "Избранные статьи",
"slug": "featured-articles",
"summary": "Лучшие статьи",
"thumbnail": "https://example.com/media/collections/thumb1.jpg",
"order": 1,
"created_at": "2025-01-15T10:30:00Z"
}
]
}, indent=2)
}
},
{
'name': 'Article Collections',
'method': 'GET',
'url': '/api/article/collections/',
'description': 'Get article collections with articles',
'parameters': [],
'response_examples': {
'success': json.dumps({
"count": 5,
"results": [
{
"id": 1,
"title": "Коллекция статей об имамах",
"slug": "imams-collection",
"summary": "Статьи о жизни имамов",
"articles": [
{
"id": 1,
"title": "Имам Джавад (мир ему)",
"slug": "imam-javad",
"thumbnail": "https://example.com/media/articles/thumb1.jpg",
"description": "Краткая биография",
"view_count": 234,
"created_at": "2025-01-15T10:30:00Z"
}
]
}
]
}, indent=2)
}
},
{
'name': 'Article List',
'method': 'GET',
'url': '/api/article/list/',
'description': 'Get paginated list of articles with filtering',
'parameters': [
{'name': 'category', 'type': 'string', 'description': 'Filter by category slug', 'required': False},
{'name': 'collection', 'type': 'string', 'description': 'Filter by collection slug', 'required': False},
{'name': 'is_bookmark', 'type': 'boolean', 'description': 'Filter bookmarked articles', 'required': False},
{'name': 'search', 'type': 'string', 'description': 'Search in article titles', 'required': False},
],
'response_examples': {
'success': json.dumps({
"count": 45,
"results": [
{
"id": 1,
"title": "Имам Джавад (мир ему)",
"slug": "imam-javad",
"thumbnail": "https://example.com/media/articles/thumb1.jpg",
"description": "Краткая биография девятого имама",
"view_count": 234,
"created_at": "2025-01-15T10:30:00Z"
}
]
}, indent=2)
}
},
{
'name': 'Article Detail',
'method': 'GET',
'url': '/api/article/detail/<slug:slug>/',
'description': 'Get detailed article with structured content including Arabic texts and translations',
'parameters': [
{'name': 'slug', 'type': 'string', 'description': 'Article slug', 'required': True},
],
'response_examples': {
'success': json.dumps({
"id": 1,
"title": "Имам Джавад (мир ему)",
"slug": "imam-javad",
"thumbnail": "https://example.com/media/articles/thumb1.jpg",
"description": "Краткая биография",
"article_file": "https://example.com/media/articles/document.pdf",
"view_count": 235,
"download_count": 45,
"categories": [
{
"id": 1,
"title": "Имамы",
"slug": "imams",
"acticle_count": 12
}
],
"created_at": "2025-01-15T10:30:00Z",
"user_rate": {
"is_rated": True,
"rate": 5
},
"average_rate": {
"average": 4.5,
"count": 23
},
"bookmark": True,
"article_content": [
{
"id": 1,
"title": "Введение",
"content": "Краткое введение о теме",
"priority": 1,
"status": True,
"created_at": "2025-01-15T10:30:00Z",
"updated_at": "2025-01-15T10:30:00Z",
"parts": [
{
"id": 1,
"order": 1,
"text_sections": [
{
"id": 1,
"arabic_text": "بِسْمِ اللَّهِ الرَّحْمَٰنِ الرَّحِيمِ",
"translation": "Во имя Аллаха, Милостивого, Милосердного",
"order": 1
},
{
"id": 2,
"arabic_text": "الْحَمْدُ لِلَّهِ رَبِّ الْعَالَمِينَ",
"translation": "Хвала Аллаху, Господу миров",
"order": 2
}
]
},
{
"id": 2,
"order": 2,
"text_sections": [
{
"id": 3,
"arabic_text": "الرَّحْمَٰنِ الرَّحِيمِ",
"translation": "Милостивому, Милосердному",
"order": 1
}
]
}
]
}
]
}, indent=2)
}
}
]
}
}

62
apps/article/admin.py

@ -17,14 +17,15 @@ from utils.admin import project_admin_site
from unfold.sections import TableSection
from apps.article.models import (
ArticleCategory,
ArticleCollection,
PinnedArticleCollection,
MiddleArticleCollection,
Article,
ArticleCategory,
ArticleCollection,
PinnedArticleCollection,
MiddleArticleCollection,
Article,
ArticleInCollection,
ArticleContent,
ContentPart
ContentPart,
TextSection
)
@ -39,13 +40,22 @@ class ArticleInCollectionInline(TabularInline):
tab = True
class TextSectionInline(TabularInline):
model = TextSection
extra = 1
fields = ('arabic_text', 'translation', 'order')
ordering = ('order',)
verbose_name = _('Text Section')
verbose_name_plural = _('Text Sections')
class ContentPartInline(StackedInline):
model = ContentPart
extra = 1
fields = ('arabic_text', 'translation', 'order')
fields = ('order',)
ordering = ('order',)
verbose_name = _('Content Part')
verbose_name_plural = _('Content Parts')
tab = True
class ArticleContentInline(StackedInline):
@ -254,22 +264,49 @@ class ArticleContentAdmin(ModelAdmin):
search_fields = ('title', 'content')
autocomplete_fields = ('article',)
inlines = [ContentPartInline]
actions_row = [
"action_parts",
]
fieldsets = (
(None, {
'fields': ('article', 'title', 'content', 'priority', 'status')
}),
)
def get_changeform_initial_data(self, request):
initial = super().get_changeform_initial_data(request)
if 'article__id__exact' in request.GET:
initial['article'] = request.GET.get('article__id__exact')
return initial
# @display(description=_("Content Title"), header=True)
# def display_header(self, obj):
# return str(obj.title)
@action(
description=_("Parts"),
url_path="actions-row-parts-url",
)
def action_parts(self, request, object_id):
content = get_object_or_404(ArticleContent, pk=object_id)
url = reverse('admin:article_contentpart_changelist') + f'?article_content__id__exact={content.id}'
return redirect(url)
class ContentPartAdmin(ModelAdmin):
list_display = ('article_content', 'order', 'created_at')
list_filter = ('created_at',)
autocomplete_fields = ('article_content',)
inlines = [TextSectionInline]
fieldsets = (
(None, {
'fields': ('article_content', 'order')
}),
)
def get_changeform_initial_data(self, request):
initial = super().get_changeform_initial_data(request)
if 'article_content__id__exact' in request.GET:
initial['article_content'] = request.GET.get('article_content__id__exact')
return initial
# Register models with admin site
@ -278,3 +315,4 @@ project_admin_site.register(PinnedArticleCollection, PinnedArticleCollectionAdmi
project_admin_site.register(MiddleArticleCollection, MiddleArticleCollectionAdmin)
project_admin_site.register(Article, ArticleAdmin)
project_admin_site.register(ArticleContent, ArticleContentAdmin)
project_admin_site.register(ContentPart, ContentPartAdmin)

0
apps/article/management/__init__.py

0
apps/article/management/commands/__init__.py

445
apps/article/management/commands/seed_article_data.py

@ -0,0 +1,445 @@
from django.core.management.base import BaseCommand
from django.utils.text import slugify
from apps.article.models import (
ArticleCategory,
ArticleCollection,
Article,
ArticleContent,
ContentPart,
TextSection
)
class Command(BaseCommand):
help = 'Seed article data with Russian content about Imams and Prophets'
def handle(self, *args, **kwargs):
self.stdout.write(self.style.SUCCESS('Starting to seed article data...'))
# Create categories
self.stdout.write('Creating categories...')
categories = self.create_categories()
# Create collections
self.stdout.write('Creating collections...')
collections = self.create_collections()
# Create articles
self.stdout.write('Creating articles...')
articles = self.create_articles(categories, collections)
self.stdout.write(self.style.SUCCESS(f'Successfully created {len(articles)} articles with content!'))
def create_categories(self):
categories_data = [
{'title': 'Пророки', 'order': 1},
{'title': 'Имамы', 'order': 2},
{'title': 'Жизнь имамов', 'order': 3},
{'title': 'Исламская история', 'order': 4},
]
categories = []
for cat_data in categories_data:
category, created = ArticleCategory.objects.get_or_create(
title=cat_data['title'],
defaults={
'slug': slugify(cat_data['title'], allow_unicode=True),
'order': cat_data['order'],
'status': True
}
)
categories.append(category)
if created:
self.stdout.write(f' Created category: {category.title}')
return categories
def create_collections(self):
collections_data = [
{
'title': 'Избранные статьи об имамах',
'summary': 'Лучшие статьи о жизни и учениях имамов',
'pin_top': True,
'display_position': 'pinned',
'order': 1
},
{
'title': 'Коллекция о пророках',
'summary': 'Статьи о пророках в исламе',
'pin_top': False,
'display_position': 'middle',
'order': 2
},
]
collections = []
for coll_data in collections_data:
collection, created = ArticleCollection.objects.get_or_create(
title=coll_data['title'],
defaults={
'slug': slugify(coll_data['title'], allow_unicode=True),
'summary': coll_data['summary'],
'pin_top': coll_data['pin_top'],
'display_position': coll_data['display_position'],
'order': coll_data['order'],
'status': True
}
)
collections.append(collection)
if created:
self.stdout.write(f' Created collection: {collection.title}')
return collections
def create_articles(self, categories, collections):
articles_data = [
{
'title': 'Имам Джавад (мир ему)',
'description': 'Биография девятого имама шиитов, Мухаммада ибн Али аль-Джавада',
'categories': [categories[1]], # Имамы
'collections': [collections[0]],
'content_sections': [
{
'title': 'Введение',
'content': 'Имам Мухаммад ибн Али аль-Джавад - девятый имам двенадцати имамов в шиитском исламе.\nОн родился в 195 году хиджры в Медине и стал имамом в очень молодом возрасте.\nЕго жизнь полна уроков мудрости и знания.',
'priority': 1,
'parts': [
{
'order': 1,
'text_sections': [
{
'arabic_text': 'بِسْمِ اللَّهِ الرَّحْمَٰنِ الرَّحِيمِ',
'translation': 'Во имя Аллаха, Милостивого, Милосердного',
'order': 1
},
{
'arabic_text': 'الْحَمْدُ لِلَّهِ رَبِّ الْعَالَمِينَ وَالصَّلَاةُ وَالسَّلَامُ عَلَىٰ سَيِّدِنَا مُحَمَّدٍ وَآلِهِ الطَّاهِرِينَ',
'translation': 'Хвала Аллаху, Господу миров, и мир и благословение нашему господину Мухаммаду и его пречистому семейству',
'order': 2
}
]
}
]
},
{
'title': 'Рождение и детство',
'content': 'Имам Джавад родился в 195 году хиджры (811 г. н.э.) в священном городе Медина.\nЕго отцом был имам Али ибн Муса ар-Рида, восьмой имам.\nОн стал имамом в возрасте семи или девяти лет после мученической смерти своего отца.',
'priority': 2,
'parts': [
{
'order': 1,
'text_sections': [
{
'arabic_text': 'وُلِدَ الإِمَامُ مُحَمَّدُ بْنُ عَلِيٍّ الْجَوَادُ فِي الْمَدِينَةِ الْمُنَوَّرَةِ',
'translation': 'Имам Мухаммад ибн Али аль-Джавад родился в благословенной Медине',
'order': 1
},
{
'arabic_text': 'فِي سَنَةِ خَمْسٍ وَتِسْعِينَ وَمِائَةٍ مِنَ الْهِجْرَةِ',
'translation': 'В сто девяносто пятом году хиджры',
'order': 2
}
]
},
{
'order': 2,
'text_sections': [
{
'arabic_text': 'وَكَانَ عُمْرُهُ عِنْدَ الإِمَامَةِ سَبْعَ سِنِينَ أَوْ تِسْعَ سِنِينَ',
'translation': 'Ему было семь или девять лет, когда он стал имамом',
'order': 1
}
]
}
]
},
{
'title': 'Знания и мудрость',
'content': 'Несмотря на свой молодой возраст, имам Джавад проявлял невероятные знания и мудрость.\nОн отвечал на сложные вопросы ученых и удивлял их своей проницательностью.\nЕго называли "аль-Джавад" (щедрый) за его великодушие и знания.',
'priority': 3,
'parts': [
{
'order': 1,
'text_sections': [
{
'arabic_text': 'كَانَ الإِمَامُ الْجَوَادُ آيَةً فِي الْعِلْمِ وَالْحِكْمَةِ',
'translation': 'Имам Джавад был знамением в знании и мудрости',
'order': 1
},
{
'arabic_text': 'وَأَجَابَ عَلَى أَسْئِلَةِ الْعُلَمَاءِ وَهُوَ صَغِيرُ السِّنِّ',
'translation': 'Он отвечал на вопросы ученых в юном возрасте',
'order': 2
}
]
}
]
}
]
},
{
'title': 'Пророк Мухаммад (да благословит его Аллах)',
'description': 'Жизнь и миссия последнего пророка Аллаха, печати пророков',
'categories': [categories[0], categories[3]], # Пророки, Исламская история
'collections': [collections[1]],
'content_sections': [
{
'title': 'Введение о Пророке',
'content': 'Мухаммад ибн Абдуллах - последний пророк и посланник Аллаха.\nОн родился в Мекке в 570 году н.э. в племени курайш.\nЕго послание является последним откровением для всего человечества.',
'priority': 1,
'parts': [
{
'order': 1,
'text_sections': [
{
'arabic_text': 'مُحَمَّدٌ رَسُولُ اللَّهِ وَخَاتَمُ النَّبِيِّينَ',
'translation': 'Мухаммад - посланник Аллаха и печать пророков',
'order': 1
},
{
'arabic_text': 'وَمَا أَرْسَلْنَاكَ إِلَّا رَحْمَةً لِّلْعَالَمِينَ',
'translation': 'Мы послали тебя только как милость для миров',
'order': 2
}
]
}
]
},
{
'title': 'Начало откровения',
'content': 'В возрасте сорока лет пророк Мухаммад получил первое откровение в пещере Хира.\nАнгел Джибриль (Гавриил) явился ему с первыми аятами Корана.\nЭто было началом его пророческой миссии, которая продлилась 23 года.',
'priority': 2,
'parts': [
{
'order': 1,
'text_sections': [
{
'arabic_text': 'اقْرَأْ بِاسْمِ رَبِّكَ الَّذِي خَلَقَ',
'translation': 'Читай во имя Господа твоего, Который сотворил',
'order': 1
},
{
'arabic_text': 'خَلَقَ الْإِنسَانَ مِنْ عَلَقٍ',
'translation': 'Сотворил человека из сгустка',
'order': 2
},
{
'arabic_text': 'اقْرَأْ وَرَبُّكَ الْأَكْرَمُ',
'translation': 'Читай, ведь твой Господь - Самый великодушный',
'order': 3
}
]
}
]
}
]
},
{
'title': 'Имам Али (мир ему)',
'description': 'Первый имам и двоюродный брат пророка Мухаммада, врата знания',
'categories': [categories[1], categories[2]], # Имамы, Жизнь имамов
'collections': [collections[0]],
'content_sections': [
{
'title': 'Али - врата знания',
'content': 'Имам Али ибн Абу Талиб - первый имам шиитов и четвертый праведный халиф.\nОн был двоюродным братом и зятем пророка Мухаммада.\nПророк сказал о нем: "Я - город знания, а Али - его врата".',
'priority': 1,
'parts': [
{
'order': 1,
'text_sections': [
{
'arabic_text': 'عَلِيٌّ مَعَ الْحَقِّ وَالْحَقُّ مَعَ عَلِيٍّ',
'translation': 'Али с истиной, и истина с Али',
'order': 1
},
{
'arabic_text': 'أَنَا مَدِينَةُ الْعِلْمِ وَعَلِيٌّ بَابُهَا',
'translation': 'Я - город знания, а Али - его врата',
'order': 2
}
]
}
]
},
{
'title': 'Мудрость имама Али',
'content': 'Имам Али известен своими мудрыми изречениями и наставлениями.\nЕго книга "Нахдж аль-Балага" содержит его проповеди, письма и изречения.\nОн был образцом справедливости, храбрости и знания.',
'priority': 2,
'parts': [
{
'order': 1,
'text_sections': [
{
'arabic_text': 'النَّاسُ أَعْدَاءُ مَا جَهِلُوا',
'translation': 'Люди - враги того, чего они не знают',
'order': 1
},
{
'arabic_text': 'الْعِلْمُ خَيْرُ مِنَ الْمَالِ',
'translation': 'Знание лучше, чем богатство',
'order': 2
}
]
},
{
'order': 2,
'text_sections': [
{
'arabic_text': 'قِيمَةُ كُلِّ امْرِئٍ مَا يُحْسِنُهُ',
'translation': 'Ценность каждого человека в том, что он хорошо делает',
'order': 1
}
]
}
]
}
]
},
{
'title': 'Имам Хусейн (мир ему)',
'description': 'Третий имам и внук пророка Мухаммада, мученик Кербелы',
'categories': [categories[1], categories[2]],
'collections': [collections[0]],
'content_sections': [
{
'title': 'Жертвоприношение в Кербеле',
'content': 'Имам Хусейн ибн Али - третий имам шиитов и внук пророка Мухаммада.\nОн принял мученичество в Кербеле в 680 году н.э., защищая истину и справедливость.\nЕго жертва стала символом борьбы против тирании и несправедливости.',
'priority': 1,
'parts': [
{
'order': 1,
'text_sections': [
{
'arabic_text': 'إِنِّي لَا أَرَى الْمَوْتَ إِلَّا سَعَادَةً وَالْحَيَاةَ مَعَ الظَّالِمِينَ إِلَّا بَرَمًا',
'translation': 'Я не вижу смерть иначе как счастье, а жизнь с угнетателями - иначе как несчастье',
'order': 1
}
]
}
]
},
{
'title': 'Послание Кербелы',
'content': 'Восстание имама Хусейна не было военным восстанием, а духовной революцией.\nОн выступил против несправедливости и коррупции правителя Язида.\nЕго послание остается актуальным для всех поколений: "Если у вас нет религии, то хотя бы будьте свободными".',
'priority': 2,
'parts': [
{
'order': 1,
'text_sections': [
{
'arabic_text': 'هَيْهَاتَ مِنَّا الذِّلَّةُ',
'translation': 'Далеки мы от унижения',
'order': 1
},
{
'arabic_text': 'مَوْتٌ فِي عِزٍّ خَيْرٌ مِنْ حَيَاةٍ فِي ذُلٍّ',
'translation': 'Смерть в достоинстве лучше жизни в унижении',
'order': 2
}
]
}
]
}
]
},
{
'title': 'Пророк Иса (мир ему)',
'description': 'Иисус, сын Марии, один из великих пророков в исламе',
'categories': [categories[0]],
'collections': [collections[1]],
'content_sections': [
{
'title': 'Пророк Иса в исламе',
'content': 'Иса ибн Марьям (Иисус) - один из величайших пророков в исламе.\nОн был рожден чудесным образом от девы Марии по воле Аллаха.\nМусульмане почитают его как пророка и посланника Бога, но не как Бога.',
'priority': 1,
'parts': [
{
'order': 1,
'text_sections': [
{
'arabic_text': 'إِنَّ مَثَلَ عِيسَىٰ عِندَ اللَّهِ كَمَثَلِ آدَمَ ۖ خَلَقَهُ مِن تُرَابٍ ثُمَّ قَالَ لَهُ كُن فَيَكُونُ',
'translation': 'Воистину, Иса перед Аллахом подобен Адаму. Он сотворил его из праха, потом сказал ему: "Будь!" - и тот возник',
'order': 1
}
]
}
]
},
{
'title': 'Чудеса пророка Исы',
'content': 'Аллах даровал пророку Исе множество чудес в подтверждение его пророчества.\nОн исцелял больных, воскрешал мертвых и говорил с людьми еще в колыбели.\nВсе эти чудеса происходили по воле и дозволению Аллаха.',
'priority': 2,
'parts': [
{
'order': 1,
'text_sections': [
{
'arabic_text': 'وَأُبْرِئُ الْأَكْمَهَ وَالْأَبْرَصَ وَأُحْيِي الْمَوْتَىٰ بِإِذْنِ اللَّهِ',
'translation': 'Я исцеляю слепого и прокаженного и оживляю мертвых с дозволения Аллаха',
'order': 1
}
]
}
]
}
]
}
]
articles = []
for article_data in articles_data:
# Create or get article
article, created = Article.objects.get_or_create(
title=article_data['title'],
defaults={
'slug': slugify(article_data['title'], allow_unicode=True),
'description': article_data['description'],
'status': True
}
)
if created:
self.stdout.write(f' Created article: {article.title}')
# Add categories
article.categories.set(article_data['categories'])
# Add to collections
for collection in article_data['collections']:
from apps.article.models import ArticleInCollection
ArticleInCollection.objects.get_or_create(
collection=collection,
article=article,
defaults={'order': 1}
)
# Create content sections
for content_data in article_data['content_sections']:
article_content = ArticleContent.objects.create(
article=article,
title=content_data['title'],
content=content_data['content'],
priority=content_data['priority'],
status=True
)
# Create parts
for part_data in content_data['parts']:
content_part = ContentPart.objects.create(
article_content=article_content,
order=part_data['order']
)
# Create text sections
for section_data in part_data['text_sections']:
TextSection.objects.create(
content_part=content_part,
arabic_text=section_data['arabic_text'],
translation=section_data['translation'],
order=section_data['order']
)
articles.append(article)
return articles

47
apps/article/migrations/0003_alter_middlearticlecollection_options_and_more.py

@ -0,0 +1,47 @@
# Generated by Django 5.1.8 on 2025-12-02 16:17
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('article', '0002_article_download_count'),
]
operations = [
migrations.AlterModelOptions(
name='middlearticlecollection',
options={'verbose_name': 'Regular Collection (Middle Section)', 'verbose_name_plural': 'Regular Collections (Middle Section)'},
),
migrations.AlterModelOptions(
name='pinnedarticlecollection',
options={'verbose_name': 'Pinned Collection (Top Section)', 'verbose_name_plural': 'Pinned Collections (Top Section)'},
),
migrations.RemoveField(
model_name='contentpart',
name='arabic_text',
),
migrations.RemoveField(
model_name='contentpart',
name='translation',
),
migrations.CreateModel(
name='TextSection',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('arabic_text', models.TextField(verbose_name='Arabic text')),
('translation', models.TextField(verbose_name='Translation')),
('order', models.PositiveIntegerField(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')),
('content_part', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='text_sections', to='article.contentpart', verbose_name='content part')),
],
options={
'verbose_name': 'Text Section',
'verbose_name_plural': 'Text Sections',
'ordering': ['order'],
},
),
]

36
apps/article/models.py

@ -184,7 +184,7 @@ class ArticleContent(models.Model):
class ContentPart(models.Model):
"""
Model for bilingual content parts (Arabic text and translation)
Model for content parts - each part can have multiple text sections
"""
article_content = models.ForeignKey(
ArticleContent,
@ -192,20 +192,44 @@ class ContentPart(models.Model):
related_name='parts',
verbose_name=_('article content')
)
arabic_text = models.TextField(verbose_name=_('Arabic text'))
translation = models.TextField(verbose_name=_('Translation'))
order = models.PositiveIntegerField(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:
verbose_name = _('Content Part')
verbose_name_plural = _('Content Parts')
ordering = ['order']
def __str__(self):
return f"{self.article_content.title} - Part {self.order}"
class TextSection(models.Model):
"""
Model for bilingual text sections (Arabic text and translation) within a content part
"""
content_part = models.ForeignKey(
ContentPart,
on_delete=models.CASCADE,
related_name='text_sections',
verbose_name=_('content part')
)
arabic_text = models.TextField(verbose_name=_('Arabic text'))
translation = models.TextField(verbose_name=_('Translation'))
order = models.PositiveIntegerField(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:
verbose_name = _('Text Section')
verbose_name_plural = _('Text Sections')
ordering = ['order']
def __str__(self):
return f"{self.content_part} - Section {self.order}"

13
apps/article/serializers.py

@ -52,13 +52,20 @@ class ArticleListSerializer(serializers.ModelSerializer):
return get_thumbs(obj.thumbnail, self.context.get('request'))
class TextSectionSerializer(serializers.ModelSerializer):
class Meta:
model = TextSection
fields = ['id', 'arabic_text', 'translation', 'order']
class ContentPartSerializer(serializers.ModelSerializer):
text_sections = TextSectionSerializer(many=True, read_only=True)
class Meta:
model = ContentPart
fields = ['id', 'arabic_text', 'translation', 'order', 'created_at', 'updated_at']
fields = ['id', 'order', 'text_sections']
class ArticleContentSerializer(serializers.ModelSerializer):
parts = ContentPartSerializer(many=True, read_only=True)
parts = ContentPartSerializer(many=True, read_only=True)
class Meta:
model = ArticleContent
fields = ['id', 'title', 'content', 'priority', 'status', 'created_at', 'updated_at', 'parts']

1
apps/hadis/models/category.py

@ -11,6 +11,7 @@ class HadisSect(models.Model):
sect_type = models.CharField(max_length=10, choices=SectType.choices, unique=True, verbose_name=_('Sect Name'))
title = models.CharField(max_length=256, verbose_name=_('Name'))
description = models.TextField(verbose_name=_('Description'), null=True, blank=True)
is_active = models.BooleanField(default=True, verbose_name=_('Is Active'))
order = models.IntegerField(default=0, verbose_name=_('order'))

Loading…
Cancel
Save