Browse Source

hadis develope and web guest token

master
Mohsen Taba 5 months ago
parent
commit
b9c244c430
  1. 17
      apps/account/migrations/0007_user_user_agent.py
  2. 22
      apps/account/migrations/0008_loginhistory_device_os_loginhistory_user_agent.py
  3. 17
      apps/account/migrations/0009_user_client_ip.py
  4. 21
      apps/account/migrations/0010_alter_user_device_os.py
  5. 9
      apps/account/models/user.py
  6. 17
      apps/account/serializers/user.py
  7. 1
      apps/account/urls.py
  8. 98
      apps/account/views/user.py
  9. 2
      apps/article/views.py
  10. 1
      apps/blog/views.py
  11. 22
      apps/hadis/migrations/0005_auto_20251209_1620.py
  12. 14
      apps/hadis/serializers/category.py
  13. 41
      apps/hadis/serializers/hadis.py
  14. 59
      apps/hadis/serializers/reference.py
  15. 11
      apps/hadis/urls.py
  16. 18
      apps/hadis/views/category.py
  17. 35
      apps/hadis/views/reference.py
  18. 17
      apps/hadis/views/transmitter.py
  19. 3
      apps/video/views.py
  20. 55
      fix_db.py
  21. 41
      fix_transmitter_opinion.py
  22. 60
      test_serializer.py

17
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"),
),
]

22
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"),
),
]

17
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"),
),
]

21
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,
),
),
]

9
apps/account/models/user.py

@ -16,6 +16,7 @@ class User(AbstractUser):
class DeviceOs(models.TextChoices): class DeviceOs(models.TextChoices):
android = 'android', 'android' android = 'android', 'android'
apple = 'apple', 'apple' apple = 'apple', 'apple'
web = 'web', 'web'
class UserType(models.TextChoices): class UserType(models.TextChoices):
PROFESSOR = 'professor', 'Professor' 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_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) 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) fcm = models.CharField(max_length=512, null=True, blank=True)
slug = models.SlugField(max_length=255, unique=True, 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')) 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) 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) city = models.CharField(max_length=255, verbose_name=_('city'), null=True, blank=True)
ip = models.CharField(max_length=255, null=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) at_time = models.DateTimeField(auto_now_add=True)

17
apps/account/serializers/user.py

@ -196,6 +196,23 @@ class UserGuestSerializer(serializers.ModelSerializer):
return data 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 UserFCMSerializer(serializers.ModelSerializer):
class Meta: class Meta:

1
apps/account/urls.py

@ -15,6 +15,7 @@ urlpatterns = [
path('verify/', views.UserVerifyView.as_view(), name='user-verify'), path('verify/', views.UserVerifyView.as_view(), name='user-verify'),
path('login/', views.UserLoginView.as_view(), name='user-login'), path('login/', views.UserLoginView.as_view(), name='user-login'),
path('guest/', views.UserGuestView.as_view(), name='user-guest'), 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('exchange-token/', views.ExchangeTokenAPIView.as_view(), name='exchange-token'),
path('location-update/', views.LocationHistoryView.as_view(), name='user-location-history'), path('location-update/', views.LocationHistoryView.as_view(), name='user-location-history'),

98
apps/account/views/user.py

@ -23,7 +23,7 @@ from rest_framework.exceptions import ValidationError
from utils.exceptions import InvaliedCodeVrify, ExpiredCodeException, ServiceUnavailableException from utils.exceptions import InvaliedCodeVrify, ExpiredCodeException, ServiceUnavailableException
from apps.account.models import User 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 apps.account.serializers.user_web import WebUserRegisterSerializer
from utils.redis import RedisManager from utils.redis import RedisManager
from utils.exceptions import AppAPIException from utils.exceptions import AppAPIException
@ -113,6 +113,102 @@ class UserGuestView(CreateAPIView):
return obj 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): class UserRegisterView(CreateAPIView):

2
apps/article/views.py

@ -80,6 +80,7 @@ class ArticleListAPIView(generics.ListAPIView):
serializer_class = ArticleListSerializer serializer_class = ArticleListSerializer
permission_classes = (IsAuthenticated,) permission_classes = (IsAuthenticated,)
@swagger_auto_schema( @swagger_auto_schema(
operation_description="Get a list of articles with optional filtering and sorting", operation_description="Get a list of articles with optional filtering and sorting",
manual_parameters=[ manual_parameters=[
@ -183,6 +184,7 @@ class ArticleListAPIView(generics.ListAPIView):
class ArticleDetailAPIView(generics.RetrieveAPIView): class ArticleDetailAPIView(generics.RetrieveAPIView):
serializer_class = ArticleDetailSerializer serializer_class = ArticleDetailSerializer
permission_classes = (IsAuthenticated,)
lookup_field = 'slug' lookup_field = 'slug'
def get_queryset(self): def get_queryset(self):

1
apps/blog/views.py

@ -2,6 +2,7 @@ from rest_framework.generics import ListAPIView, GenericAPIView
from rest_framework.permissions import AllowAny from rest_framework.permissions import AllowAny
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework import status from rest_framework import status
from rest_framework.permissions import IsAuthenticated
from django.db.models import Q from django.db.models import Q
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from .models import Blog from .models import Blog

22
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'),
),
]

14
apps/hadis/serializers/category.py

@ -1,7 +1,8 @@
from rest_framework import serializers from rest_framework import serializers
from django.utils.translation import gettext_lazy as _ 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): class HadisCategorySectListSerializer(serializers.ModelSerializer):
@ -127,4 +128,13 @@ class HadisCategoryTreeSerializer(serializers.ModelSerializer):
'xmind_file': self.get_xmind_file(c), 'xmind_file': self.get_xmind_file(c),
'has_xmind_file': self.get_has_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], 'children': [] if not filtered_children else [self.to_dict(i) for i in filtered_children],
}
}
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']

