diff --git a/apps/article/models.py b/apps/article/models.py index 28a065b..1c45c72 100755 --- a/apps/article/models.py +++ b/apps/article/models.py @@ -1,5 +1,6 @@ from django.db import models from django.utils.translation import gettext_lazy as _ +from django.conf import settings from utils import generate_slug_for_model @@ -111,6 +112,12 @@ class Article(models.Model): def __str__(self): return self.title + @property + def share_link(self): + if self.slug: + return f"{settings.DOVODI_DOMAIN}/articles/{self.slug}" + return None + def increment_view_count(self): self.view_count += 1 self.save(update_fields=['view_count']) diff --git a/apps/article/serializers.py b/apps/article/serializers.py index 8a50354..4068e61 100644 --- a/apps/article/serializers.py +++ b/apps/article/serializers.py @@ -44,10 +44,11 @@ class MiddleArticleCollectionSerializer(serializers.ModelSerializer): class ArticleListSerializer(serializers.ModelSerializer): thumbnail = serializers.SerializerMethodField() categories = ArticleCategoryListSerializer(many=True, read_only=True) + share_link = serializers.CharField(read_only=True) class Meta: model = Article - fields = ['id', 'title', 'slug', 'thumbnail', 'description', 'view_count', 'created_at', 'categories'] + fields = ['id', 'title', 'slug', 'thumbnail', 'description', 'view_count', 'created_at', 'categories', 'share_link'] def get_thumbnail(self, obj): return get_thumbs(obj.thumbnail, self.context.get('request')) @@ -78,12 +79,13 @@ class ArticleDetailSerializer(serializers.ModelSerializer): user_rate = serializers.SerializerMethodField() average_rate = serializers.SerializerMethodField() article_content = serializers.SerializerMethodField() + share_link = serializers.CharField(read_only=True) class Meta: model = Article fields = ['id', 'title', 'slug', 'thumbnail', 'description', 'article_file', 'view_count', 'download_count', - 'categories', 'created_at', 'user_rate', 'average_rate', 'bookmark', 'article_content'] + 'categories', 'created_at', 'user_rate', 'average_rate', 'bookmark', 'article_content', 'share_link'] def get_thumbnail(self, obj): return get_thumbs(obj.thumbnail, self.context.get('request')) diff --git a/apps/hadis/management/commands/refresh_share_links.py b/apps/hadis/management/commands/refresh_share_links.py new file mode 100644 index 0000000..8a6cf27 --- /dev/null +++ b/apps/hadis/management/commands/refresh_share_links.py @@ -0,0 +1,44 @@ +from django.core.management.base import BaseCommand +from django.db import transaction +from apps.hadis.models import Hadis, HadisCorrection, TransmitterOriginalText + +class Command(BaseCommand): + help = 'Refreshes slugs and share_links by re-saving all existing instances of Hadis models.' + + def handle(self, *args, **options): + models_to_refresh = [ + (Hadis, "Hadis"), + (HadisCorrection, "Hadis Correction"), + (TransmitterOriginalText, "Transmitter Original Text"), + ] + + for model_class, name in models_to_refresh: + self.stdout.write(self.style.WARNING(f'--- Processing {name} ---')) + + instances = model_class.objects.all() + count = instances.count() + + if count == 0: + self.stdout.write(f"No {name} instances found.") + continue + + processed = 0 + # Using transaction.atomic to ensure database integrity + with transaction.atomic(): + for instance in instances: + try: + # This triggers your updated .save() logic + instance.save() + processed += 1 + + if processed % 100 == 0: + self.stdout.write(f"Updated {processed}/{count} {name}...") + + except Exception as e: + self.stdout.write(self.style.ERROR( + f"Error saving {name} ID {instance.id}: {str(e)}" + )) + + self.stdout.write(self.style.SUCCESS(f'Successfully refreshed {processed} {name} instances.')) + + self.stdout.write(self.style.SUCCESS('--- All models refreshed successfully ---')) \ No newline at end of file diff --git a/apps/hadis/models/hadis.py b/apps/hadis/models/hadis.py index 5ec922d..cb4a568 100644 --- a/apps/hadis/models/hadis.py +++ b/apps/hadis/models/hadis.py @@ -173,13 +173,13 @@ class HadisStatus(models.Model): GRAY = 'gray', _('Gray') COLOR_PALETTE = { - 'red': {'main': '#E74C3C', 'light': '#FADBD8'}, - 'green': {'main': '#27AE60', 'light': '#D5F5E3'}, - 'blue': {'main': '#2980B9', 'light': '#D4E6F1'}, - 'yellow': {'main': '#F1C40F', 'light': '#FCF3CF'}, - 'orange': {'main': '#E67E22', 'light': '#FAE5D3'}, - 'purple': {'main': '#8E44AD', 'light': '#EBDEF0'}, - 'gray': {'main': '#7F8C8D', 'light': '#E5E8E8'}, + 'red': {'main': '#D33A3A', 'light': '#FBEBEB'}, + 'green': {'main': '#1DAC43', 'light': '#E8F7EC'}, + 'blue': {'main': '#5172E1', 'light': '#FAFBFC'}, + 'yellow': {'main': '#EDC130', 'light': '#FCF8EA'}, + 'orange': {'main': '#E67E22', 'light': '#FDF1E6'}, + 'purple': {'main': '#7C5CC4', 'light': '#F2EDFA'}, + 'gray': {'main': '#374151', 'light': '#EEEFF2'}, } title = models.JSONField(default = list , verbose_name=_('Title')) @@ -354,9 +354,10 @@ class Hadis(models.Model): counter += 1 self.slug = base_slug - # Generate share_link before saving - if not self.share_link and self.slug: - self.share_link = f"{settings.SITE_DOMAIN}/hadis/{self.slug}" + # Generate/update share_link before saving + if self.slug: + category_slug = self.category.slug if self.category and self.category.slug else 'uncategorized' + self.share_link = f"{settings.DOVODI_DOMAIN}/arguments/hadith/{category_slug}/{self.slug}" super().save(*args, **kwargs) @@ -555,9 +556,10 @@ class HadisCorrection(models.Model): counter += 1 self.slug = base_slug - # Generate share_link before saving - if not self.share_link and self.slug and self.hadis and self.hadis.slug: - self.share_link = f"{settings.SITE_DOMAIN}/hadis/{self.hadis.slug}/corrections/{self.slug}" + # Generate/update share_link before saving + if self.slug and self.hadis and self.hadis.slug: + category_slug = self.hadis.category.slug if self.hadis.category and self.hadis.category.slug else 'uncategorized' + self.share_link = f"{settings.DOVODI_DOMAIN}/arguments/hadith/{category_slug}/{self.hadis.slug}/corrections/{self.slug}" super().save(*args, **kwargs) diff --git a/apps/hadis/models/reference.py b/apps/hadis/models/reference.py index 5b74fa6..0981a5b 100644 --- a/apps/hadis/models/reference.py +++ b/apps/hadis/models/reference.py @@ -1,6 +1,7 @@ from django.db import models from django.utils.translation import gettext_lazy as _ from django.utils.text import slugify +from django.conf import settings from typing import Optional class BookSubjectArea(models.Model): @@ -121,6 +122,12 @@ class BookReference(models.Model): print(item) return item.get("text", "") if isinstance(item, dict) else None + @property + def share_link(self): + if self.slug: + return f"{settings.DOVODI_DOMAIN}/arguments/sources/{self.slug}" + return None + def get_title(self,lang): return self._get_json_field("title" , lang) diff --git a/apps/hadis/models/transmitter.py b/apps/hadis/models/transmitter.py index ea8e8f8..71cd7d4 100644 --- a/apps/hadis/models/transmitter.py +++ b/apps/hadis/models/transmitter.py @@ -296,6 +296,12 @@ class Transmitters(models.Model): verbose_name_plural = _('Transmitters') ordering = ('full_name',) + @property + def share_link(self): + if self.slug: + return f"{settings.DOVODI_DOMAIN}/arguments/narrators/{self.slug}" + return None + def save(self, *args, **kwargs): if not self.slug or (isinstance(self.slug, str) and self.slug.strip() == ''): # Try to get text from full_name field with robust error handling @@ -696,9 +702,9 @@ class TransmitterOriginalText(models.Model): counter += 1 self.slug = base_slug - # Generate share_link before saving - if not self.share_link and self.slug and self.transmitter and self.transmitter.slug: - self.share_link = f"{settings.SITE_DOMAIN}/hadis/narrators/{self.transmitter.slug}/original-texts/{self.slug}" + # Generate/update share_link before saving + if self.slug and self.transmitter and self.transmitter.slug: + self.share_link = f"{settings.DOVODI_DOMAIN}/arguments/narrators/{self.transmitter.slug}/original-texts/{self.slug}" super().save(*args, **kwargs) diff --git a/apps/hadis/serializers/hadis.py b/apps/hadis/serializers/hadis.py index 5f1cae0..d26bd6c 100644 --- a/apps/hadis/serializers/hadis.py +++ b/apps/hadis/serializers/hadis.py @@ -269,12 +269,13 @@ class TransmitterSerializer(serializers.ModelSerializer): known_as = LocalizedField() nickname = LocalizedField() reliability = serializers.SerializerMethodField() + share_link = serializers.CharField(read_only=True) class Meta: model = Transmitters fields = [ 'id', 'full_name', 'slug','birth_year_hijri', 'death_year_hijri', - "known_as",'nickname','reliability','madhhab','generation' + "known_as",'nickname','reliability','madhhab','generation','share_link' ] def get_reliability(self, obj): @@ -344,6 +345,7 @@ class TransmitterDetailSerializer(serializers.ModelSerializer): died_in = LocalizedField() description = LocalizedField() reliability = serializers.SerializerMethodField() + share_link = serializers.CharField(read_only=True) class Meta: model = Transmitters @@ -352,7 +354,7 @@ class TransmitterDetailSerializer(serializers.ModelSerializer): 'origin','lived_in','died_in','birth_year_hijri', 'death_year_hijri','age_at_death','reliability', 'madhhab',"in_sahih_muslim","in_sahih_bukhari", - "description",'generation' + "description",'generation','share_link' ] def get_reliability(self, obj): diff --git a/apps/hadis/serializers/reference.py b/apps/hadis/serializers/reference.py index 2708891..6832488 100644 --- a/apps/hadis/serializers/reference.py +++ b/apps/hadis/serializers/reference.py @@ -27,9 +27,10 @@ class BookReferenceSerializer(serializers.ModelSerializer): title = LocalizedField() description = LocalizedField() author = serializers.SerializerMethodField() + share_link = serializers.CharField(read_only=True) class Meta: model = BookReference - fields = ['id','title','slug','rate','author','description','image','volume'] + fields = ['id','title','slug','rate','author','description','image','volume','share_link'] def get_author(self, obj): request = self.context.get("request") # Prefer request.LANGUAGE_CODE if you use LocaleMiddleware @@ -107,9 +108,11 @@ class BookDetailSerializer(serializers.ModelSerializer): publisher = LocalizedField() description = LocalizedField() + share_link = serializers.CharField(read_only=True) + class Meta: model = BookReference - fields = ['id','title','rate','isbn','language','number_page','publisher','description','volume','slug','attribute','author','image','hadis'] + fields = ['id','title','rate','isbn','share_link','language','number_page','publisher','description','volume','slug','attribute','author','image','hadis'] def get_hadis(self,obj): references = obj.hadis_references.all() diff --git a/apps/library/models.py b/apps/library/models.py index 4476958..7128d8b 100644 --- a/apps/library/models.py +++ b/apps/library/models.py @@ -1,6 +1,7 @@ from django.db import models from django.utils.translation import gettext_lazy as _ +from django.conf import settings from filer.fields.image import FilerImageField from dj_language.field import LanguageField @@ -139,6 +140,12 @@ class Book(models.Model): def __str__(self): return f'<{self.id}>-{self.title}' + @property + def share_link(self): + if self.slug: + return f"{settings.DOVODI_DOMAIN}/library/{self.slug}" + return None + def save(self, *args, **kwargs): if not self.slug: self.slug = generate_slug_for_model(Book, self.title) diff --git a/apps/library/serializers.py b/apps/library/serializers.py index 3fb05df..2c385f5 100644 --- a/apps/library/serializers.py +++ b/apps/library/serializers.py @@ -52,6 +52,7 @@ class BookSerializer(serializers.ModelSerializer): bookmark = serializers.SerializerMethodField() user_rate = serializers.SerializerMethodField() average_rate = serializers.SerializerMethodField() + share_link = serializers.CharField(read_only=True) def get_thumbnail(self, obj): if obj.thumbnail: @@ -65,7 +66,7 @@ class BookSerializer(serializers.ModelSerializer): 'status', 'pin', 'view_count', 'download_count', 'publisher', 'year_of_publication', 'author', 'isbn', 'numnber_of_volume', 'language', 'main_themes', 'notable_works', 'file_type', 'book_file', 'created_at', 'bookmark', 'user_rate', - 'average_rate' + 'average_rate', 'share_link' ) def get_bookmark(self, obj): diff --git a/apps/podcast/models.py b/apps/podcast/models.py index 50ff592..de16e7f 100644 --- a/apps/podcast/models.py +++ b/apps/podcast/models.py @@ -1,5 +1,6 @@ from django.db import models from django.utils.translation import gettext_lazy as _ +from django.conf import settings from utils import generate_slug_for_model @@ -100,6 +101,12 @@ class Podcast(models.Model): def __str__(self): return self.title + @property + def share_link(self): + if self.slug: + return f"{settings.DOVODI_DOMAIN}/podcast/{self.slug}" + return None + def increment_view_count(self): self.view_count += 1 self.save(update_fields=['view_count']) diff --git a/apps/podcast/serializers.py b/apps/podcast/serializers.py index e50547a..942b2f8 100755 --- a/apps/podcast/serializers.py +++ b/apps/podcast/serializers.py @@ -19,11 +19,12 @@ class PodcastListSerializer(serializers.ModelSerializer): thumbnail = serializers.SerializerMethodField() audio_file = serializers.SerializerMethodField() in_user_playlist = serializers.SerializerMethodField() + share_link = serializers.CharField(read_only=True) class Meta: model = Podcast fields = ['id', 'title', 'slug', 'thumbnail', 'description', 'audio_file', - 'audio_time', 'view_count', 'created_at', 'in_user_playlist'] + 'audio_time', 'view_count', 'created_at', 'in_user_playlist', 'share_link'] def get_thumbnail(self, obj): return get_thumbs(obj.thumbnail, self.context.get('request')) @@ -60,13 +61,14 @@ class PodcastDetailSerializer(serializers.ModelSerializer): is_in_playlist = serializers.SerializerMethodField() playlist_podcasts = serializers.SerializerMethodField() in_user_playlist = serializers.SerializerMethodField() + share_link = serializers.CharField(read_only=True) class Meta: model = Podcast fields = ['id', 'title', 'slug', 'thumbnail', 'description', 'audio_file', 'audio_time', 'view_count', 'download_count', 'categories', 'created_at', 'user_rate', 'average_rate', 'bookmark', - 'is_in_playlist', 'playlist_podcasts', 'in_user_playlist'] + 'is_in_playlist', 'playlist_podcasts', 'in_user_playlist', 'share_link'] def get_thumbnail(self, obj): return get_thumbs(obj.thumbnail, self.context.get('request')) diff --git a/apps/video/models.py b/apps/video/models.py index de5770e..011a103 100644 --- a/apps/video/models.py +++ b/apps/video/models.py @@ -1,5 +1,6 @@ from django.db import models from django.utils.translation import gettext_lazy as _ +from django.conf import settings from filer.fields.image import FilerImageField from utils import generate_slug_for_model @@ -103,6 +104,12 @@ class Video(models.Model): def __str__(self): return self.title + @property + def share_link(self): + if self.slug: + return f"{settings.DOVODI_DOMAIN}/videos/{self.slug}" + return None + def increment_view_count(self): """Increment the view count for this video""" self.view_count += 1 diff --git a/apps/video/serializers.py b/apps/video/serializers.py index da34808..bbcf2ea 100644 --- a/apps/video/serializers.py +++ b/apps/video/serializers.py @@ -18,11 +18,12 @@ class VideoCategoryListSerializer(serializers.ModelSerializer): class VideoListSerializer(serializers.ModelSerializer): thumbnail = serializers.SerializerMethodField() video_file = serializers.SerializerMethodField() + share_link = serializers.CharField(read_only=True) class Meta: model = Video fields = ['id', 'title', 'slug', 'thumbnail', 'description', 'video_type', - 'video_file', 'video_url', 'video_time', 'view_count', 'created_at'] + 'video_file', 'video_url', 'video_time', 'view_count', 'created_at', 'share_link'] def get_thumbnail(self, obj): return get_thumbs(obj.thumbnail, self.context.get('request')) @@ -154,13 +155,14 @@ class VideoDetailSerializer(serializers.ModelSerializer): average_rate = serializers.SerializerMethodField() is_in_playlist = serializers.SerializerMethodField() playlist_videos = serializers.SerializerMethodField() + share_link = serializers.CharField(read_only=True) class Meta: model = Video fields = ['id', 'title', 'slug', 'thumbnail', 'description', 'video_type', 'video_file', 'video_url', 'video_time', 'view_count', 'categories', 'created_at', 'user_rate', 'average_rate', 'bookmark', - 'is_in_playlist', 'playlist_videos'] + 'is_in_playlist', 'playlist_videos', 'share_link'] def get_thumbnail(self, obj): return get_thumbs(obj.thumbnail, self.context.get('request')) diff --git a/config/settings/base.py b/config/settings/base.py index 5482d9f..d648f04 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -278,7 +278,8 @@ FILER_ENABLE_LOGGING = True FILER_DEBUG = True ADMIN_TITLE = 'Imam Javad App' ADMIN_INDEX_TITLE = 'Imam Javad Administration' -SITE_DOMAIN = "https://imamjavad.nwhco.ir" +SITE_DOMAIN = "https://imamjavad.newhorizonco.uk" +DOVODI_DOMAIN = "https://dovodi.newhorizonco.uk" ONLINE_CLASS_FRONTEND_DOMAIN = env('ONLINE_CLASS_FRONTEND_DOMAIN', default=SITE_DOMAIN) ONLINE_CLASS_TOKEN_TTL = env.int('ONLINE_CLASS_TOKEN_TTL', default=3000) PLUGNMEET_SERVER_URL ='https://meet.newhorizonco.uk'