diff --git a/apps/article/management/commands/populate_article.py b/apps/article/management/commands/populate_article.py index f30ca34..884df2d 100644 --- a/apps/article/management/commands/populate_article.py +++ b/apps/article/management/commands/populate_article.py @@ -1,11 +1,11 @@ import os import random from django.core.management.base import BaseCommand -from django.core.files import File +from django.core.files.base import ContentFile from django.conf import settings -from django.db import transaction +from django.utils.text import slugify -# REPLACE 'apps.article.models' WITH YOUR ACTUAL APP PATH +# REPLACE WITH YOUR ACTUAL APP PATHS from apps.article.models import ( Article, ArticleCategory, @@ -17,107 +17,182 @@ from apps.article.models import ( ) class Command(BaseCommand): - help = 'Seeds 25 articles with categories, collections, and structured content.' + help = 'Seeds 25 rich Islamic articles (No Atomic Transaction).' def handle(self, *args, **kwargs): - self.stdout.write('Seeding Articles...') + self.stdout.write('--- Starting Rich Article Seeding ---') - # 1. Configuration - TOTAL_ARTICLES = 25 + # ========================================== + # 1. CONFIGURATION + # ========================================== + TOTAL_ARTICLES_TO_CREATE = 25 CATEGORY_IDS = [6, 7, 8, 9] - CONTENT_SOURCE_IDS = range(13, 24) # IDs 13 to 23 + PROTECTED_SLUG = "امام-حسین-ع-از-ولادت-تا-جاودانگی-تحلیلی-بر-مبانی-ق" + IMAGE_DIR = os.path.join(settings.BASE_DIR, 'seeds', 'images') - IMAGE_NAMES = [f'articl{i}.jpg' for i in range(2, 5)] - - # 2. Fetch Existing Dependencies + + # Explicit file names based on your folder structure + IMAGE_NAMES = ['articl2.jpg', 'articl3.jpg', 'articl4.jpg'] + + RICH_DATA = [ + { + 'title_en': 'The Event of Ghadir Khumm: Completion of Religion', + 'title_fa': 'واقعه غدیر خم: کمال دین و اتمام نعمت', + 'summary': 'An analysis of the sermon of the Prophet (PBUH) at Ghadir Khumm.', + 'arabic': 'مَنْ كُنْتُ مَوْلَاهُ فَهَذَا عَلِيٌّ مَوْلَاهُ', + 'trans': 'For whomever I am his Master (Mawla), then Ali is his Master.', + 'content_body': 'The event of Ghadir is one of the most undeniable historical events in Islam...' + }, + { + 'title_en': 'Lady Fatimah (SA): The Role Model for Women', + 'title_fa': 'حضرت فاطمه زهرا (س): الگوی زنان عالم', + 'summary': 'Exploring the virtues of the daughter of the Prophet.', + 'arabic': 'فَاطِمَةُ بَضْعَةٌ مِنِّي', + 'trans': 'Fatimah is a part of me.', + 'content_body': 'Lady Fatimah az-Zahra (SA) is not just a role model for women...' + }, + { + 'title_en': 'Nahj al-Balagha: The Peak of Eloquence', + 'title_fa': 'نهج البلاغه: قله فصاحت و بلاغت', + 'summary': 'A look into the sermons of Imam Ali (AS).', + 'arabic': 'سَلُونِي قَبْلَ أَنْ تَفْقِدُونِي', + 'trans': 'Ask me before you lose me.', + 'content_body': 'Nahj al-Balagha contains deep philosophical guidance...' + }, + { + 'title_en': 'The Significance of Laylat al-Qadr', + 'title_fa': 'عظمت شب قدر', + 'summary': 'Why is the Night of Power better than a thousand months?', + 'arabic': 'إِنَّا أَنزَلْنَاهُ فِي لَيْلَةِ الْقَدْرِ', + 'trans': 'Indeed, We sent the Qur\'an down during the Night of Decree.', + 'content_body': 'The Night of Qadr is the night where destiny is written...' + }, + { + 'title_en': 'Imam Mahdi (AJ): The Savior', + 'title_fa': 'امام مهدی (عج): منجی بشریت', + 'summary': 'The concept of the Awaited One in Islamic eschatology.', + 'arabic': 'بَقِيَّتُ اللَّهِ خَيْرٌ لَكُمْ', + 'trans': 'The remnant of Allah is better for you.', + 'content_body': 'The belief in a savior is universal...' + }, + { + 'title_en': 'Rights of Parents', + 'title_fa': 'حقوق والدین', + 'summary': 'Examining the status of parents.', + 'arabic': 'وَبِالْوَالِدَيْنِ إِحْسَانًا', + 'trans': 'And be good to parents.', + 'content_body': 'Respecting parents is placed immediately after worship of God...' + } + ] + + # ========================================== + # 2. CLEANUP + # ========================================== + self.stdout.write("Cleaning old articles (preserving protected one)...") + deleted_count, _ = Article.objects.exclude(slug=PROTECTED_SLUG).delete() + ArticleCollection.objects.all().delete() + self.stdout.write(self.style.WARNING(f"Deleted {deleted_count} articles.")) + + # ========================================== + # 3. PREPARE DEPENDENCIES + # ========================================== categories = list(ArticleCategory.objects.filter(id__in=CATEGORY_IDS)) - if not categories: - self.stdout.write(self.style.WARNING(f"Categories with IDs {CATEGORY_IDS} not found. Skipping category assignment.")) - - # 3. Create 4 New Collections + + collection_names = [ + ("Islamic History", "تاریخ اسلام"), + ("Theology & Beliefs", "عقاید و کلام"), + ("Ethics & Spirituality", "اخلاق و عرفان"), + ("Quran & Hadith", "قرآن و حدیث") + ] collections = [] - for i in range(1, 5): - col, created = ArticleCollection.objects.get_or_create( - title=f'Seeded Collection {i}', - defaults={ - 'slug': f'seeded-collection-{i}', - 'summary': f'This is a generated summary for collection {i}', - 'display_position': ArticleCollection.DisplayPosition.MIDDLE - } + for en_name, fa_name in collection_names: + col = ArticleCollection.objects.create( + title=en_name, + slug=slugify(en_name), + summary=f'Collection of {en_name}', + display_position=ArticleCollection.DisplayPosition.MIDDLE ) collections.append(col) - self.stdout.write(self.style.SUCCESS(f'Created/Loaded {len(collections)} collections.')) - - # 4. Fetch Source Content for Cloning - # NOTE: Since ArticleContent is a ForeignKey (One-to-Many), we cannot - # link existing content objects to new articles without removing them - # from their old articles. Instead, we will CLONE their data. - source_contents = list(ArticleContent.objects.filter(id__in=CONTENT_SOURCE_IDS)) - if not source_contents: - self.stdout.write(self.style.WARNING(f"ArticleContents with IDs 13-23 not found. Creating generic content instead.")) - - # 5. Main Loop: Create Articles - with transaction.atomic(): - for i in range(1, TOTAL_ARTICLES + 1): - title = f'Seeded Article Title {i}' - - # Create Article - article = Article.objects.create( - title=title, - description=f'Description for article {i}. Lorem ipsum dolor sit amet.', - content=f'Full content body for article {i}.', - status=True - ) - - # Assign Thumbnail (Cycle through images 1-5) - img_name = IMAGE_NAMES[(i - 1) % len(IMAGE_NAMES)] - img_path = os.path.join(IMAGE_DIR, img_name) - - if os.path.exists(img_path): + + # ========================================== + # 4. MAIN CREATION LOOP (NO ATOMIC TRANSACTION) + # ========================================== + self.stdout.write("Starting creation loop...") + + for i in range(1, TOTAL_ARTICLES_TO_CREATE + 1): + + # Select Data + data = RICH_DATA[(i - 1) % len(RICH_DATA)] + + # Construct Unique Data + final_title = f"{data['title_en']} (Vol {i})" + short_slug_base = slugify(data['title_en'])[:30] + unique_slug = f"{short_slug_base}-{i}" + + if unique_slug == PROTECTED_SLUG: + unique_slug = f"{unique_slug}-generated" + + # 1. Create Article Object + article = Article.objects.create( + title=final_title, + slug=unique_slug, + description=data['summary'], + content=data['content_body'], + status=True + ) + + # 2. Handle Image + img_name = IMAGE_NAMES[(i - 1) % len(IMAGE_NAMES)] + img_path = os.path.join(IMAGE_DIR, img_name) + + if os.path.exists(img_path): + try: with open(img_path, 'rb') as f: - article.thumbnail.save(img_name, File(f), save=True) - else: - self.stdout.write(self.style.WARNING(f"Image not found at {img_path}")) - - # Assign Categories (Randomly pick 1 or 2) - if categories: - article.categories.add(*random.sample(categories, k=random.randint(1, 2))) - - # Assign to Collections (Randomly pick 1) - random_col = random.choice(collections) - ArticleInCollection.objects.create( - article=article, - collection=random_col, - order=i - ) - - # Create Article Content (Cloning from IDs 13-23 or Generic) - if source_contents: - # Pick a random template from the requested IDs - template = random.choice(source_contents) - content_title = f"{template.title} (Copy for Article {i})" - content_body = template.content - else: - content_title = f"Structured Content for Article {i}" - content_body = "This is a section of structured content." - - # Create the ArticleContent object linked to this NEW article - ac = ArticleContent.objects.create( - article=article, - title=content_title, - content=content_body, - priority=1 - ) - - # Create nested ContentParts and TextSections - part = ContentPart.objects.create(article_content=ac, order=1) - TextSection.objects.create( - content_part=part, - arabic_text="نص تجريبي باللغة العربية", - translation="Experimental text in English translation", - order=1 - ) - - self.stdout.write(f'Created Article: {article.title}') - - self.stdout.write(self.style.SUCCESS(f'Successfully seeded {TOTAL_ARTICLES} articles!')) \ No newline at end of file + file_content = f.read() + + # NOTE: If the script hangs here, check Article.save() signals + article.thumbnail.save(img_name, ContentFile(file_content), save=True) + except Exception as e: + self.stdout.write(self.style.ERROR(f"Error saving image for item {i}: {e}")) + else: + self.stdout.write(self.style.WARNING(f"Image missing: {img_path}")) + + # 3. Assign Categories + if categories: + article.categories.add(*random.sample(categories, k=random.randint(1, 2))) + + # 4. Assign Collection + random_col = random.choice(collections) + ArticleInCollection.objects.create( + article=article, + collection=random_col, + order=i + ) + + # 5. Create Content Hierarchy + ac = ArticleContent.objects.create( + article=article, + title=data['title_fa'], + content=data['content_body'], + priority=1 + ) + + part = ContentPart.objects.create(article_content=ac, order=1) + + TextSection.objects.create( + content_part=part, + arabic_text=data['arabic'], + translation=data['trans'], + order=1 + ) + + TextSection.objects.create( + content_part=part, + arabic_text=f"شرح: {data['title_fa']}", + translation=f"Commentary on {data['title_en']}", + order=2 + ) + + self.stdout.write(f"[{i}/{TOTAL_ARTICLES_TO_CREATE}] Created: {unique_slug}") + + self.stdout.write(self.style.SUCCESS(f'Successfully seeded {TOTAL_ARTICLES_TO_CREATE} rich articles.')) \ No newline at end of file diff --git a/entrypoint.sh b/entrypoint.sh index 7b77cb3..c990fdb 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -7,6 +7,7 @@ python manage.py collectstatic --noinput # python manage.py populate_books # python manage.py populate_book_reference -python manage.py populate_refrence_images +# python manage.py populate_refrence_images +python manage.py populate_article exec "$@" diff --git a/seeds/images/articl3.jpg b/seeds/images/articl3.jpg new file mode 100644 index 0000000..bafb6a1 Binary files /dev/null and b/seeds/images/articl3.jpg differ