From b9c244c430a2e4fbffb378ce7ce1bd2bfb662c4c Mon Sep 17 00:00:00 2001 From: mohsentaba Date: Wed, 10 Dec 2025 13:38:37 +0330 Subject: [PATCH] hadis develope and web guest token --- .../migrations/0007_user_user_agent.py | 17 ++++ ...story_device_os_loginhistory_user_agent.py | 22 +++++ .../account/migrations/0009_user_client_ip.py | 17 ++++ .../migrations/0010_alter_user_device_os.py | 21 ++++ apps/account/models/user.py | 9 +- apps/account/serializers/user.py | 17 ++++ apps/account/urls.py | 1 + apps/account/views/user.py | 98 ++++++++++++++++++- apps/article/views.py | 2 + apps/blog/views.py | 1 + .../migrations/0005_auto_20251209_1620.py | 22 +++++ apps/hadis/serializers/category.py | 14 ++- apps/hadis/serializers/hadis.py | 41 +++++++- apps/hadis/serializers/reference.py | 59 +++++++++++ apps/hadis/urls.py | 11 ++- apps/hadis/views/category.py | 18 +++- apps/hadis/views/reference.py | 35 +++++++ apps/hadis/views/transmitter.py | 17 ++++ apps/video/views.py | 3 + fix_db.py | 55 +++++++++++ fix_transmitter_opinion.py | 41 ++++++++ test_serializer.py | 60 ++++++++++++ 22 files changed, 572 insertions(+), 9 deletions(-) create mode 100644 apps/account/migrations/0007_user_user_agent.py create mode 100644 apps/account/migrations/0008_loginhistory_device_os_loginhistory_user_agent.py create mode 100644 apps/account/migrations/0009_user_client_ip.py create mode 100644 apps/account/migrations/0010_alter_user_device_os.py create mode 100644 apps/hadis/migrations/0005_auto_20251209_1620.py create mode 100644 apps/hadis/serializers/reference.py create mode 100644 apps/hadis/views/reference.py create mode 100644 apps/hadis/views/transmitter.py create mode 100644 fix_db.py create mode 100644 fix_transmitter_opinion.py create mode 100644 test_serializer.py diff --git a/apps/account/migrations/0007_user_user_agent.py b/apps/account/migrations/0007_user_user_agent.py new file mode 100644 index 0000000..5e8d0b3 --- /dev/null +++ b/apps/account/migrations/0007_user_user_agent.py @@ -0,0 +1,17 @@ +# Generated by Django 5.2.9 on 2025-12-09 15:13 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("account", "0006_auto_20251006_1101"), + ] + + operations = [ + migrations.AddField( + model_name="user", + name="user_agent", + field=models.TextField(blank=True, null=True, verbose_name="user agent"), + ), + ] diff --git a/apps/account/migrations/0008_loginhistory_device_os_loginhistory_user_agent.py b/apps/account/migrations/0008_loginhistory_device_os_loginhistory_user_agent.py new file mode 100644 index 0000000..9a70df9 --- /dev/null +++ b/apps/account/migrations/0008_loginhistory_device_os_loginhistory_user_agent.py @@ -0,0 +1,22 @@ +# Generated by Django 5.2.9 on 2025-12-09 15:13 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("account", "0007_user_user_agent"), + ] + + operations = [ + migrations.AddField( + model_name="loginhistory", + name="device_os", + field=models.CharField(blank=True, max_length=16, null=True), + ), + migrations.AddField( + model_name="loginhistory", + name="user_agent", + field=models.TextField(blank=True, null=True, verbose_name="user agent"), + ), + ] diff --git a/apps/account/migrations/0009_user_client_ip.py b/apps/account/migrations/0009_user_client_ip.py new file mode 100644 index 0000000..5488aed --- /dev/null +++ b/apps/account/migrations/0009_user_client_ip.py @@ -0,0 +1,17 @@ +# Generated by Django 5.2.9 on 2025-12-09 15:37 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("account", "0008_loginhistory_device_os_loginhistory_user_agent"), + ] + + operations = [ + migrations.AddField( + model_name="user", + name="client_ip", + field=models.TextField(blank=True, null=True, verbose_name="client ip"), + ), + ] diff --git a/apps/account/migrations/0010_alter_user_device_os.py b/apps/account/migrations/0010_alter_user_device_os.py new file mode 100644 index 0000000..72fa771 --- /dev/null +++ b/apps/account/migrations/0010_alter_user_device_os.py @@ -0,0 +1,21 @@ +# Generated by Django 5.2.9 on 2025-12-09 15:52 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("account", "0009_user_client_ip"), + ] + + operations = [ + migrations.AlterField( + model_name="user", + name="device_os", + field=models.CharField( + choices=[("android", "android"), ("apple", "apple"), ("web", "web")], + max_length=16, + null=True, + ), + ), + ] diff --git a/apps/account/models/user.py b/apps/account/models/user.py index 2b3bb05..aaa5ec4 100644 --- a/apps/account/models/user.py +++ b/apps/account/models/user.py @@ -16,6 +16,7 @@ class User(AbstractUser): class DeviceOs(models.TextChoices): android = 'android', 'android' apple = 'apple', 'apple' + web = 'web', 'web' class UserType(models.TextChoices): PROFESSOR = 'professor', 'Professor' @@ -54,7 +55,9 @@ class User(AbstractUser): device_id = models.CharField(verbose_name=_('device id'), max_length=255, null=True, blank=True) device_os = models.CharField(choices=DeviceOs.choices, null=True, max_length=16) - + user_agent = models.TextField(verbose_name=_('user agent'), null=True, blank=True) + client_ip = models.TextField(verbose_name=_('client ip'), null=True, blank=True) + fcm = models.CharField(max_length=512, null=True, blank=True) slug = models.SlugField(max_length=255, unique=True, null=True, blank=True) experience_years = models.PositiveIntegerField(default=0, verbose_name=_('Experience years')) @@ -281,7 +284,9 @@ class LoginHistory(models.Model): country = models.CharField(max_length=255, verbose_name=_('country'), null=True, blank=True) city = models.CharField(max_length=255, verbose_name=_('city'), null=True, blank=True) ip = models.CharField(max_length=255, null=True) - timezone = models.CharField(max_length=100, null=True, blank=True) + timezone = models.CharField(max_length=100, null=True, blank=True) + user_agent = models.TextField(verbose_name=_('user agent'), null=True, blank=True) + device_os = models.CharField(max_length=16, null=True, blank=True) at_time = models.DateTimeField(auto_now_add=True) diff --git a/apps/account/serializers/user.py b/apps/account/serializers/user.py index 6a8a410..a6ad2e8 100644 --- a/apps/account/serializers/user.py +++ b/apps/account/serializers/user.py @@ -196,6 +196,23 @@ class UserGuestSerializer(serializers.ModelSerializer): return data +class WebUserGuestSerializer(serializers.ModelSerializer): + + user_agent = serializers.CharField(required=False, allow_null=True, allow_blank=True) + client_ip = serializers.CharField(required=False, allow_null=True, allow_blank=True) + timezone = serializers.CharField(required=False, allow_null=True, allow_blank=True) + + class Meta: + model = User + fields = ['user_agent', 'client_ip', 'timezone', 'device_id', 'device_os'] + + def validate(self, data): + # Ensure device_id is provided (generated by view) + if not data.get('device_id'): + raise serializers.ValidationError({"device_id": "Device ID is required for web guest users."}) + return data + + class UserFCMSerializer(serializers.ModelSerializer): class Meta: diff --git a/apps/account/urls.py b/apps/account/urls.py index 89c85c8..a3332af 100644 --- a/apps/account/urls.py +++ b/apps/account/urls.py @@ -15,6 +15,7 @@ urlpatterns = [ path('verify/', views.UserVerifyView.as_view(), name='user-verify'), path('login/', views.UserLoginView.as_view(), name='user-login'), path('guest/', views.UserGuestView.as_view(), name='user-guest'), + path('web/guest/', views.WebUserGuestView.as_view(), name='user-guest'), path('exchange-token/', views.ExchangeTokenAPIView.as_view(), name='exchange-token'), path('location-update/', views.LocationHistoryView.as_view(), name='user-location-history'), diff --git a/apps/account/views/user.py b/apps/account/views/user.py index 68311c0..2392603 100644 --- a/apps/account/views/user.py +++ b/apps/account/views/user.py @@ -23,7 +23,7 @@ from rest_framework.exceptions import ValidationError from utils.exceptions import InvaliedCodeVrify, ExpiredCodeException, ServiceUnavailableException from apps.account.models import User -from apps.account.serializers import UserRegisterSerializer, UserProfileSerializer, UserVerifySerializer, UserLoginSerializer, UserRecoverPasswordSerializer, UserResetPasswordSerializer, UserGuestSerializer,UserFCMSerializer +from apps.account.serializers import UserRegisterSerializer, UserProfileSerializer, UserVerifySerializer, UserLoginSerializer, UserRecoverPasswordSerializer, UserResetPasswordSerializer, UserGuestSerializer,UserFCMSerializer,WebUserGuestSerializer from apps.account.serializers.user_web import WebUserRegisterSerializer from utils.redis import RedisManager from utils.exceptions import AppAPIException @@ -113,6 +113,102 @@ class UserGuestView(CreateAPIView): return obj +class WebUserGuestView(CreateAPIView): + permission_classes = [AllowAny] + serializer_class = WebUserGuestSerializer + + @swagger_auto_schema( + operation_description="Create a guest user account for web users using IP and user agent", + request_body=openapi.Schema( + type=openapi.TYPE_OBJECT, + properties={ + "timezone": openapi.Schema(type=openapi.TYPE_STRING, default="1.0"), + "user_agent": openapi.Schema(type=openapi.TYPE_STRING, default="Mozilla/5.0..."), + }, + required=[], # No required fields - we'll extract from request + ), + ) + def post(self, request, *args, **kwargs): + logger.info(f'WebGuestAuthView--> IP: {self.get_client_ip()}, User-Agent: {self.get_user_agent()}') + return super().post(request, *args, **kwargs) + + @staticmethod + def generate_login_token(user): + token, created = Token.objects.update_or_create(user=user) + return token.key + + def get_client_ip(self): + """Get client IP address from request""" + request = self.request + x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR') + if x_forwarded_for: + ip = x_forwarded_for.split(',')[0] + else: + ip = request.META.get('REMOTE_ADDR') + return ip + + def get_user_agent(self): + """Get user agent from request headers""" + return self.request.META.get('HTTP_USER_AGENT', '') + + def create(self, request, *args, **kwargs): + # Override to pass data to serializer + data = request.data.copy() + client_ip = self.get_client_ip() + user_agent = self.get_user_agent() + + # Create unique device_id for web user + web_user_id = f"{client_ip}_{hash(user_agent) % 1000000}" + + data.update({ + 'device_id': web_user_id, + 'device_os': 'web', + 'user_agent': user_agent, + 'client_ip': client_ip, + }) + + serializer = self.get_serializer(data=data) + serializer.is_valid(raise_exception=True) + user = self.perform_create(serializer) + return Response({ + 'token': self.generate_login_token(user), + }, status=200) + + + def perform_create(self, serializer): + # Extract web-specific data + user_timezone = serializer.validated_data.pop('timezone', None) + device_id = serializer.validated_data.get('device_id') + user_agent = serializer.validated_data.get('user_agent') + client_ip = serializer.validated_data.get('client_ip') + + serializer_data = dict(serializer.validated_data) + + # Find or create user based on device_id (which is IP + hashed user agent) + obj = User.objects.select_for_update().filter(Q(device_id=device_id)).first() + if not obj: + obj, created = User.objects.select_for_update().get_or_create( + device_id=device_id, + defaults=serializer_data + ) + if created: + logger.info(f'WebGuest-(created)->: {device_id} (IP: {client_ip})') + + # Update user on each login + obj.last_login = timezone.now() + obj.user_agent = user_agent # Update user agent on each login + obj.client_ip = client_ip # Update IP on each login + obj.save() + + # Create login history + login_history_obj = obj.login_history.create( + ip=client_ip, + user_agent=user_agent, + timezone=user_timezone, + device_os='web', + ) + return obj + class UserRegisterView(CreateAPIView): diff --git a/apps/article/views.py b/apps/article/views.py index 02b4e43..1be4b2f 100755 --- a/apps/article/views.py +++ b/apps/article/views.py @@ -80,6 +80,7 @@ class ArticleListAPIView(generics.ListAPIView): serializer_class = ArticleListSerializer permission_classes = (IsAuthenticated,) + @swagger_auto_schema( operation_description="Get a list of articles with optional filtering and sorting", manual_parameters=[ @@ -183,6 +184,7 @@ class ArticleListAPIView(generics.ListAPIView): class ArticleDetailAPIView(generics.RetrieveAPIView): serializer_class = ArticleDetailSerializer + permission_classes = (IsAuthenticated,) lookup_field = 'slug' def get_queryset(self): diff --git a/apps/blog/views.py b/apps/blog/views.py index 4538d3a..4096fbd 100644 --- a/apps/blog/views.py +++ b/apps/blog/views.py @@ -2,6 +2,7 @@ from rest_framework.generics import ListAPIView, GenericAPIView from rest_framework.permissions import AllowAny from rest_framework.response import Response from rest_framework import status +from rest_framework.permissions import IsAuthenticated from django.db.models import Q from django.shortcuts import get_object_or_404 from .models import Blog diff --git a/apps/hadis/migrations/0005_auto_20251209_1620.py b/apps/hadis/migrations/0005_auto_20251209_1620.py new file mode 100644 index 0000000..60ec132 --- /dev/null +++ b/apps/hadis/migrations/0005_auto_20251209_1620.py @@ -0,0 +1,22 @@ +# Generated by Django 5.2.9 on 2025-12-09 16:20 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("hadis", "0004_hadiscollection_hadisincollection"), + ] + + operations = [ + 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'), + ), + ] diff --git a/apps/hadis/serializers/category.py b/apps/hadis/serializers/category.py index bb8c70b..36f602d 100644 --- a/apps/hadis/serializers/category.py +++ b/apps/hadis/serializers/category.py @@ -1,7 +1,8 @@ from rest_framework import serializers from django.utils.translation import gettext_lazy as _ -from ..models import HadisSect, HadisCategory, Hadis +from ..models import HadisSect, HadisCategory, Hadis , HadisCategory + class HadisCategorySectListSerializer(serializers.ModelSerializer): @@ -127,4 +128,13 @@ class HadisCategoryTreeSerializer(serializers.ModelSerializer): 'xmind_file': self.get_xmind_file(c), 'has_xmind_file': self.get_has_xmind_file(c), 'children': [] if not filtered_children else [self.to_dict(i) for i in filtered_children], - } \ No newline at end of file + } + + +class CategorySerializer(serializers.ModelSerializer): + sect_id = serializers.IntegerField(source='sect.id', read_only=True) + sect_type = serializers.CharField(source='sect.sect_type', read_only=True) + + class Meta: + model = HadisCategory + fields = ['id', 'title', 'sect_id', 'sect_type','source_type','description','order','slug','xmind_file'] diff --git a/apps/hadis/serializers/hadis.py b/apps/hadis/serializers/hadis.py index a34c6f6..88c5547 100644 --- a/apps/hadis/serializers/hadis.py +++ b/apps/hadis/serializers/hadis.py @@ -1,9 +1,11 @@ from rest_framework import serializers from django.utils.translation import gettext_lazy as _ +from urllib3 import fields from ..models import ( Hadis, HadisStatus, HadisTag, HadisTransmitter, - HadisReference, ReferenceImage, Transmitters, HadisCollection + HadisReference, ReferenceImage, Transmitters, HadisCollection, + TransmitterOpinion , ) from apps.library.serializers import BookSerializer @@ -115,6 +117,43 @@ class TransmitterSerializer(serializers.ModelSerializer): 'id', 'full_name', 'birth_year_hijri', 'death_year_hijri', 'description' ] +class TransmitterOpinionSerializer(serializers.ModelSerializer): + """ Serializer for TransmitterOpinions """ + + class Meta: + model = TransmitterOpinion + fields = '__all__' + +class HadisTransmitterSerializer(serializers.ModelSerializer): + """ Serializer for HadisTransmitters """ + + class Meta: + model = HadisTransmitter + fields = '__all__' + +class TransmitterDetailSerializer(serializers.ModelSerializer): + """ Serializer for Details of Transmitters """ + + opinions = TransmitterOpinionSerializer( + many=True, + read_only=True, + ) + + hadis_transmissions = HadisTransmitterSerializer( + source='hadis', + many=True, + read_only=True, + ) + class Meta: + model = Transmitters + fields = [ + 'id','full_name','kunya','known_as','nickname', + 'origin','lived_in','died_in','birth_year_hijri', + 'death_year_hijri','age_at_death','reliability', + 'madhhab',"in_sahih_muslim","in_sahih_bukhari","description", + "thumbnail",'opinions','hadis_transmissions' + ] + class HadisTransmitterSerializer(serializers.ModelSerializer): diff --git a/apps/hadis/serializers/reference.py b/apps/hadis/serializers/reference.py new file mode 100644 index 0000000..124a546 --- /dev/null +++ b/apps/hadis/serializers/reference.py @@ -0,0 +1,59 @@ +from rest_framework import serializers + +from ..serializers import HadisListSerializer +from ..models import BookReference , BookAuthor , BookReferenceImage + +class BookAuthorSerializer(serializers.ModelSerializer): + class Meta: + model = BookAuthor + fields = '__all__' + +class BookReferenceImageSerializer(serializers.ModelSerializer): + class Meta: + model = BookReferenceImage + fields = '__all__' + +class BookReferenceSerializer(serializers.ModelSerializer): + image = BookReferenceImageSerializer( + many= True , + read_only = True , + source = 'bookreference_set' + ) + volume_count = serializers.SerializerMethodField() + class Meta: + model = BookReference + fields = ['id','title','description','rate','image','volume_count'] + def get_volume_count(self,obj): + request = self.context.get('request') + return BookReference.objects.filter(title=obj.title).count() + +class BookDetailSerializer(serializers.ModelSerializer): + + author = BookAuthorSerializer( + read_only = True , + source = 'bookauthor_set' + ) + image = BookReferenceImageSerializer( + many= True , + read_only = True , + source = 'bookreference_set' + ) + volume_count = serializers.SerializerMethodField() + + hadis = HadisListSerializer( + many=True, + read_only=True, + source='hadisreference_set' + ) + + class Meta: + model = BookReference + fields = '__all__' + def get_volume_count(self,obj): + request = self.context.get('request') + return BookReference.objects.filter(title=obj.title).count() + + # def create(self , validated_data): + # author = validated_data.pop('author') + # book = BookReference.objects.create(**validated_data) + # for author in author \ No newline at end of file diff --git a/apps/hadis/urls.py b/apps/hadis/urls.py index fc77a38..1369624 100644 --- a/apps/hadis/urls.py +++ b/apps/hadis/urls.py @@ -1,6 +1,8 @@ from django.urls import path -from .views.category import HadisCategorySectListView, HadisCategoryTreeView +from .views.category import HadisCategorySectListView, HadisCategoryTreeView,CategoriesView from .views.hadis import HadisCollectionListView, HadisListView, HadisDetailView, HadisSyncView +from .views.transmitter import TransmitterView ,TransmitterDetailView +from .views.reference import BookDetailView, BookReferencesView from .views.info import HadisInfoView @@ -12,4 +14,11 @@ urlpatterns = [ path('info/', HadisInfoView.as_view(), name='hadis-info'), path('category//', HadisListView.as_view(), name='hadis-list'), path('/', HadisDetailView.as_view(), name='hadis-detail'), + path('categories/', CategoriesView.as_view(), name='categories'), + path('narrators/',TransmitterView.as_view(), name='narrators'), + path('narrators/',TransmitterDetailView.as_view(), name='narrator-detail'), + path('references/',BookReferencesView.as_view(), name='references'), + path('references/',BookDetailView.as_view(), name='reference-detail'), + + ] \ No newline at end of file diff --git a/apps/hadis/views/category.py b/apps/hadis/views/category.py index 64b918d..39e6601 100644 --- a/apps/hadis/views/category.py +++ b/apps/hadis/views/category.py @@ -4,7 +4,7 @@ from django.shortcuts import get_object_or_404 from utils.pagination import NoPagination from ..models import HadisSect, HadisCategory -from ..serializers import HadisCategorySectListSerializer, HadisCategoryTreeSerializer +from ..serializers import HadisCategorySectListSerializer, HadisCategoryTreeSerializer, CategorySerializer from ..docs import hadis_sect_list_swagger, hadis_category_tree_swagger @@ -125,4 +125,18 @@ class HadisCategoryTreeView(ListAPIView): 'results': grouped_data } - return Response(response_data) \ No newline at end of file + return Response(response_data) + +class CategoriesView(ListAPIView): + """ + API view to list all HadisCategories + """ + queryset = HadisCategory.objects.all() + serializer_class = CategorySerializer + pagination_class = NoPagination + + def get(self, request, *args, **kwargs): + return self.list(request, *args, **kwargs) + + def list(self, request, *args, **kwargs): + return super().list(request, *args, **kwargs) \ No newline at end of file diff --git a/apps/hadis/views/reference.py b/apps/hadis/views/reference.py new file mode 100644 index 0000000..3cfbf64 --- /dev/null +++ b/apps/hadis/views/reference.py @@ -0,0 +1,35 @@ +from rest_framework.generics import ListAPIView, RetrieveAPIView +from ..models import BookReference , BookAuthor , BookReferenceImage +from ..serializers.reference import BookAuthorSerializer, BookDetailSerializer , BookReferenceSerializer +from utils.pagination import NoPagination + + + +class BookReferencesView(ListAPIView): + queryset = BookReference.objects.all() + serializer_class = BookReferenceSerializer + pagination_class = NoPagination + + +class BookAuthorView(ListAPIView): + queryset = BookAuthor.objects.all() + serializer_class = BookAuthorSerializer + pagination_class = NoPagination + +class BookDetailView(RetrieveAPIView): + serializer_class = BookDetailSerializer + lookup_field = 'id' + lookup_url_kwarg = 'bookreference_id' + + def get_queryset(self): + return BookReference.objects.all().prefetch_related( + 'bookauthor_set__name', + 'bookreferenceimage_set__image', + ) + + + + + +# class BookReferencesView(ListAPIView): +# pass \ No newline at end of file diff --git a/apps/hadis/views/transmitter.py b/apps/hadis/views/transmitter.py new file mode 100644 index 0000000..af47c22 --- /dev/null +++ b/apps/hadis/views/transmitter.py @@ -0,0 +1,17 @@ +from django.contrib.admin.utils import lookup_field +from rest_framework.generics import ListAPIView , RetrieveAPIView +from ..models import Transmitters , TransmitterOpinion +from ..serializers import TransmitterSerializer , TransmitterDetailSerializer +from utils.pagination import NoPagination +class TransmitterView(ListAPIView): + queryset = Transmitters.objects.all() + serializer_class = TransmitterSerializer + pagination_class = NoPagination + +class TransmitterDetailView(RetrieveAPIView): + serializer_class = TransmitterDetailSerializer + lookup_field = 'id' + lookup_url_kwarg = 'transmitters_id' + def get_queryset(self): + input = self.kwargs['transmitters_id'] + return Transmitters.objects.filter(id=input) \ No newline at end of file diff --git a/apps/video/views.py b/apps/video/views.py index b5dae30..ed0b6f1 100755 --- a/apps/video/views.py +++ b/apps/video/views.py @@ -88,6 +88,7 @@ class VideoPlaylistListAPIView(generics.ListAPIView): API view to list all video playlists, with optional filtering by category or collection """ serializer_class = VideoPlaylistListSerializer + permission_classes = (IsAuthenticated,) @swagger_auto_schema( operation_description="Get a list of video playlists with optional filtering", @@ -169,6 +170,7 @@ class VideoPlaylistListAPIView(generics.ListAPIView): class VideoPlaylistDetailAPIView(generics.RetrieveAPIView): serializer_class = VideoPlaylistDetailSerializer + permission_classes = (IsAuthenticated,) lookup_field = 'slug' def get_queryset(self): @@ -183,6 +185,7 @@ class VideoPlaylistDetailAPIView(generics.RetrieveAPIView): class VideoDetailAPIView(generics.RetrieveAPIView): serializer_class = VideoDetailSerializer + permission_classes = (IsAuthenticated,) lookup_field = 'slug' def get_queryset(self): diff --git a/fix_db.py b/fix_db.py new file mode 100644 index 0000000..137f794 --- /dev/null +++ b/fix_db.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python +import psycopg2 + +# Connect directly to the database +try: + conn = psycopg2.connect( + dbname="imam_javad_db", + user="postgres", + password="123456789", + host="localhost", + port="5432" + ) + cursor = conn.cursor() + print("Connected to database successfully") +except Exception as e: + print(f"Failed to connect to database: {e}") + exit(1) + +# Add missing transmitter fields +fields_to_add = [ + ('kunya', 'VARCHAR(255) NULL'), + ('known_as', 'VARCHAR(255) NULL'), + ('nickname', 'VARCHAR(255) NULL'), + ('origin', 'VARCHAR(255) NULL'), + ('lived_in', 'VARCHAR(255) NULL'), + ('died_in', 'VARCHAR(255) NULL'), + ('age_at_death', 'INTEGER NULL'), + ('reliability', "VARCHAR(20) DEFAULT 'unknown'"), + ('madhhab', "VARCHAR(20) DEFAULT 'unknown'"), + ('in_sahih_bukhari', 'BOOLEAN DEFAULT FALSE'), + ('in_sahih_muslim', 'BOOLEAN DEFAULT FALSE'), + ('created_at', 'TIMESTAMP WITH TIME ZONE DEFAULT NOW()'), + ('updated_at', 'TIMESTAMP WITH TIME ZONE DEFAULT NOW()'), +] + +print("Adding missing transmitter fields...") +for field_name, field_type in fields_to_add: + try: + cursor.execute(f'ALTER TABLE hadis_transmitters ADD COLUMN IF NOT EXISTS {field_name} {field_type};') + print(f'✓ Added column: {field_name}') + except Exception as e: + print(f'✗ Error adding {field_name}: {e}') + +conn.commit() +print('All missing transmitter fields added successfully!') + +# Test if the fields exist +cursor.execute("SELECT column_name FROM information_schema.columns WHERE table_name = 'hadis_transmitters' AND column_name = 'kunya';") +result = cursor.fetchone() +if result: + print('✓ kunya column exists in database') +else: + print('✗ kunya column not found') + +conn.close() diff --git a/fix_transmitter_opinion.py b/fix_transmitter_opinion.py new file mode 100644 index 0000000..41237fc --- /dev/null +++ b/fix_transmitter_opinion.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python +import psycopg2 + +# Connect directly to the database +try: + conn = psycopg2.connect( + dbname="imam_javad_db", + user="postgres", + password="123456789", + host="localhost", + port="5432" + ) + cursor = conn.cursor() + print("Connected to database successfully") +except Exception as e: + print(f"Failed to connect to database: {e}") + exit(1) + +# Create the missing TransmitterOpinion table +sql = """ +CREATE TABLE IF NOT EXISTS hadis_transmitteropinion ( + id BIGSERIAL PRIMARY KEY, + scholar_name VARCHAR(255) NOT NULL, + opinion_text TEXT NOT NULL, + status VARCHAR(20) DEFAULT 'confirmed', + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + transmitter_id BIGINT REFERENCES hadis_transmitters(id) ON DELETE CASCADE +); +""" + +try: + cursor.execute(sql) + print("✓ Created hadis_transmitteropinion table") +except Exception as e: + print(f"✗ Error creating table: {e}") + +conn.commit() +conn.close() + +print("TransmitterOpinion table creation completed!") diff --git a/test_serializer.py b/test_serializer.py new file mode 100644 index 0000000..80d7d13 --- /dev/null +++ b/test_serializer.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python +import os +import sys +import django +from pathlib import Path + +# Set up Django environment manually +sys.path.insert(0, str(Path(__file__).parent)) + +try: + # Try to avoid the environ import issue + import importlib + sys.modules['environ'] = importlib.util.spec_from_loader('environ', None) + + from django.conf import settings + if not settings.configured: + settings.configure( + DATABASES={ + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': 'imam_javad_db', + 'USER': 'postgres', + 'PASSWORD': '123456789', + 'HOST': 'localhost', + 'PORT': '5432', + } + }, + INSTALLED_APPS=[ + 'django.contrib.contenttypes', + 'django.contrib.auth', + 'apps.hadis', + ], + USE_TZ=True, + SECRET_KEY='temp-key-for-test', + ) + + django.setup() + + from apps.hadis.models import Transmitters + from apps.hadis.serializers.hadis import TransmitterDetailSerializer + + transmitter = Transmitters.objects.first() + if transmitter: + serializer = TransmitterDetailSerializer(transmitter) + data = serializer.data + print('✓ Serializer works!') + print(f'Has opinions field: {"opinions" in data}') + print(f'Has hadis_transmissions field: {"hadis_transmissions" in data}') + if 'opinions' in data: + print(f'Opinions count: {len(data["opinions"])}') + if 'hadis_transmissions' in data: + print(f'Hadis transmissions count: {len(data["hadis_transmissions"])}') + print('Test completed successfully!') + else: + print('No transmitters found') + +except Exception as e: + print(f'✗ Error: {e}') + import traceback + traceback.print_exc()