41
apps/hadis/serializers/hadis.py

@ -1,9 +1,11 @@
from rest_framework import serializers from rest_framework import serializers
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from urllib3 import fields
from ..models import ( from ..models import (
Hadis, HadisStatus, HadisTag, HadisTransmitter, Hadis, HadisStatus, HadisTag, HadisTransmitter,
HadisReference, ReferenceImage, Transmitters, HadisCollection
HadisReference, ReferenceImage, Transmitters, HadisCollection,
TransmitterOpinion ,
) )
from apps.library.serializers import BookSerializer from apps.library.serializers import BookSerializer
@ -115,6 +117,43 @@ class TransmitterSerializer(serializers.ModelSerializer):
'id', 'full_name', 'birth_year_hijri', 'death_year_hijri', 'id', 'full_name', 'birth_year_hijri', 'death_year_hijri',
'description' '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): class HadisTransmitterSerializer(serializers.ModelSerializer):

59
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

11
apps/hadis/urls.py

@ -1,6 +1,8 @@
from django.urls import path 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.hadis import HadisCollectionListView, HadisListView, HadisDetailView, HadisSyncView
from .views.transmitter import TransmitterView ,TransmitterDetailView
from .views.reference import BookDetailView, BookReferencesView
from .views.info import HadisInfoView from .views.info import HadisInfoView
@ -12,4 +14,11 @@ urlpatterns = [
path('info/', HadisInfoView.as_view(), name='hadis-info'), path('info/', HadisInfoView.as_view(), name='hadis-info'),
path('category/<int:category_id>/', HadisListView.as_view(), name='hadis-list'), path('category/<int:category_id>/', HadisListView.as_view(), name='hadis-list'),
path('<int:hadis_id>/', HadisDetailView.as_view(), name='hadis-detail'), path('<int:hadis_id>/', HadisDetailView.as_view(), name='hadis-detail'),
path('categories/', CategoriesView.as_view(), name='categories'),
path('narrators/',TransmitterView.as_view(), name='narrators'),
path('narrators/<int:transmitters_id>',TransmitterDetailView.as_view(), name='narrator-detail'),
path('references/',BookReferencesView.as_view(), name='references'),
path('references/<int:bookreference_id>',BookDetailView.as_view(), name='reference-detail'),
] ]

18
apps/hadis/views/category.py

@ -4,7 +4,7 @@ from django.shortcuts import get_object_or_404
from utils.pagination import NoPagination from utils.pagination import NoPagination
from ..models import HadisSect, HadisCategory 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 from ..docs import hadis_sect_list_swagger, hadis_category_tree_swagger
@ -125,4 +125,18 @@ class HadisCategoryTreeView(ListAPIView):
'results': grouped_data 'results': grouped_data
} }
return Response(response_data)
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)

35
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

17
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)

3
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 API view to list all video playlists, with optional filtering by category or collection
""" """
serializer_class = VideoPlaylistListSerializer serializer_class = VideoPlaylistListSerializer
permission_classes = (IsAuthenticated,)
@swagger_auto_schema( @swagger_auto_schema(
operation_description="Get a list of video playlists with optional filtering", operation_description="Get a list of video playlists with optional filtering",
@ -169,6 +170,7 @@ class VideoPlaylistListAPIView(generics.ListAPIView):
class VideoPlaylistDetailAPIView(generics.RetrieveAPIView): class VideoPlaylistDetailAPIView(generics.RetrieveAPIView):
serializer_class = VideoPlaylistDetailSerializer serializer_class = VideoPlaylistDetailSerializer
permission_classes = (IsAuthenticated,)
lookup_field = 'slug' lookup_field = 'slug'
def get_queryset(self): def get_queryset(self):
@ -183,6 +185,7 @@ class VideoPlaylistDetailAPIView(generics.RetrieveAPIView):
class VideoDetailAPIView(generics.RetrieveAPIView): class VideoDetailAPIView(generics.RetrieveAPIView):
serializer_class = VideoDetailSerializer serializer_class = VideoDetailSerializer
permission_classes = (IsAuthenticated,)
lookup_field = 'slug' lookup_field = 'slug'
def get_queryset(self): def get_queryset(self):

55
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()

41
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!")

60
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()
Loading…
Cancel
Save