diff --git a/apps/account/admin/__init__.py b/apps/account/admin/__init__.py
index fc61670..ac7abff 100644
--- a/apps/account/admin/__init__.py
+++ b/apps/account/admin/__init__.py
@@ -1,9 +1,3 @@
-<<<<<<< HEAD
-
-from .user import *
-from .professor import *
-from .student import *
-=======
from unfold.components import BaseComponent, register_component
from django.template.loader import render_to_string
@@ -71,4 +65,3 @@ class StudentUserComponent(BaseComponent):
},
)
return context
->>>>>>> develop
diff --git a/apps/account/admin/professor.py b/apps/account/admin/professor.py
index 40e515f..82fecc0 100644
--- a/apps/account/admin/professor.py
+++ b/apps/account/admin/professor.py
@@ -1,10 +1,5 @@
-<<<<<<< HEAD
-from django.contrib import admin
-from django.contrib.auth.forms import UserChangeForm, UsernameField
-=======
# This file is no longer used. All admin classes are now in user.pyfrom django.contrib import admin
from django.contrib.auth.forms import UserChangeForm, UsernameField, UserCreationForm
->>>>>>> develop
from django.contrib.auth.admin import UserAdmin
from django.utils.translation import gettext_lazy as _
from rest_framework.authtoken.models import TokenProxy
@@ -13,29 +8,15 @@ from ajaxdatatable.admin import AjaxDatatable
from django.contrib import admin
from apps.account.models import User
from django import forms
-<<<<<<< HEAD
-from django.contrib import admin
-from django.urls import path, reverse
-from django.shortcuts import render, redirect
-from django.contrib import messages
-=======
from django.urls import path, reverse
from django.shortcuts import render, redirect
from django.contrib import messages
from django.contrib.auth.models import Group
from phonenumber_field.formfields import PhoneNumberField
->>>>>>> develop
from apps.account.models import ProfessorUser
-<<<<<<< HEAD
-
-@admin.register(ProfessorUser)
-class ProfessorUserAdmin(UserAdmin, AjaxDatatable):
- list_display = (
- 'device_id', 'email', 'fullname', 'user_type','last_login', 'date_joined',
-=======
class ProfessorUserCreationForm(UserCreationForm):
phone_number = PhoneNumberField(
help_text="Enter the phone number in international format. Example: +989012023212",
@@ -52,7 +33,6 @@ class ProfessorUserAdmin(UserAdmin, AjaxDatatable):
add_form = ProfessorUserCreationForm
list_display = (
'email', 'fullname', 'last_login', 'date_joined',
->>>>>>> develop
)
ordering = 'last_login',
readonly_fields = ('date_joined',)
@@ -84,12 +64,6 @@ class ProfessorUserAdmin(UserAdmin, AjaxDatatable):
)
def save_model(self, request, obj, form, change):
-<<<<<<< HEAD
- if not change:
- obj.set_password(form.cleaned_data['password1'])
- obj.user_type = User.UserType.PROFESSOR
- super().save_model(request, obj, form, change)
-=======
if not change: # Creating a new professor
# Check if a user with this email already exists
email = form.cleaned_data.get('email')
@@ -131,7 +105,6 @@ class ProfessorUserAdmin(UserAdmin, AjaxDatatable):
if obj: # Only proceed if obj is not None
obj.add_role('professor')
super().save_model(request, obj, form, change)
->>>>>>> develop
@admin.display(description='Phone Number')
def _phone_number(self, obj):
diff --git a/apps/account/admin/student.py b/apps/account/admin/student.py
index a56b91c..a7cc389 100644
--- a/apps/account/admin/student.py
+++ b/apps/account/admin/student.py
@@ -4,10 +4,7 @@ from django.contrib.auth.admin import UserAdmin
from django.utils.translation import gettext_lazy as _
from rest_framework.authtoken.models import TokenProxy
from ajaxdatatable.admin import AjaxDatatable
-<<<<<<< HEAD
-=======
from unfold.admin import TabularInline, StackedInline
->>>>>>> develop
from django.contrib import admin
from apps.account.models import User
@@ -19,21 +16,11 @@ from django.contrib import messages
from apps.account.models import StudentUser, User
-<<<<<<< HEAD
-
-
-@admin.register(StudentUser)
-class StudentUserAdmin(UserAdmin, AjaxDatatable):
- list_display = (
- 'device_id', 'email', 'fullname', 'user_type','last_login', 'date_joined',
- )
-=======
@admin.register(StudentUser)
class StudentUserAdmin(UserAdmin, AjaxDatatable):
list_display = (
'device_id', 'email', 'fullname', 'user_type', 'enrolled_courses_count', 'last_login', 'date_joined',
)
->>>>>>> develop
ordering = 'last_login',
readonly_fields = ('date_joined',)
exclude = ('password', 'user_permissions')
@@ -41,11 +28,6 @@ class StudentUserAdmin(UserAdmin, AjaxDatatable):
(None, {
'classes': ('wide',),
'fields': ('fullname', 'email', 'phone_number',),
-<<<<<<< HEAD
- # 'description': 'Please provide the student details including full name, email, and phone number.',
-
-=======
->>>>>>> develop
}),
('other', {
'classes': ('wide',),
@@ -62,25 +44,13 @@ class StudentUserAdmin(UserAdmin, AjaxDatatable):
fieldsets = (
(_('Personal info'), {'fields': ('fullname', 'email', 'phone_number', 'avatar',)}),
(_('Permissions'), {
-<<<<<<< HEAD
- 'fields': ('is_active', 'is_staff', 'is_superuser', 'groups',),
-=======
'fields': ('is_active', 'groups',),
->>>>>>> develop
}),
(_('Important dates'), {'fields': ('last_login', 'date_joined', 'fcm')}),
)
@admin.display(description='Phone Number')
def _phone_number(self, obj):
return obj.phone_number
-<<<<<<< HEAD
-
-
- def get_queryset(self, request):
- # محدود کردن نمایش فقط دانشآموزان
- qs = super().get_queryset(request)
- return qs.filter(user_type=User.UserType.STUDENT)
-=======
@admin.display(description=_('Enrolled Courses'))
def enrolled_courses_count(self, obj):
@@ -93,17 +63,12 @@ class StudentUserAdmin(UserAdmin, AjaxDatatable):
# محدود کردن نمایش فقط دانشآموزان و بهینهسازی query
qs = super().get_queryset(request)
return qs.filter(user_type=User.UserType.STUDENT).prefetch_related('participated_courses')
->>>>>>> develop
def save_model(self, request, obj, form, change):
if not change:
obj.set_password(form.cleaned_data['password1'])
-<<<<<<< HEAD
- obj.user_type = User.UserType.STUDENT
-=======
obj.add_role('student')
->>>>>>> develop
super().save_model(request, obj, form, change)
diff --git a/apps/account/admin/user.py b/apps/account/admin/user.py
index d81c5cd..49f7f6a 100644
--- a/apps/account/admin/user.py
+++ b/apps/account/admin/user.py
@@ -1,108 +1,3 @@
-<<<<<<< HEAD
-from django.contrib import admin
-from django.contrib.auth.forms import UserChangeForm, UsernameField
-from django.contrib.auth.admin import UserAdmin
-from django.utils.translation import gettext_lazy as _
-from rest_framework.authtoken.models import TokenProxy
-from ajaxdatatable.admin import AjaxDatatable
-
-from apps.account.models import User, Notification
-from django import forms
-from django.contrib import admin
-from django.urls import path, reverse
-from django.shortcuts import render, redirect
-from django.contrib import messages
-
-from apps.account.models import ClientUser, AdminUser
-
-
-@admin.register(Notification)
-class NotificationAdmin(AjaxDatatable):
- list_display = ('title', 'user', 'is_read', 'created_at')
- list_filter = ('is_read', 'created_at')
- search_fields = ('title', 'message', 'user__fullname')
- list_editable = ('is_read',)
- ordering = ('-created_at',)
- autocomplete_fields = ['user',]
-
-
-@admin.register(User)
-class UserAdmin(UserAdmin, AjaxDatatable):
- list_display = (
- 'device_id', 'email', 'fullname', 'user_type','last_login', 'device_os', 'date_joined',
- )
- ordering = '-id',
- readonly_fields = ('date_joined',)
- exclude = ('password', 'user_permissions')
- add_fieldsets = (
- (None, {
- 'classes': ('wide',),
- 'fields': ('email', 'password1', 'password2'),
- }),
- )
- search_fields = (
- 'email', 'fullname', 'username',
- )
- fieldsets = (
- (_('Personal info'), {'fields': ('fullname', 'email', 'phone_number', 'avatar',)}),
- (_('Permissions'), {
- 'fields': ('is_active', 'is_staff', 'is_superuser', 'groups', 'password'),
- }),
- (_('Important dates'), {'fields': ('last_login', 'date_joined', 'fcm')}),
- )
-
- def save_model(self, request, obj, form, change):
- if not change:
- obj.set_password(form.cleaned_data['password1'])
-
- # obj.user_type = User.UserType.CLIENT
- super().save_model(request, obj, form, change)
-
- @admin.display(description='Phone Number')
- def _phone_number(self, obj):
- return obj.phone_number
-
-
-
-
-@admin.register(AdminUser)
-class AdminUserAdmin(UserAdmin, AjaxDatatable):
- list_display = (
- 'email', 'fullname', 'user_type','last_login', 'date_joined',
- )
- ordering = 'last_login',
- readonly_fields = ('date_joined',)
- exclude = ('password', 'user_permissions')
- add_fieldsets = (
- (None, {
- 'classes': ('wide',),
- 'fields': ('email', 'password1', 'password2'),
- }),
- )
- search_fields = (
- 'email', 'fullname', 'username',
- )
- fieldsets = (
- (_('Personal info'), {'fields': ('fullname', 'email', 'phone_number', 'avatar',)}),
- (_('Permissions'), {
- 'fields': ('is_active', 'is_staff', 'is_superuser', 'groups', 'password'),
- }),
- (_('Important dates'), {'fields': ('last_login', 'date_joined', 'fcm')}),
- )
-
- def save_model(self, request, obj, form, change):
- if not change:
- obj.set_password(form.cleaned_data['password1'])
-
- # obj.user_type = User.UserType.CLIENT
- super().save_model(request, obj, form, change)
-
- @admin.display(description='Phone Number')
- def _phone_number(self, obj):
- return obj.phone_number
-
-admin.site.unregister(TokenProxy)
-=======
from django import forms
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
@@ -481,4 +376,3 @@ try:
admin.site.unregister(TokenProxy)
except admin.sites.NotRegistered:
pass
->>>>>>> develop
diff --git a/apps/account/serializers/__init__.py b/apps/account/serializers/__init__.py
index 5d28da6..f6a8ca6 100644
--- a/apps/account/serializers/__init__.py
+++ b/apps/account/serializers/__init__.py
@@ -1,7 +1,4 @@
from .user import *
from .notification import *
-<<<<<<< HEAD
-=======
-from .location_history import *
from .auth import *
->>>>>>> develop
+from .location_history import *
diff --git a/apps/account/serializers/user.py b/apps/account/serializers/user.py
index 738e393..1c0a605 100644
--- a/apps/account/serializers/user.py
+++ b/apps/account/serializers/user.py
@@ -1,8 +1,3 @@
-
-<<<<<<< HEAD
-=======
-
->>>>>>> develop
from rest_framework import serializers
from rest_framework.authtoken.models import Token
from django.contrib.auth.password_validation import validate_password
@@ -18,17 +13,6 @@ class UserProfileSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True, required=False, validators=[validate_password])
fullname = serializers.CharField(required=False)
gender = serializers.ChoiceField(
-<<<<<<< HEAD
- choices=User.GenderChoices.choices,
- required=False,
- help_text="Select the user's gender."
- )
- fcm = serializers.CharField(required=False, help_text="Firebase Cloud Messaging token.")
- class Meta:
- model = User
- fields = ['id', 'device_id', 'fcm', 'fullname', 'avatar', 'email', 'phone_number', 'password', 'info', 'skill', 'city', 'country', 'birthdate', 'gender']
- read_only_fields = ['email', 'info', 'skill', 'device_id']
-=======
choices=User.GenderChoices.choices,
required=False,
help_text="Select the user's gender."
@@ -54,7 +38,6 @@ class UserProfileSerializer(serializers.ModelSerializer):
'at_time': last_location.at_time,
}
return None
->>>>>>> develop
# def validate_email(self, value):
# if User.objects.filter(email=value).exists():
@@ -62,36 +45,25 @@ class UserProfileSerializer(serializers.ModelSerializer):
# return value
def update(self, instance, validated_data):
-<<<<<<< HEAD
-=======
# Pop the password from the data to handle it separately
password = validated_data.pop('password', None)
# Use the default update logic for all other fields
->>>>>>> develop
for attr, value in validated_data.items():
if value is not None:
setattr(instance, attr, value)
-<<<<<<< HEAD
-=======
# If a new password was provided, hash and set it correctly
if password:
instance.set_password(password)
->>>>>>> develop
instance.save()
return instance
class UserRegisterSerializer(serializers.ModelSerializer):
-<<<<<<< HEAD
- fcm = serializers.CharField(required=False)
- device_id = serializers.CharField(required=True)
-=======
fcm = serializers.CharField(required=False, allow_blank=True, allow_null=True)
device_id = serializers.CharField(required=False, allow_blank=True, allow_null=True, write_only=True)
->>>>>>> develop
email = serializers.EmailField()
class Meta:
@@ -100,15 +72,6 @@ class UserRegisterSerializer(serializers.ModelSerializer):
extra_kwargs = {
'fullname': {'required': True,},
'email': {'required': True,},
-<<<<<<< HEAD
- 'device_id': {'required': True,},
- }
-
- def validate_email(self, value):
- if User.objects.filter(email=value).exists():
- raise serializers.ValidationError("This email is already registered.")
- return value
-=======
}
def create(self, validated_data):
@@ -124,7 +87,6 @@ class UserRegisterSerializer(serializers.ModelSerializer):
if User.objects.filter(email=normalized_email).exists():
raise serializers.ValidationError("This email is already registered.")
return normalized_email
->>>>>>> develop
@@ -133,16 +95,12 @@ class UserVerifySerializer(serializers.Serializer):
email = serializers.EmailField()
device_id = serializers.CharField(max_length=255, required=False)
-<<<<<<< HEAD
-
-=======
def validate_email(self, value):
"""
Normalize the email to ensure the Redis key matches correctly.
"""
return User.objects.normalize_email(value)
->>>>>>> develop
class UserLoginSerializer(serializers.Serializer):
password = serializers.CharField(write_only=True)
@@ -160,15 +118,12 @@ class UserLoginSerializer(serializers.Serializer):
# data.pop('fcm', None)
# data.pop('device_id', None)
return data
-<<<<<<< HEAD
-=======
def validate_email(self, value):
"""
Normalize email for case-insensitive login.
"""
return User.objects.normalize_email(value)
->>>>>>> develop
# class UserLoginSerializer(serializers.Serializer):
# password = serializers.CharField(write_only=True)
@@ -184,19 +139,6 @@ class UserLoginSerializer(serializers.Serializer):
-<<<<<<< HEAD
-class UserRecoverPasswordSerializer(serializers.ModelSerializer):
- email = serializers.EmailField()
-
- class Meta:
- model = User
- fields = ['email',]
- extra_kwargs = {
- 'email': {'required': True,},
- }
-
-
-=======
# class UserRecoverPasswordSerializer(serializers.ModelSerializer):
# email = serializers.EmailField()
@@ -220,7 +162,6 @@ class UserRecoverPasswordSerializer(serializers.Serializer):
"""
return User.objects.normalize_email(value)
->>>>>>> develop
class UserResetPasswordSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
@@ -253,8 +194,6 @@ class UserGuestSerializer(serializers.ModelSerializer):
return data
-<<<<<<< HEAD
-=======
class WebUserGuestSerializer(serializers.ModelSerializer):
user_agent = serializers.CharField(required=False, allow_null=True, allow_blank=True)
@@ -277,4 +216,3 @@ class UserFCMSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['fcm']
->>>>>>> develop
diff --git a/apps/account/urls.py b/apps/account/urls.py
index 4517911..5e450c1 100644
--- a/apps/account/urls.py
+++ b/apps/account/urls.py
@@ -1,4 +1,3 @@
-
from django.urls import path, include
from rest_framework.routers import DefaultRouter
diff --git a/apps/account/views/__init__.py b/apps/account/views/__init__.py
index d25e678..4266590 100644
--- a/apps/account/views/__init__.py
+++ b/apps/account/views/__init__.py
@@ -1,9 +1,4 @@
from .user import *
from .notification import *
-<<<<<<< HEAD
-=======
-from .location_history import *
from .auth import *
-
->>>>>>> develop
-
+from .location_history import*
diff --git a/apps/account/views/notification.py b/apps/account/views/notification.py
index ba8bac9..fa20834 100644
--- a/apps/account/views/notification.py
+++ b/apps/account/views/notification.py
@@ -1,17 +1,11 @@
-
from rest_framework import generics, status
from rest_framework.response import Response
from drf_yasg.utils import swagger_auto_schema
from drf_yasg import openapi
from rest_framework.permissions import IsAuthenticated
from apps.account.serializers import NotificationSerializer, NotificationSendSerializer
-<<<<<<< HEAD
-from apps.account.models import Notification
-# from apps.account.fcm_notification import send_notification
-=======
from apps.account.models import Notification, User
from apps.account.tasks import send_notification
->>>>>>> develop
@@ -39,7 +33,6 @@ class NotificationListView(generics.ListAPIView):
This API allows you to retrieve a list of notifications based on the authenticated user's type.
If the user is a regular user, their notifications will be fetched from the `Notification` model.
If the user is a merchant, their notifications will be fetched from the `MerchantAccountNotification` model.
-
- **Method**: GET
- **URL**: /api/notifications/
- **Query Parameters**:
@@ -108,8 +101,6 @@ class NotificationReadAllView(generics.GenericAPIView):
-<<<<<<< HEAD
-=======
class SendNotificationView(generics.GenericAPIView):
@swagger_auto_schema(
@@ -161,5 +152,3 @@ class SendNotificationView(generics.GenericAPIView):
return Response({
'error': str(e)
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
-
->>>>>>> develop
diff --git a/apps/account/views/user.py b/apps/account/views/user.py
index 515d56d..5f7bf05 100644
--- a/apps/account/views/user.py
+++ b/apps/account/views/user.py
@@ -23,12 +23,8 @@ from rest_framework.exceptions import ValidationError
from utils.exceptions import InvaliedCodeVrify, ExpiredCodeException, ServiceUnavailableException
from apps.account.models import User
-<<<<<<< HEAD
-from apps.account.serializers import UserRegisterSerializer, UserProfileSerializer, UserVerifySerializer, UserLoginSerializer, UserRecoverPasswordSerializer, UserResetPasswordSerializer, UserGuestSerializer
-=======
from apps.account.serializers import UserRegisterSerializer, UserProfileSerializer, UserVerifySerializer, UserLoginSerializer, UserRecoverPasswordSerializer, UserResetPasswordSerializer, UserGuestSerializer,UserFCMSerializer,WebUserGuestSerializer
from apps.account.serializers.user_web import WebUserRegisterSerializer
->>>>>>> develop
from utils.redis import RedisManager
from utils.exceptions import AppAPIException
from utils import send_email, is_valid_email
@@ -117,8 +113,6 @@ class UserGuestView(CreateAPIView):
return obj
-<<<<<<< HEAD
-=======
class WebUserGuestView(CreateAPIView):
permission_classes = [AllowAny]
serializer_class = WebUserGuestSerializer
@@ -215,7 +209,6 @@ class WebUserGuestView(CreateAPIView):
)
return obj
->>>>>>> develop
class UserRegisterView(CreateAPIView):
@@ -230,25 +223,16 @@ class UserRegisterView(CreateAPIView):
def post(self, request):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
-<<<<<<< HEAD
- data = serializer.data
-=======
data = serializer.validated_data
->>>>>>> develop
code = RedisManager.generate_otp_code()
logger.info(f"phone= {data['email']}")
print(f'send {code}/{data["email"]}')
phone_number = RedisManager().add_to_redis(code, **data)
-<<<<<<< HEAD
-
- send_email([data['email']], code)
-=======
try:
send_email([data['email']], code)
except Exception as exp:
print(f'-exp-register-->{exp}')
->>>>>>> develop
return Response(
data= {
"user": data,
@@ -290,11 +274,7 @@ class UserVerifyView(CreateAPIView):
code = self.valied_code(data['code'], verify_data['code'])
del verify_data['code']
user = self.perform_create(
-<<<<<<< HEAD
- email=serializer.data['email'], device_id=serializer.data['device_id'], **verify_data
-=======
email=serializer.data['email'], device_id=serializer.data.get('device_id'), **verify_data
->>>>>>> develop
)
token, _ = Token.objects.get_or_create(user=user)
return Response(data={
@@ -308,11 +288,8 @@ class UserVerifyView(CreateAPIView):
def valied_code(self, current_code, save_code):
if (current_code and save_code) and ( current_code != save_code):
-<<<<<<< HEAD
-=======
if current_code == "11111":
return current_code
->>>>>>> develop
raise ValidationError({"code": "code notfound"})
return current_code
@@ -322,27 +299,6 @@ class UserVerifyView(CreateAPIView):
device_id = kwargs.get('device_id')
user = User.objects.filter(email=email).first()
if user:
-<<<<<<< HEAD
- if kwargs['password']:
- user.is_active = True
- user.deletion_date = None
- user.device_id = device_id
- user.last_login = timezone.now()
- user.save()
- else:
- user = User.objects.filter(device_id=device_id, email__isnull=True).first()
- if not user:
- user = User.objects.create(**kwargs)
- else:
- user.email = email
- user.fullname = kwargs['fullname']
- user.device_id = device_id
- user.last_login = timezone.now()
- user.is_active = True
- user.save()
-
- return user
-=======
if kwargs.get('password'):
user.is_active = True
user.deletion_date = None
@@ -410,7 +366,6 @@ class WebUserRegisterView(CreateAPIView):
},
status=status.HTTP_202_ACCEPTED,
)
->>>>>>> develop
class UserLoginView(CreateAPIView):
@@ -503,14 +458,10 @@ class UserRecoverPassword(CreateAPIView):
print(f' send {code}')
phone_number = RedisManager().add_to_redis(code, fullname=str(user.fullname), password='', email=data['email'])
-<<<<<<< HEAD
- send_email([data['email']], code)
-=======
try:
send_email([data['email']], code)
except Exception as exp:
print(f'-exp-register-->{exp}')
->>>>>>> develop
return Response(
data= {
@@ -518,11 +469,7 @@ class UserRecoverPassword(CreateAPIView):
"fullname": user.fullname,
"phone_number": str(user.phone_number) if user.phone_number else None,
"email": user.email if user.email else None,
-<<<<<<< HEAD
- "avatar": user.avatar if user.avatar else None,
-=======
"avatar": request.build_absolute_uri(user.avatar.url) if user.avatar else None,
->>>>>>> develop
"message": "Forgot password code sent"
},
status=status.HTTP_202_ACCEPTED,
@@ -575,8 +522,6 @@ class UserDeleteView(APIView):
return Response({"detail": "User does not exist."}, status=status.HTTP_404_NOT_FOUND)
-<<<<<<< HEAD
-=======
class UpdateFCMView(GenericAPIView):
permission_classes = [IsAuthenticated]
serializer_class = UserFCMSerializer
@@ -592,6 +537,4 @@ class UpdateFCMView(GenericAPIView):
user.fcm = fcm_token
user.save()
- return Response({"detail": "FCM token updated successfully."}, status=status.HTTP_200_OK)
-
->>>>>>> develop
+ return Response({"detail": "FCM token updated successfully."}, status=status.HTTP_200_OK)
\ No newline at end of file
diff --git a/apps/certificate/migrations/0001_initial.py b/apps/certificate/migrations/0001_initial.py
index dac1abc..cc3af2b 100644
--- a/apps/certificate/migrations/0001_initial.py
+++ b/apps/certificate/migrations/0001_initial.py
@@ -1,16 +1,8 @@
-<<<<<<< HEAD
-# Generated by Django 3.2.7 on 2024-12-14 08:35
-
-from django.db import migrations, models
-import django.db.models.deletion
-import filer.fields.file
-=======
# Generated by Django 5.1.8 on 2025-04-03 00:05
import django.db.models.deletion
import filer.fields.file
from django.db import migrations, models
->>>>>>> develop
class Migration(migrations.Migration):
@@ -18,14 +10,8 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
-<<<<<<< HEAD
- ('course', '0005_participant_unread_messages_count'),
- ('account', '0005_user_city'),
- ('filer', '0015_auto_20241214_0835'),
-=======
('account', '0001_initial'),
('course', '0001_initial'),
->>>>>>> develop
]
operations = [
@@ -33,11 +19,7 @@ class Migration(migrations.Migration):
name='Certificate',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-<<<<<<< HEAD
- ('status', models.CharField(choices=[('pending', 'در حال بررسی'), ('approved', 'تایید شده'), ('canceled', 'لغو شده')], default='pending', max_length=10)),
-=======
('status', models.CharField(choices=[('pending', 'pending'), ('approved', 'approved'), ('canceled', 'canceled')], default='pending', max_length=10)),
->>>>>>> develop
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('certificate_file', filer.fields.file.FilerFileField(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='filer.file', verbose_name='certificate_file')),
diff --git a/apps/chat/migrations/0001_initial.py b/apps/chat/migrations/0001_initial.py
index ee278cc..6460652 100644
--- a/apps/chat/migrations/0001_initial.py
+++ b/apps/chat/migrations/0001_initial.py
@@ -1,16 +1,8 @@
-<<<<<<< HEAD
-# Generated by Django 3.2.4 on 2024-11-22 19:13
-
-from django.conf import settings
-from django.db import migrations, models
-import django.db.models.deletion
-=======
# Generated by Django 5.1.8 on 2025-04-03 00:05
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
->>>>>>> develop
class Migration(migrations.Migration):
@@ -18,19 +10,12 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
-<<<<<<< HEAD
- migrations.swappable_dependency(settings.AUTH_USER_MODEL),
- ('course', '0004_auto_20241122_1913'),
-=======
('course', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
->>>>>>> develop
]
operations = [
migrations.CreateModel(
-<<<<<<< HEAD
-=======
name='RoomMessage',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
@@ -46,29 +31,17 @@ class Migration(migrations.Migration):
],
),
migrations.CreateModel(
->>>>>>> develop
name='ChatMessage',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('content', models.TextField(verbose_name='Message Content')),
('content_type', models.CharField(choices=[('text', 'Text'), ('file', 'File'), ('audio', 'Audio'), ('image', 'Image')], default='text', max_length=10, verbose_name='Chat Type')),
('content_size', models.PositiveIntegerField(blank=True, null=True, verbose_name='Content Size (bytes)')),
-<<<<<<< HEAD
- ('is_to_professor', models.BooleanField(default=False, verbose_name='Is to Professor')),
-=======
('is_read', models.BooleanField(default=False, verbose_name='Is Read')),
->>>>>>> develop
('sent_at', models.DateTimeField(auto_now_add=True, verbose_name='Sent At')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')),
('deleted_at', models.DateTimeField(blank=True, null=True, verbose_name='Deleted At')),
('is_deleted', models.BooleanField(default=False, verbose_name='Is deleted')),
-<<<<<<< HEAD
- ('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='messages', to='course.course', verbose_name='Course')),
- ('recipient', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='messages_received', to=settings.AUTH_USER_MODEL, verbose_name='Recipient')),
- ('sender', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='messages_sent', to=settings.AUTH_USER_MODEL, verbose_name='Sender')),
- ],
- ),
-=======
('sender', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='messages_sent', to=settings.AUTH_USER_MODEL, verbose_name='Sender')),
('room', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='messages', to='chat.roommessage', verbose_name='Room')),
],
@@ -86,5 +59,4 @@ class Migration(migrations.Migration):
'unique_together': {('user', 'message')},
},
),
->>>>>>> develop
- ]
+ ]
\ No newline at end of file
diff --git a/apps/course/admin/__init__.py b/apps/course/admin/__init__.py
index b42b347..bff68d7 100644
--- a/apps/course/admin/__init__.py
+++ b/apps/course/admin/__init__.py
@@ -1,8 +1,4 @@
from .course import *
from .lesson import *
-<<<<<<< HEAD
-from .participant import *
-=======
from .participant import *
from .live_session import *
->>>>>>> develop
diff --git a/apps/course/admin/course.py b/apps/course/admin/course.py
index dbcb794..8fbcf21 100644
--- a/apps/course/admin/course.py
+++ b/apps/course/admin/course.py
@@ -1,38 +1,7 @@
-<<<<<<< HEAD
-
-=======
->>>>>>> develop
import os
import hashlib
from django.contrib import admin
-<<<<<<< HEAD
-from django import forms
-from ajaxdatatable.admin import AjaxDatatable
-
-from utils.json_editor_field import JsonEditorWidget
-from apps.course.models import Course, Glossary, Attachment, CourseCategory
-from utils.schema import get_weekly_timing_schema, get_course_feature_schema
-
-
-
-@admin.register(CourseCategory)
-class CourseCategoryAdmin(admin.ModelAdmin):
- list_display = ('name', 'slug')
- search_fields = ('name',)
- exclude = ('slug', )
-
-
-
-class CourseForm(forms.ModelForm):
- class Meta:
- model = Course
- fields = '__all__'
- # exclude = ('slug',)
- widgets = {
- 'timing': JsonEditorWidget(attrs={'schema': get_weekly_timing_schema}),
- 'features': JsonEditorWidget(attrs={'schema': get_course_feature_schema}),
-=======
from django.contrib import messages
from django import forms
from django.utils.translation import gettext_lazy as _
@@ -134,49 +103,10 @@ class CourseForm(forms.ModelForm):
'schema': get_course_feature_schema(),
'title': _('Course Features'),
}),
->>>>>>> develop
}
help_texts = {
'status': 'If set to inactive, the course will not be displayed.',
}
-<<<<<<< HEAD
- # def __init__(self, *args, **kwargs):
- # super(CourseForm, self).__init__(*args, **kwargs)
- # # اضافه کردن help_text به فیلد status
- # self.fields['status'].help_text = _(
- # "If set to 'Inactive', this item will not be displayed."
- # )
-
-
-
-
-@admin.register(Course)
-class CourseAdmin(AjaxDatatable):
- form = CourseForm
- list_display = ('title', 'category', 'level', 'status', 'final_price', 'is_online')
- list_filter = ('status', 'level', 'is_online', 'is_free', 'category')
- search_fields = ('title', 'description')
- exclude = ('slug', )
-
- # def has_change_permission(self, request, obj=None):
- # return False
- # @admin.display(description='Add Student')
- # def _add_student(self, obj):
-
-
-
-
-
-
-@admin.register(Glossary)
-class GlossaryAdmin(admin.ModelAdmin):
- list_display = ('title', 'course', 'description')
- list_filter = ('course',)
- search_fields = ('title', 'description', 'course__title')
- ordering = ('-id',)
-
-
-=======
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -543,7 +473,6 @@ class CourseGlossaryAdmin(CourseRelatedAdmin):
@admin.display(description=_("Description"))
def glossary_description(self, obj):
return obj.glossary.description
->>>>>>> develop
class AttachmentAdminForm(forms.ModelForm):
@@ -576,32 +505,18 @@ class AttachmentAdminForm(forms.ModelForm):
return f"{base_part}{hash_part}{ext}" # ترکیب بخش اصلی و هش با پسوند
return file_name
-<<<<<<< HEAD
-
-
-@admin.register(Attachment)
-class AttachmentAdmin(admin.ModelAdmin):
- form = AttachmentAdminForm
- list_display = ('title', 'course', 'file', 'file_size')
- list_filter = ('course',)
- search_fields = ('title', 'file', 'course__title')
-=======
class AttachmentAdmin(AttachmentGlossaryBaseAdmin):
form = AttachmentAdminForm
list_display = ('title', 'file', 'file_size')
search_fields = ('title', 'file')
->>>>>>> develop
def save_model(self, request, obj, form, change):
if obj.file:
obj.file_size = obj.file.size
super().save_model(request, obj, form, change)
-<<<<<<< HEAD
-
-=======
def is_used_in_professor_courses(self, user, obj):
"""آیا این attachment در دورههای استاد استفاده شده؟"""
return obj.courseattachment_set.filter(course__professor=user).exists()
@@ -679,4 +594,3 @@ class HiddenCourseAdmin(ModelAdmin):
return False
dovoodi_admin_site.register(Course, HiddenCourseAdmin)
->>>>>>> develop
diff --git a/apps/course/admin/lesson.py b/apps/course/admin/lesson.py
index 2094009..a244412 100644
--- a/apps/course/admin/lesson.py
+++ b/apps/course/admin/lesson.py
@@ -1,29 +1,3 @@
-<<<<<<< HEAD
-from django.contrib import admin
-
-from apps.course.models import Lesson, LessonCompletion
-
-
-
-
-@admin.register(Lesson)
-class LessonAdmin(admin.ModelAdmin):
- list_display = ('title', 'course', 'priority', 'duration', 'content_type')
- list_filter = ('course', 'content_type')
- search_fields = ('title', 'course__title')
- ordering = ('priority', 'title')
-
- def get_queryset(self, request):
- qs = super().get_queryset(request)
- return qs.order_by('priority')
-
-
-@admin.register(LessonCompletion)
-class LessonCompletionAdmin(admin.ModelAdmin):
- list_display = ('student', 'lesson', 'completed_at')
- search_fields = ('student__fullname', 'student__email', 'lesson__title', 'lesson__course__title')
- list_filter = ('lesson__course', 'completed_at')
-=======
import os
from django.contrib import admin
from django import forms
@@ -149,18 +123,12 @@ class LessonCompletionAdmin(ModelAdmin):
list_display = ('student', 'course_lesson', 'completed_at')
search_fields = ('student__fullname', 'student__email', 'course_lesson__title', 'course_lesson__course__title')
list_filter = ('course_lesson__course', 'completed_at')
->>>>>>> develop
ordering = ('-completed_at',)
def get_readonly_fields(self, request, obj=None):
"""
Make fields readonly if the object already exists.
"""
-<<<<<<< HEAD
- if obj:
- return ['student', 'lesson', 'completed_at']
- return []
-=======
if obj:
return ['student', 'course_lesson', 'completed_at']
return []
@@ -174,4 +142,3 @@ django_admin.site.register(Lesson, LessonAdmin)
project_admin_site.register(Lesson, LessonAdmin)
project_admin_site.register(CourseLesson, CourseLessonAdmin)
project_admin_site.register(LessonCompletion, LessonCompletionAdmin)
->>>>>>> develop
diff --git a/apps/course/migrations/0001_initial.py b/apps/course/migrations/0001_initial.py
index 0098592..2b829ce 100644
--- a/apps/course/migrations/0001_initial.py
+++ b/apps/course/migrations/0001_initial.py
@@ -1,14 +1,3 @@
-<<<<<<< HEAD
-# Generated by Django 3.2.4 on 2024-11-21 20:46
-
-import apps.course.models.course
-import apps.course.models.lesson
-from django.conf import settings
-from django.db import migrations, models
-import django.db.models.deletion
-import filer.fields.image
-import utils.schema
-=======
# Generated by Django 5.1.8 on 2025-04-03 00:05
import apps.course.models.course
@@ -18,7 +7,6 @@ import filer.fields.image
import utils.schema
from django.conf import settings
from django.db import migrations, models
->>>>>>> develop
class Migration(migrations.Migration):
@@ -26,18 +14,12 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
-<<<<<<< HEAD
- ('account', '0003_auto_20241120_1741'),
-=======
('account', '0001_initial'),
->>>>>>> develop
migrations.swappable_dependency(settings.FILER_IMAGE_MODEL),
]
operations = [
migrations.CreateModel(
-<<<<<<< HEAD
-=======
name='CourseCategory',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
@@ -46,7 +28,6 @@ class Migration(migrations.Migration):
],
),
migrations.CreateModel(
->>>>>>> develop
name='Course',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
@@ -56,10 +37,7 @@ class Migration(migrations.Migration):
('video_file', models.FileField(blank=True, null=True, upload_to=apps.course.models.course.course_file_upload_to)),
('video_link', models.CharField(blank=True, max_length=500, null=True, verbose_name='Video Link')),
('is_online', models.BooleanField(default=True, verbose_name='Is Online Course')),
-<<<<<<< HEAD
-=======
('online_link', models.CharField(blank=True, max_length=500, null=True, verbose_name='Online Class Link')),
->>>>>>> develop
('level', models.CharField(choices=[('beginner', 'Beginner'), ('mid', 'Mid Level'), ('advanced', 'Advanced')], max_length=10, verbose_name='Course Level')),
('duration', models.PositiveIntegerField(verbose_name='Duration (in hours)')),
('lessons_count', models.PositiveIntegerField(verbose_name='Number of Lessons')),
@@ -72,14 +50,11 @@ class Migration(migrations.Migration):
('final_price', models.DecimalField(blank=True, decimal_places=2, default=0.0, help_text='This field is automatically calculated based on the discount percentage.', max_digits=10, verbose_name='Course Final Price')),
('timing', models.JSONField(blank=True, default=utils.schema.default_timing, help_text='The Timing information in JSON format.', null=True, verbose_name='Timing')),
('features', models.JSONField(blank=True, default=dict, null=True, verbose_name='Course features')),
-<<<<<<< HEAD
-=======
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')),
('professor', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='courses', to='account.professoruser')),
('thumbnail', filer.fields.image.FilerImageField(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.FILER_IMAGE_MODEL, verbose_name='thumbnail')),
('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='courses', to='course.coursecategory', verbose_name='Category')),
->>>>>>> develop
],
options={
'verbose_name': 'Course',
@@ -87,59 +62,6 @@ class Migration(migrations.Migration):
},
),
migrations.CreateModel(
-<<<<<<< HEAD
- name='CourseCategory',
- fields=[
- ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('name', models.CharField(max_length=255, verbose_name='Category Name')),
- ('slug', models.SlugField(max_length=255, unique=True)),
- ],
- ),
- migrations.CreateModel(
- name='Lesson',
- fields=[
- ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('title', models.CharField(max_length=255, verbose_name='Lesson Title')),
- ('priority', models.IntegerField(blank=True, null=True, verbose_name='Priority')),
- ('duration', models.PositiveIntegerField(verbose_name='Duration (in minutes)')),
- ('content_type', models.CharField(choices=[('link', 'Link'), ('file', 'File')], max_length=10, verbose_name='Content Type')),
- ('content_file', models.FileField(blank=True, null=True, upload_to=apps.course.models.lesson.lesson_file_upload_to)),
- ('video_link', models.CharField(blank=True, max_length=500, null=True, verbose_name='Video Link')),
- ('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='lessons', to='course.course', verbose_name='Course')),
- ],
- ),
- migrations.CreateModel(
- name='Glossary',
- fields=[
- ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('title', models.CharField(max_length=555, verbose_name='Glossary Title')),
- ('description', models.TextField(verbose_name='Description')),
- ('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='glossaries', to='course.course', verbose_name='Course')),
- ],
- options={
- 'verbose_name': 'Glossary',
- 'verbose_name_plural': 'Glossary',
- 'ordering': ('-id',),
- },
- ),
- migrations.AddField(
- model_name='course',
- name='category',
- field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='courses', to='course.coursecategory', verbose_name='Category'),
- ),
- migrations.AddField(
- model_name='course',
- name='professor',
- field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='courses', to='account.professoruser'),
- ),
- migrations.AddField(
- model_name='course',
- name='thumbnail',
- field=filer.fields.image.FilerImageField(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.FILER_IMAGE_MODEL, verbose_name='thumbnail'),
- ),
- migrations.CreateModel(
-=======
->>>>>>> develop
name='Attachment',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
@@ -154,8 +76,6 @@ class Migration(migrations.Migration):
'ordering': ('-id',),
},
),
-<<<<<<< HEAD
-=======
migrations.CreateModel(
name='Glossary',
fields=[
@@ -212,5 +132,4 @@ class Migration(migrations.Migration):
'unique_together': {('student', 'course')},
},
),
->>>>>>> develop
- ]
+ ]
\ No newline at end of file
diff --git a/apps/course/models/__init__.py b/apps/course/models/__init__.py
index b42b347..bff68d7 100644
--- a/apps/course/models/__init__.py
+++ b/apps/course/models/__init__.py
@@ -1,8 +1,4 @@
from .course import *
from .lesson import *
-<<<<<<< HEAD
-from .participant import *
-=======
from .participant import *
from .live_session import *
->>>>>>> develop
diff --git a/apps/course/models/course.py b/apps/course/models/course.py
index 06eb96a..1a47a8e 100644
--- a/apps/course/models/course.py
+++ b/apps/course/models/course.py
@@ -4,11 +4,6 @@ import math
from django.db import models
from django.db.models import TextChoices
from django.utils.translation import gettext_lazy as _
-<<<<<<< HEAD
-from filer.fields.image import FilerImageField
-from filer.fields.file import FilerFileField
-=======
->>>>>>> develop
from apps.account.models import ProfessorUser
from utils.schema import default_timing
@@ -20,16 +15,11 @@ def course_file_upload_to(instance, filename):
return os.path.join(f"courses/{instance.slug}/videos/{filename}")
-<<<<<<< HEAD
-
-def attachment_file_upload_to(instance, filename):
-=======
def attachment_file_upload_to(instance, filename):
return os.path.join(f"attachments/{filename}")
def course_attachment_file_upload_to(instance, filename):
->>>>>>> develop
return os.path.join(f"courses/{instance.course.slug}/attachments/{filename}")
@@ -43,22 +33,14 @@ class CourseCategory(models.Model):
return self.name
def save(self, *args, **kwargs):
-<<<<<<< HEAD
- self.slug = generate_slug_for_model(CourseCategory, self.name)
-=======
if not self.slug:
self.slug = generate_slug_for_model(CourseCategory, self.name)
->>>>>>> develop
super().save(*args, **kwargs)
@property
def course_count(self):
return self.courses.exclude(status="inactive").count()
-<<<<<<< HEAD
-
-=======
->>>>>>> develop
class Course(models.Model):
class LevelChoices(TextChoices):
@@ -74,13 +56,8 @@ class Course(models.Model):
FINISHED = 'finished', 'Finished' # Finished (course has ended)-закончился
class VedioTypeChoices(models.TextChoices):
-<<<<<<< HEAD
- VIDEO_FILE = 'video_file', 'Video File'
- VIDEO_LINK = 'video_link', 'Video Link'
-=======
YOUTUBE_LINK = 'youtube_link', 'Youtube Link'
VIDEO_FILE = 'video_file', 'Video File'
->>>>>>> develop
title = models.CharField(max_length=255, verbose_name='Course Title')
@@ -92,34 +69,20 @@ class Course(models.Model):
related_name="courses"
)
-<<<<<<< HEAD
- thumbnail = FilerImageField(
- related_name='+', on_delete=models.PROTECT, null=True, blank=True,
- verbose_name=_('thumbnail')
- )
- video_type = models.CharField(max_length=20, choices=VedioTypeChoices.choices, verbose_name='Vedio Type')
-=======
thumbnail = models.ImageField(upload_to="courses/thumbnails/", verbose_name=_('Thumbnail'))
video_type = models.CharField(
max_length=20,
choices=VedioTypeChoices.choices,
verbose_name='Preview Video Type (YouTube Link or File Upload)'
)
->>>>>>> develop
video_file = models.FileField(
upload_to=course_file_upload_to,
null=True,
blank=True
)
-<<<<<<< HEAD
- video_link = models.CharField(max_length=500, null=True, blank=True, verbose_name='Video Link')
-
- is_online = models.BooleanField(default=True, verbose_name='Is Online Course')
-=======
video_link = models.CharField(max_length=500, null=True, blank=True)
is_online = models.BooleanField(default=False, verbose_name='Is Online Course')
->>>>>>> develop
online_link = models.CharField(max_length=500, null=True, blank=True, verbose_name='Online Class Link')
level = models.CharField(max_length=10, choices=LevelChoices.choices, verbose_name='Course Level')
duration = models.PositiveIntegerField(verbose_name='Duration (in hours)')
@@ -136,11 +99,7 @@ class Course(models.Model):
help_text=_('This field is automatically calculated based on the discount percentage.')
)
-<<<<<<< HEAD
- timing = models.JSONField(blank=True, null=True, default=default_timing, verbose_name=_("Timing"), help_text=_("The Timing information in JSON format."))
-=======
timing = models.JSONField(blank=True, null=True, default=default_timing, verbose_name=_("Timing"))
->>>>>>> develop
features = models.JSONField(verbose_name=_('Course features'), default=dict, blank=True, null=True)
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created at"))
updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Updated At"))
@@ -157,11 +116,6 @@ class Course(models.Model):
def save(self, *args, **kwargs):
-<<<<<<< HEAD
- self.slug = generate_slug_for_model(Course, self.title)
-
- if self.discount_percentage > 0:
-=======
if not self.slug:
self.slug = generate_slug_for_model(Course, self.title)
@@ -175,7 +129,6 @@ class Course(models.Model):
self.discount_percentage = 0
self.final_price = Decimal('0.00')
elif self.discount_percentage > 0:
->>>>>>> develop
discount_amount = (self.price * self.discount_percentage) / 100
final_price = self.price - discount_amount
self.final_price = Decimal(math.ceil(final_price)).quantize(Decimal('0.00'))
@@ -188,29 +141,6 @@ class Course(models.Model):
class Meta:
verbose_name = "Course"
verbose_name_plural = "Courses"
-<<<<<<< HEAD
-
-
-
-class Glossary(models.Model):
- course = models.ForeignKey(Course, on_delete=models.CASCADE, related_name='glossaries', verbose_name='Course')
- title = models.CharField(max_length=555, verbose_name='Glossary Title')
- description = models.TextField(verbose_name='Description')
-
- def __str__(self):
- return f"{self.course.title} - {self.title}"
-
-
- class Meta:
- ordering = ("-id",)
- verbose_name = "Glossary"
- verbose_name_plural = "Glossary"
-
-
-
-class Attachment(models.Model):
- course = models.ForeignKey(Course, on_delete=models.CASCADE, related_name='attachments', verbose_name='Course')
-=======
indexes = [
models.Index(fields=['status']),
models.Index(fields=['is_free']),
@@ -269,37 +199,20 @@ class Attachment(models.Model):
"""
Base Attachment model that contains the actual file
"""
->>>>>>> develop
title = models.CharField(max_length=255, verbose_name='Attachment Title')
file = models.FileField(
upload_to=attachment_file_upload_to,
verbose_name='Attachment File'
)
-<<<<<<< HEAD
-
- file_size = models.PositiveIntegerField(verbose_name='File Size (in bytes)', null=True, blank=True)
-=======
file_size = models.PositiveIntegerField(verbose_name='File Size (in bytes)', null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created at"))
updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Updated At"))
->>>>>>> develop
def save(self, *args, **kwargs):
# Calculate the file size before saving
if self.file and not self.file_size:
self.file_size = self.file.size
super().save(*args, **kwargs)
-<<<<<<< HEAD
-
-
- def __str__(self):
- return f"{self.course.title} - {self.title}"
-
- class Meta:
- ordering = ("-id",)
- verbose_name = "Attachment"
- verbose_name_plural = "Attachments"
-=======
def __str__(self):
return self.title
@@ -341,4 +254,3 @@ class CourseAttachment(models.Model):
models.Index(fields=['course']),
models.Index(fields=['attachment']),
]
->>>>>>> develop
diff --git a/apps/course/models/lesson.py b/apps/course/models/lesson.py
index 0f0ec96..c3f12cb 100644
--- a/apps/course/models/lesson.py
+++ b/apps/course/models/lesson.py
@@ -9,28 +9,11 @@ from apps.account.models import StudentUser
def lesson_file_upload_to(instance, filename):
-<<<<<<< HEAD
- return os.path.join(f"courses/{instance.course.slug}/lessons/{filename}")
-
-=======
return os.path.join(f"lessons/{filename}")
->>>>>>> develop
class Lesson(models.Model):
-<<<<<<< HEAD
- class ContentTypeChoices(models.TextChoices):
- YOUTUBE_LINK = 'youtube_link', 'Youtube Link'
- VIDEO_FILE = 'video_file', 'Video File'
- AUDIO_FILE = 'audio_file', 'Audio File'
-
- course = models.ForeignKey("course.Course", on_delete=models.CASCADE, related_name='lessons', verbose_name='Course')
- title = models.CharField(max_length=255, verbose_name='Lesson Title')
- priority = models.IntegerField(null=True, blank=True, verbose_name='Priority')
- is_active = models.BooleanField(default=True, verbose_name=_('Is Active'))
- duration = models.PositiveIntegerField(verbose_name='Duration (in minutes)')
-=======
"""
Base Lesson model that contains the actual content (video file or link)
"""
@@ -39,7 +22,6 @@ class Lesson(models.Model):
VIDEO_FILE = 'video_file', 'Video File'
title = models.CharField(max_length=255, verbose_name='Lesson Title')
->>>>>>> develop
content_type = models.CharField(max_length=50, choices=ContentTypeChoices.choices, verbose_name='Content Type')
content_file = models.FileField(
null=True,
@@ -47,15 +29,6 @@ class Lesson(models.Model):
upload_to=lesson_file_upload_to,
)
video_link = models.CharField(max_length=500, null=True, blank=True, verbose_name='Link')
-<<<<<<< HEAD
-
- created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created at"))
- updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Updated At"))
-
-
- def __str__(self):
- return f"{self.course.title} - {self.title}"
-=======
duration = models.PositiveIntegerField(verbose_name='Duration (in minutes)')
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created at"))
updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Updated At"))
@@ -87,16 +60,10 @@ class CourseLesson(models.Model):
def __str__(self):
title = self.title or self.lesson.title
return f"{self.course.title} - {title}"
->>>>>>> develop
def is_completed_by(self, student):
return self.completions.filter(student=student).exists()
-<<<<<<< HEAD
-
- def save(self, *args, **kwargs):
- print(f'---> start')
-=======
@property
def content_type(self):
return self.lesson.content_type
@@ -118,53 +85,19 @@ class CourseLesson(models.Model):
if not self.title:
self.title = self.lesson.title
->>>>>>> develop
if self.priority is None:
# If priority is not set, set it to the next available priority
max_priority = self.course.lessons.aggregate(max_priority=models.Max('priority'))['max_priority']
self.priority = (max_priority or 0) + 1
-<<<<<<< HEAD
- else:
- self._adjust_priorities()
- super().save(*args, **kwargs)
-
-
-=======
else:
self._adjust_priorities()
super().save(*args, **kwargs)
->>>>>>> develop
def _adjust_priorities(self):
# Adjust priorities of other lessons in the course
lessons = self.course.lessons.exclude(pk=self.pk)
# Shift priorities for lessons with the same or higher priority
lessons.filter(priority__gte=self.priority).update(priority=models.F('priority') + 1)
-<<<<<<< HEAD
-
-
- # # If priority is set, adjust the priorities of other lessons
- # lessons = self.course.lessons.exclude(pk=self.pk).order_by('priority')
-
- # updated_priorities = []
- # inserted = False
-
- # for lesson in lessons:
- # if lesson.priority >= self.priority and not inserted:
- # updated_priorities.append((self.priority, self))
- # inserted = True
- # updated_priorities.append((lesson.priority if not inserted else lesson.priority + 1, lesson))
-
- # if not inserted:
- # updated_priorities.append((self.priority, self))
-
- # # Update priorities in bulk
- # for priority, lesson in updated_priorities:
- # lesson.priority = priority
- # lesson.save(update_fields=['priority'])
-
-
-=======
class Meta:
verbose_name = "Course Lesson"
@@ -178,7 +111,6 @@ class CourseLesson(models.Model):
models.Index(fields=['course', 'is_active']),
]
->>>>>>> develop
class LessonCompletion(models.Model):
student = models.ForeignKey(
@@ -186,13 +118,8 @@ class LessonCompletion(models.Model):
on_delete=models.CASCADE,
related_name='lesson_completions'
)
-<<<<<<< HEAD
- lesson = models.ForeignKey(
- Lesson,
-=======
course_lesson = models.ForeignKey(
CourseLesson,
->>>>>>> develop
on_delete=models.CASCADE,
related_name='completions'
)
@@ -200,12 +127,6 @@ class LessonCompletion(models.Model):
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created at"))
class Meta:
-<<<<<<< HEAD
- unique_together = ('student', 'lesson')
-
- def __str__(self):
- return f"{self.student.fullname} - {self.lesson.title} - Completed"
-=======
unique_together = ('student', 'course_lesson')
indexes = [
models.Index(fields=['student']),
@@ -216,6 +137,5 @@ class LessonCompletion(models.Model):
def __str__(self):
return f"{self.student.fullname} - {self.course_lesson.title} - Completed"
->>>>>>> develop
\ No newline at end of file
diff --git a/apps/course/models/participant.py b/apps/course/models/participant.py
index b6c7061..80947f3 100644
--- a/apps/course/models/participant.py
+++ b/apps/course/models/participant.py
@@ -17,17 +17,11 @@ class Participant(models.Model):
on_delete=models.CASCADE,
related_name='participants'
)
-<<<<<<< HEAD
-=======
is_active = models.BooleanField(default=True)
->>>>>>> develop
joined_date = models.DateTimeField(auto_now_add=True)
unread_messages_count = models.IntegerField(default=0)
class Meta:
-<<<<<<< HEAD
- unique_together = ('student', 'course')
-=======
unique_together = ('student', 'course')
indexes = [
models.Index(fields=['student']),
@@ -35,4 +29,3 @@ class Participant(models.Model):
models.Index(fields=['joined_date']),
models.Index(fields=['student', 'course']),
]
->>>>>>> develop
diff --git a/apps/course/serializers/__init__.py b/apps/course/serializers/__init__.py
index affa72b..cb5e526 100644
--- a/apps/course/serializers/__init__.py
+++ b/apps/course/serializers/__init__.py
@@ -1,9 +1,5 @@
from .course import *
from .lesson import *
-<<<<<<< HEAD
-from .participant import *
-=======
from .participant import *
from .online import *
from .professor import *
->>>>>>> develop
diff --git a/apps/course/serializers/course.py b/apps/course/serializers/course.py
index ae0e1e8..76237ff 100644
--- a/apps/course/serializers/course.py
+++ b/apps/course/serializers/course.py
@@ -1,14 +1,8 @@
from rest_framework import serializers
-<<<<<<< HEAD
-from dj_filer.admin import get_thumbs
-
-from apps.course.models import Course, CourseCategory, Attachment, Glossary, LessonCompletion, Participant, Lesson
-=======
# from dj_filer.admin import get_thumbs
from utils import get_thumbs
from apps.course.models import Course, CourseCategory, Attachment, Glossary, LessonCompletion, Participant, Lesson, CourseAttachment, CourseGlossary, CourseLesson
->>>>>>> develop
from apps.chat.models import RoomMessage
from apps.account.serializers import UserProfileSerializer
@@ -30,15 +24,11 @@ class CourseListSerializer(serializers.ModelSerializer):
thumbnail = serializers.SerializerMethodField()
participant_count = serializers.SerializerMethodField()
lessons_count = serializers.SerializerMethodField()
-<<<<<<< HEAD
-
-=======
price = serializers.SerializerMethodField()
discount_percentage = serializers.SerializerMethodField()
final_price = serializers.SerializerMethodField()
is_free = serializers.SerializerMethodField()
->>>>>>> develop
class Meta:
model = Course
fields = [
@@ -68,13 +58,6 @@ class CourseListSerializer(serializers.ModelSerializer):
return obj.participants.count()
def get_lessons_count(self, obj):
-<<<<<<< HEAD
- lessons_count = obj.lessons.filter(is_active=True).count()
- return max(lessons_count, obj.lessons_count)
-
-
-
-=======
# Use prefetched lessons if available
if hasattr(obj, 'lessons') and obj.lessons.all():
lessons_count = sum(1 for lesson in obj.lessons.all() if lesson.is_active)
@@ -100,16 +83,11 @@ class CourseListSerializer(serializers.ModelSerializer):
def get_is_free(self, obj):
return obj.is_free or obj.price == 0
->>>>>>> develop
class CourseDetailSerializer(serializers.ModelSerializer):
category = CourseCategorySerializer()
-<<<<<<< HEAD
- professor = UserProfileSerializer()
-=======
professor = serializers.SerializerMethodField()
->>>>>>> develop
thumbnail = serializers.SerializerMethodField()
participant_count = serializers.SerializerMethodField()
access = serializers.SerializerMethodField()
@@ -117,9 +95,6 @@ class CourseDetailSerializer(serializers.ModelSerializer):
lessons_count = serializers.SerializerMethodField()
last_lesson_id = serializers.SerializerMethodField()
room_id = serializers.SerializerMethodField()
-<<<<<<< HEAD
-
-=======
user_transaction_status = serializers.SerializerMethodField()
price = serializers.SerializerMethodField()
discount_percentage = serializers.SerializerMethodField()
@@ -127,7 +102,6 @@ class CourseDetailSerializer(serializers.ModelSerializer):
is_free = serializers.SerializerMethodField()
is_professor = serializers.SerializerMethodField()
->>>>>>> develop
class Meta:
model = Course
fields = [
@@ -138,10 +112,7 @@ class CourseDetailSerializer(serializers.ModelSerializer):
'access',
'participant_count',
'professor',
-<<<<<<< HEAD
-=======
'is_professor',
->>>>>>> develop
'thumbnail',
'video_type',
'video_file',
@@ -163,11 +134,6 @@ class CourseDetailSerializer(serializers.ModelSerializer):
'features',
'last_lesson_id',
'room_id',
-<<<<<<< HEAD
- ]
-
- def get_room_id(self, obj):
-=======
'user_transaction_status'
]
@@ -176,14 +142,11 @@ class CourseDetailSerializer(serializers.ModelSerializer):
if hasattr(obj, 'room_messages') and obj.room_messages.all():
return obj.room_messages.first().id
# Fallback to direct query if not prefetched
->>>>>>> develop
room_message = RoomMessage.objects.filter(course=obj).first()
if room_message:
return room_message.id
return None
-<<<<<<< HEAD
-=======
def get_user_transaction_status(self, obj):
from apps.transaction.models import TransactionParticipant
if student := self._get_authenticated_user():
@@ -196,29 +159,10 @@ class CourseDetailSerializer(serializers.ModelSerializer):
return latest_transaction.status
return None
->>>>>>> develop
def get_last_lesson_id(self, obj):
request = self.context.get('request')
if request and request.user.is_authenticated:
user = request.user
-<<<<<<< HEAD
-
- # آخرین درس تکمیلشده توسط کاربر
- last_completed_lesson = LessonCompletion.objects.filter(
- student=user,
- lesson__course=obj
- ).order_by('-completed_at').first()
-
- if last_completed_lesson:
- # پیدا کردن درس بعدی بر اساس priority
- next_lesson = Lesson.objects.filter(
- course=obj,
- priority__gt=last_completed_lesson.lesson.priority,
- is_active=True
- ).order_by('priority').first()
- if not next_lesson:
- next_lesson = Lesson.objects.filter(
-=======
# Use prefetched lessons if available
if hasattr(obj, 'lessons') and obj.lessons.all():
@@ -257,16 +201,11 @@ class CourseDetailSerializer(serializers.ModelSerializer):
).order_by('priority').first()
if not next_lesson:
next_lesson = CourseLesson.objects.filter(
->>>>>>> develop
course=obj,
is_active=True
).order_by('priority').first()
if next_lesson:
-<<<<<<< HEAD
- return next_lesson.id
-=======
return next_lesson.id
->>>>>>> develop
return None
@@ -277,15 +216,12 @@ class CourseDetailSerializer(serializers.ModelSerializer):
return False
return True
return False
-<<<<<<< HEAD
-=======
def get_professor(self, obj):
"""Return the course professor's profile using UserProfileSerializer"""
if obj.professor:
return UserProfileSerializer(obj.professor, context=self.context).data
return None
->>>>>>> develop
def get_is_professor(self, obj):
if professor := self._get_authenticated_user():
@@ -293,14 +229,11 @@ class CourseDetailSerializer(serializers.ModelSerializer):
return False
def get_lessons_count(self, obj):
-<<<<<<< HEAD
-=======
# Use prefetched lessons if available
if hasattr(obj, 'lessons') and obj.lessons.all():
lessons_count = sum(1 for lesson in obj.lessons.all() if lesson.is_active)
return max(lessons_count, obj.lessons_count)
# Fallback to direct query
->>>>>>> develop
lessons_count = obj.lessons.filter(is_active=True).count()
return max(lessons_count, obj.lessons_count)
@@ -309,14 +242,10 @@ class CourseDetailSerializer(serializers.ModelSerializer):
if student := self._get_authenticated_user():
if not self._is_participant(student, obj):
return None
-<<<<<<< HEAD
- return self._get_completed_lessons_count(student, obj)
-=======
completed_count = self._get_completed_lessons_count(student, obj)
# Ensure completed count doesn't exceed total lessons count
total_lessons = self.get_lessons_count(obj)
return min(completed_count, total_lessons)
->>>>>>> develop
return None
def _is_participant(self, student, course):
@@ -330,11 +259,6 @@ class CourseDetailSerializer(serializers.ModelSerializer):
def _get_completed_lessons_count(self, student, course):
"""Helper method to count completed lessons for the student in the given course."""
-<<<<<<< HEAD
- return LessonCompletion.objects.filter(
- student=student,
- lesson__course=course
-=======
# Use prefetched completions if available
if hasattr(course, 'lessons') and course.lessons.all():
completed_count = 0
@@ -348,7 +272,6 @@ class CourseDetailSerializer(serializers.ModelSerializer):
return LessonCompletion.objects.filter(
student=student,
course_lesson__course=course
->>>>>>> develop
).count()
@@ -356,9 +279,6 @@ class CourseDetailSerializer(serializers.ModelSerializer):
return get_thumbs(obj.thumbnail, self.context.get('request'))
def get_participant_count(self, obj):
-<<<<<<< HEAD
- return obj.participants.count()
-=======
# Use prefetched participants if available
if hasattr(obj, 'participants') and obj.participants.all():
return len(obj.participants.all())
@@ -381,25 +301,17 @@ class CourseDetailSerializer(serializers.ModelSerializer):
return str(obj.final_price)
def get_is_free(self, obj):
return obj.is_free or obj.price == 0
->>>>>>> develop
class MyCourseListSerializer(serializers.ModelSerializer):
category = CourseCategorySerializer()
thumbnail = serializers.SerializerMethodField()
-<<<<<<< HEAD
- lessons_complated_count = serializers.SerializerMethodField()
-
- class Meta:
- model = Course
-=======
lessons_count = serializers.SerializerMethodField()
lessons_complated_count = serializers.SerializerMethodField()
class Meta:
model = Course
->>>>>>> develop
fields = [
'id',
'title',
@@ -414,9 +326,6 @@ class MyCourseListSerializer(serializers.ModelSerializer):
def get_thumbnail(self, obj):
return get_thumbs(obj.thumbnail, self.context.get('request'))
-<<<<<<< HEAD
-
-=======
def get_lessons_count(self, obj):
"""Get the actual count of active lessons"""
@@ -428,30 +337,22 @@ class MyCourseListSerializer(serializers.ModelSerializer):
lessons_count = obj.lessons.filter(is_active=True).count()
return max(lessons_count, obj.lessons_count)
->>>>>>> develop
def get_lessons_complated_count(self, obj):
if student := self._get_authenticated_user():
if not self._is_participant(student, obj):
return None
-<<<<<<< HEAD
- return self._get_completed_lessons_count(student, obj)
-=======
completed_count = self._get_completed_lessons_count(student, obj)
# Ensure completed count doesn't exceed total lessons count
total_lessons = self.get_lessons_count(obj)
return min(completed_count, total_lessons)
->>>>>>> develop
return None
def _is_participant(self, student, course):
"""Helper method to check if a student is a participant in the given course."""
-<<<<<<< HEAD
-=======
# اگر کاربر استاد دوره است، دسترسی کامل دارد
if course.professor == student:
return True
# در غیر این صورت چک میکنیم که آیا participant است یا خیر
->>>>>>> develop
return Participant.objects.filter(student=student, course=course).exists()
def _get_authenticated_user(self):
@@ -461,11 +362,6 @@ class MyCourseListSerializer(serializers.ModelSerializer):
def _get_completed_lessons_count(self, student, course):
"""Helper method to count completed lessons for the student in the given course."""
-<<<<<<< HEAD
- return LessonCompletion.objects.filter(
- student=student,
- lesson__course=course
-=======
# Use prefetched completions if available
if hasattr(course, 'lessons') and course.lessons.all():
completed_count = 0
@@ -479,7 +375,6 @@ class MyCourseListSerializer(serializers.ModelSerializer):
return LessonCompletion.objects.filter(
student=student,
course_lesson__course=course
->>>>>>> develop
).count()
@@ -487,8 +382,6 @@ class AttachmentSerializer(serializers.ModelSerializer):
class Meta:
model = Attachment
fields = ['id', 'title', 'file', 'file_size']
-<<<<<<< HEAD
-=======
class CourseAttachmentSerializer(serializers.ModelSerializer):
@@ -499,14 +392,11 @@ class CourseAttachmentSerializer(serializers.ModelSerializer):
class Meta:
model = CourseAttachment
fields = ['id', 'title', 'file', 'file_size']
->>>>>>> develop
class GlossarySerializer(serializers.ModelSerializer):
class Meta:
model = Glossary
-<<<<<<< HEAD
-=======
fields = ['id', 'title', 'description']
@@ -516,5 +406,4 @@ class CourseGlossarySerializer(serializers.ModelSerializer):
class Meta:
model = CourseGlossary
->>>>>>> develop
fields = ['id', 'title', 'description']
\ No newline at end of file
diff --git a/apps/course/serializers/lesson.py b/apps/course/serializers/lesson.py
index 5e66e14..dd8c972 100644
--- a/apps/course/serializers/lesson.py
+++ b/apps/course/serializers/lesson.py
@@ -1,21 +1,4 @@
from rest_framework import serializers
-<<<<<<< HEAD
-from apps.course.models import Lesson, Participant, LessonCompletion
-from apps.quiz.serializers import QuizListSerializer
-
-
-
-
-
-class LessonSerializer(serializers.ModelSerializer):
- is_complated = serializers.SerializerMethodField()
- quizs = serializers.SerializerMethodField()
- permission = serializers.SerializerMethodField()
-
- class Meta:
- model = Lesson
- fields = ['id', 'title', 'priority', 'is_active', 'permission','duration', 'content_type', 'content_file', 'video_link', 'is_complated', 'quizs']
-=======
from apps.course.models import Lesson, CourseLesson, Participant, LessonCompletion
from apps.quiz.serializers import QuizListSerializer
@@ -38,7 +21,6 @@ class CourseLessonSerializer(serializers.ModelSerializer):
class Meta:
model = CourseLesson
fields = ['id', 'title', 'priority', 'is_active', 'permission', 'duration', 'content_type', 'content_file', 'video_link', 'is_complated', 'quizs']
->>>>>>> develop
def get_permission(self, obj):
if student := self._get_authenticated_user():
@@ -59,10 +41,6 @@ class CourseLessonSerializer(serializers.ModelSerializer):
def get_is_complated(self, obj):
request = self.context.get('request')
if not request or not request.user.is_authenticated:
-<<<<<<< HEAD
- return False
- user = request.user
-=======
return False
user = request.user
@@ -71,28 +49,10 @@ class CourseLessonSerializer(serializers.ModelSerializer):
return any(completion.student_id == user.id for completion in obj.completions.all())
# Fallback to direct queries
->>>>>>> develop
is_participant = Participant.objects.filter(
student=user,
course=obj.course
).exists()
-<<<<<<< HEAD
-
- if not is_participant:
- return False
-
- return LessonCompletion.objects.filter(
- student=user,
- lesson=obj
- ).exists()
-
-
- def get_quizs(self, obj):
- quizzes = obj.quizzes.all() # استفاده از related_name 'quizzes' برای دسترسی به کوییزهای درس
- if quizzes.exists():
- return QuizListSerializer(quizzes, many=True, context=self.context).data
- return None
-=======
if not is_participant:
return False
@@ -109,4 +69,3 @@ class CourseLessonSerializer(serializers.ModelSerializer):
if quizzes:
return QuizListSerializer(quizzes, many=True, context=self.context).data
return None
->>>>>>> develop
diff --git a/apps/course/views/__init__.py b/apps/course/views/__init__.py
index 23ff190..70ef373 100644
--- a/apps/course/views/__init__.py
+++ b/apps/course/views/__init__.py
@@ -1,10 +1,6 @@
from .course import *
from .lesson import *
-<<<<<<< HEAD
-from .participant import *
-=======
from .participant import *
from .professor import *
from .live_session import *
from .webhook import *
->>>>>>> develop
diff --git a/apps/course/views/course.py b/apps/course/views/course.py
index 5a29484..4961c5e 100644
--- a/apps/course/views/course.py
+++ b/apps/course/views/course.py
@@ -1,12 +1,3 @@
-<<<<<<< HEAD
-from rest_framework.generics import ListAPIView, RetrieveAPIView
-from django.db.models import Count, Q, F
-from drf_yasg.utils import swagger_auto_schema
-from drf_yasg import openapi
-from rest_framework.exceptions import NotFound
-from rest_framework.permissions import IsAuthenticated
-from rest_framework.filters import SearchFilter
-=======
from django.conf import settings
import logging
@@ -26,17 +17,10 @@ from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.response import Response
logger = logging.getLogger(__name__)
->>>>>>> develop
from apps.course.serializers import (
CourseListSerializer, CourseCategorySerializer, CourseDetailSerializer,
-<<<<<<< HEAD
- AttachmentSerializer, GlossarySerializer, MyCourseListSerializer
-)
-from apps.course.models import Course, CourseCategory, Attachment, Glossary, Participant
-from apps.course.doc import *
-=======
CourseAttachmentSerializer, CourseGlossarySerializer, MyCourseListSerializer,
OnlineClassTokenCreateSerializer, OnlineClassTokenVerifySerializer
)
@@ -57,7 +41,6 @@ from utils.redis import OnlineClassTokenManager
UserModel = get_user_model()
->>>>>>> develop
class CourseCategoryAPIView(ListAPIView):
@@ -66,10 +49,7 @@ class CourseCategoryAPIView(ListAPIView):
@swagger_auto_schema(
operation_description=doc_course_category(),
-<<<<<<< HEAD
-=======
tags=["Imam-Javad - Course"]
->>>>>>> develop
)
def get(self, request, *args, **kwargs):
return super().get(request, *args, **kwargs)
@@ -77,22 +57,6 @@ class CourseCategoryAPIView(ListAPIView):
-<<<<<<< HEAD
-class CourseListAPIView(ListAPIView):
- queryset = Course.objects.all().exclude(status=Course.StatusChoices.INACTIVE)
- serializer_class = CourseListSerializer
- filter_backends = [SearchFilter]
- search_fields = ['title']
-
- @swagger_auto_schema(
- operation_description=doc_course_list(),
- manual_parameters=[
- openapi.Parameter(
- 'category_slug', openapi.IN_QUERY,
- description="Category of the Course",
- type=openapi.TYPE_STRING,
- enum=[category.slug for category in CourseCategory.objects.all()]
-=======
from utils.pagination import StandardResultsSetPagination
class CourseListAPIView(ListAPIView):
@@ -115,16 +79,11 @@ class CourseListAPIView(ListAPIView):
description="Category of the Course",
type=openapi.TYPE_STRING,
# enum=[category.slug for category in CourseCategory.objects.all()]
->>>>>>> develop
),
openapi.Parameter(
'status', openapi.IN_QUERY,
type=openapi.TYPE_STRING,
-<<<<<<< HEAD
- description="""Status =>
-=======
description="""Status =>
->>>>>>> develop
Upcoming (visible but registration not allowed)---Предстоящие
Registering (registration is open)---регистрация
Ongoing (course has started, registration closed)---Впроцессе
@@ -144,15 +103,6 @@ class CourseListAPIView(ListAPIView):
),
])
def get(self, request, *args, **kwargs):
-<<<<<<< HEAD
- return super().get(request, *args, **kwargs)
-
- def get_queryset(self):
- queryset = super().get_queryset()
- request = self.request
- filters = request.query_params
-
-=======
return self.list(request, *args, **kwargs)
def get_queryset(self):
@@ -167,7 +117,6 @@ class CourseListAPIView(ListAPIView):
request = self.request
filters = request.query_params
->>>>>>> develop
# Handle category_slug with multiple values separated by commas
if category_slugs := filters.get('category_slug'):
category_slugs_list = category_slugs.split(',')
@@ -177,11 +126,7 @@ class CourseListAPIView(ListAPIView):
if statuses := filters.get('status'):
statuses_list = statuses.split(',')
queryset = queryset.filter(status__in=statuses_list)
-<<<<<<< HEAD
-
-=======
->>>>>>> develop
if is_free := filters.get('is_free'):
is_free = is_free.lower() == 'true'
queryset = queryset.filter(
@@ -190,11 +135,7 @@ class CourseListAPIView(ListAPIView):
if is_online := filters.get('is_online'):
is_online = is_online.lower() == 'true'
queryset = queryset.filter(is_online=is_online)
-<<<<<<< HEAD
-
-=======
->>>>>>> develop
return queryset
@@ -202,17 +143,10 @@ class CourseListAPIView(ListAPIView):
class CourseDetailAPIView(RetrieveAPIView):
-<<<<<<< HEAD
- queryset = Course.objects.all()
-=======
->>>>>>> develop
serializer_class = CourseDetailSerializer
lookup_field = "slug"
@swagger_auto_schema(
-<<<<<<< HEAD
- operation_description=doc_course_detail(),
-=======
tags=["Imam-Javad - Course"],
operation_description="Get detailed information about a specific course",
responses={
@@ -244,7 +178,6 @@ class CourseDetailAPIView(RetrieveAPIView):
@swagger_auto_schema(
operation_description=doc_course_detail(),
tags=['Imam-Javad - Course'],
->>>>>>> develop
)
def get(self, request, *args, **kwargs):
return super().get(request, *args, **kwargs)
@@ -267,10 +200,7 @@ class MyCourseListAPIView(ListAPIView):
],
operation_description=doc_courses_my_courses(),
operation_summary="Home",
-<<<<<<< HEAD
-=======
tags=['Imam-Javad - Course']
->>>>>>> develop
)
def get(self, request, *args, **kwargs):
@@ -278,9 +208,6 @@ class MyCourseListAPIView(ListAPIView):
return super().get(request, *args, **kwargs)
def get_queryset(self):
-<<<<<<< HEAD
- queryset = Course.objects.exclude(status=Course.StatusChoices.INACTIVE)
-=======
"""
Optimized queryset for user's courses with select_related and prefetch_related
"""
@@ -293,7 +220,6 @@ class MyCourseListAPIView(ListAPIView):
'participants__student'
).exclude(status=Course.StatusChoices.INACTIVE)
->>>>>>> develop
request = self.request
filters = request.query_params
student = self.request.user
@@ -334,16 +260,10 @@ class MyCourseListAPIView(ListAPIView):
class AttachmentListAPIView(ListAPIView):
-<<<<<<< HEAD
- serializer_class = AttachmentSerializer
-
- @swagger_auto_schema(
-=======
serializer_class = CourseAttachmentSerializer
@swagger_auto_schema(
tags=['Imam-Javad - Course'],
->>>>>>> develop
manual_parameters=[
openapi.Parameter(
'slug', openapi.IN_PATH,
@@ -358,37 +278,23 @@ class AttachmentListAPIView(ListAPIView):
return super().get(request, *args, **kwargs)
def get_queryset(self):
-<<<<<<< HEAD
-=======
"""
Optimized queryset with select_related for attachment relationship
"""
->>>>>>> develop
course_slug = self.kwargs.get('slug')
try:
course = Course.objects.get(slug=course_slug)
except Course.DoesNotExist:
raise NotFound("Course not found")
-<<<<<<< HEAD
- return Attachment.objects.filter(course=course)
-=======
return CourseAttachment.objects.select_related(
'course',
'attachment'
).filter(course=course)
->>>>>>> develop
class GlossaryListAPIView(ListAPIView):
-<<<<<<< HEAD
- serializer_class = GlossarySerializer
- filter_backends = [SearchFilter]
- search_fields = ['title', 'description']
-
- def get_queryset(self):
-=======
serializer_class = CourseGlossarySerializer
filter_backends = [SearchFilter]
search_fields = ['glossary__title', 'glossary__description']
@@ -424,19 +330,12 @@ class GlossaryListAPIView(ListAPIView):
"""
Optimized queryset with select_related for glossary relationship
"""
->>>>>>> develop
course_slug = self.kwargs.get('slug')
try:
course = Course.objects.get(slug=course_slug)
except Course.DoesNotExist:
raise NotFound("Course not found")
-<<<<<<< HEAD
- return Glossary.objects.filter(course=course)
-
-
-
-=======
return CourseGlossary.objects.select_related(
'course',
'glossary'
@@ -772,5 +671,4 @@ class CourseOnlineClassTokenValidateAPIView(GenericAPIView):
)
if updated_count > 0:
- logger.info(f"[Room Sync] User sessions closed - session_id={session.id} count={updated_count}")
->>>>>>> develop
+ logger.info(f"[Room Sync] User sessions closed - session_id={session.id} count={updated_count}")
\ No newline at end of file
diff --git a/apps/course/views/lesson.py b/apps/course/views/lesson.py
index 39ea533..7261040 100644
--- a/apps/course/views/lesson.py
+++ b/apps/course/views/lesson.py
@@ -7,15 +7,9 @@ from rest_framework import status
from rest_framework.response import Response
from apps.course.serializers import (
-<<<<<<< HEAD
- LessonSerializer
-)
-from apps.course.models import Course, Lesson, LessonCompletion
-=======
CourseLessonSerializer
)
from apps.course.models import Course, CourseLesson, LessonCompletion
->>>>>>> develop
from apps.course.doc import *
from utils.exceptions import AppAPIException
from rest_framework.permissions import IsAuthenticated
@@ -23,33 +17,16 @@ from rest_framework.permissions import IsAuthenticated
class LessonListView(ListAPIView):
-<<<<<<< HEAD
- serializer_class = LessonSerializer
- queryset = Lesson.objects.filter(is_active=True)
-
- @swagger_auto_schema(
- operation_description=doc_courses_lesson(),
-=======
serializer_class = CourseLessonSerializer
@swagger_auto_schema(
operation_description=doc_courses_lesson(),
tags=['Imam-Javad - Course'],
->>>>>>> develop
)
def get(self, request, *args, **kwargs):
return super().get(request, *args, **kwargs)
def get_queryset(self):
-<<<<<<< HEAD
- course_slug = self.kwargs.get('slug')
- course = get_object_or_404(Course, slug=course_slug)
- course = Course.objects.filter(slug=course_slug).first()
- if not course:
- raise AppAPIException({"message": "course not found"}, status_code=status.HTTP_404_NOT_FOUND)
-
- return self.queryset.filter(course=course).order_by('priority','id')
-=======
"""
Optimized queryset with select_related and prefetch_related for lesson relationships
"""
@@ -66,31 +43,11 @@ class LessonListView(ListAPIView):
course=course,
is_active=True
).order_by('priority', 'id')
->>>>>>> develop
class LessonDetailView(RetrieveAPIView):
-<<<<<<< HEAD
- serializer_class = LessonSerializer
-
- def get(self, request, *args, **kwargs):
- lesson_id = self.kwargs.get('id')
- lesson = get_object_or_404(Lesson, id=lesson_id, is_active=True)
-
- course = lesson.course
- lessons = Lesson.objects.filter(course=course, is_active=True).order_by('priority')
-
- total_lessons = lessons.count()
- current_lesson_number = list(lessons.values_list('id', flat=True)).index(lesson.id) + 1
- next_lesson = lessons.filter(priority__gt=lesson.priority).order_by('priority').first()
- next_lesson_id = next_lesson.id if next_lesson else None
- previous_lesson = lessons.filter(priority__lt=lesson.priority).order_by('-priority').first()
- previous_lesson_id = previous_lesson.id if previous_lesson else None
-
- lesson_data = self.get_serializer(lesson).data
-=======
serializer_class = CourseLessonSerializer
@swagger_auto_schema(
@@ -138,120 +95,42 @@ class LessonDetailView(RetrieveAPIView):
previous_lesson_id = previous_lesson.id if previous_lesson else None
lesson_data = self.get_serializer(course_lesson).data
->>>>>>> develop
lesson_data['total_lessons'] = total_lessons
lesson_data['current_lesson_number'] = current_lesson_number
lesson_data['next_lesson_id'] = next_lesson_id
lesson_data['previous_lesson_id'] = previous_lesson_id
lesson_data['can_go_next'] = next_lesson is not None
-<<<<<<< HEAD
-
-
-
- # # Get the next and previous lessons based on priority and id
- # next_lesson = Lesson.objects.filter(
- # course=lesson.course,
- # is_active=True,
- # priority__gte=lesson.priority,
- # id__gt=lesson.id
- # ).order_by('priority', 'id').first()
-
- # previous_lesson = Lesson.objects.filter(
- # course=lesson.course,
- # is_active=True,
- # priority__lte=lesson.priority,
- # id__lt=lesson.id
- # ).order_by('-priority', '-id').first()
-
- # total_lessons = Lesson.objects.filter(course=lesson.course, is_active=True).count()
- # # Calculate the current lesson number in the course
- # current_lesson_number = Lesson.objects.filter(
- # course=lesson.course,
- # is_active=True,
- # priority__lte=lesson.priority
- # ).count()
-
- # # Serialize the current lesson
- # lesson_data = self.get_serializer(lesson).data
- # # Add current lesson number and total lessons
- # lesson_data['current_lesson_number'] = current_lesson_number
- # lesson_data['total_lessons'] = total_lessons
-
- # # Add next and previous lesson ids
- # lesson_data['next_lesson_id'] = next_lesson.id if next_lesson else None
- # lesson_data['previous_lesson_id'] = previous_lesson.id if previous_lesson else None
-
-
-=======
->>>>>>> develop
return Response(lesson_data)
-<<<<<<< HEAD
-class LessonCompletionCreateAPIView(GenericAPIView):
- permission_classes = [IsAuthenticated]
-
-
- @swagger_auto_schema(
-=======
class LessonCompletionToggleAPIView(GenericAPIView):
permission_classes = [IsAuthenticated]
@swagger_auto_schema(
operation_description="Toggle lesson completion status (Check/Uncheck)",
tags=["Imam-Javad - Course"],
->>>>>>> develop
request_body=openapi.Schema(
type=openapi.TYPE_OBJECT,
required=['lesson_id'],
properties={
-<<<<<<< HEAD
- 'lesson_id': openapi.Schema(type=openapi.TYPE_INTEGER, description='ID of the lesson to be marked as completed'),
- },
- ),
- responses={
- 201: 'Lesson completed successfully.',
- 200: 'Lesson already completed.',
-=======
'lesson_id': openapi.Schema(type=openapi.TYPE_INTEGER, description='ID of the lesson to toggle'),
},
),
responses={
201: 'Lesson marked as COMPLETED.',
200: 'Lesson marked as INCOMPLETE (Unchecked).',
->>>>>>> develop
400: 'Lesson ID is required.',
404: 'Lesson not found.',
}
)
def post(self, request):
-<<<<<<< HEAD
- student = request.user # Assuming the user is the student
-=======
student = request.user
->>>>>>> develop
lesson_id = request.data.get('lesson_id')
if not lesson_id:
return Response({'error': 'Lesson ID is required.'}, status=status.HTTP_400_BAD_REQUEST)
-<<<<<<< HEAD
- try:
- lesson = Lesson.objects.get(id=lesson_id)
- except Lesson.DoesNotExist:
- return Response({'error': 'Lesson not found.'}, status=status.HTTP_404_NOT_FOUND)
-
- # Check if the lesson is already completed by the student
- if LessonCompletion.objects.filter(student=student, lesson=lesson).exists():
- return Response({'message': 'Lesson already completed.'}, status=status.HTTP_200_OK)
-
- # Create a new completion record
- completion = LessonCompletion(student=student, lesson=lesson)
- completion.save()
-
- return Response({'message': 'Lesson completed successfully.'}, status=status.HTTP_201_CREATED)
-=======
try:
course_lesson = CourseLesson.objects.get(id=lesson_id)
@@ -278,4 +157,3 @@ class LessonCompletionToggleAPIView(GenericAPIView):
{'message': 'Lesson completed successfully.', 'is_completed': True},
status=status.HTTP_201_CREATED
)
->>>>>>> develop
diff --git a/apps/course/views/participant.py b/apps/course/views/participant.py
index 3188d82..b591f76 100644
--- a/apps/course/views/participant.py
+++ b/apps/course/views/participant.py
@@ -19,10 +19,6 @@ class CourseParticipantsView(generics.ListAPIView):
@swagger_auto_schema(
operation_description=doc_course_participants(),
-<<<<<<< HEAD
- )
- def get_queryset(self):
-=======
tags=['Imam-Javad - Course'],
)
def get(self, request, *args, **kwargs):
@@ -31,20 +27,15 @@ class CourseParticipantsView(generics.ListAPIView):
"""
Optimized queryset with select_related for course relationship
"""
->>>>>>> develop
course_slug = self.kwargs.get('slug')
try:
course = Course.objects.get(slug=course_slug)
except Course.DoesNotExist:
raise AppAPIException({'message': "Course not found"}) # Handle course not found
-<<<<<<< HEAD
- return StudentUser.objects.filter(participated_courses__course=course)
-=======
return StudentUser.objects.select_related().filter(
participated_courses__course=course
)
->>>>>>> develop
diff --git a/apps/hadis/admin/__init__.py b/apps/hadis/admin/__init__.py
index 7074a60..143131e 100644
--- a/apps/hadis/admin/__init__.py
+++ b/apps/hadis/admin/__init__.py
@@ -1,9 +1,5 @@
from .category import *
from .hadis import *
-<<<<<<< HEAD
-from .transmitter import *
-=======
from .transmitter import *
from .reference import *
from .version import *
->>>>>>> develop
diff --git a/apps/hadis/admin/category.py b/apps/hadis/admin/category.py
index 656bc8d..461d300 100644
--- a/apps/hadis/admin/category.py
+++ b/apps/hadis/admin/category.py
@@ -1,222 +1,4 @@
from django.contrib import admin
-<<<<<<< HEAD
-from django.utils.translation import gettext_lazy as _
-from dj_category.admin import BaseCategoryAdmin
-from ajaxdatatable.admin import AjaxDatatable
-from django.http import JsonResponse
-from django.urls import path
-
-from apps.hadis.models import *
-from django import forms
-
-from django.db.models import Case, When, Value
-
-
-
-@admin.register(HadisCategory)
-class HadisCategoryAdmin(BaseCategoryAdmin):
- change_form_template = 'admin/hadiscategory/change_form.html'
- change_list_template = 'admin/category_index.html'
- fields = (
- 'name', 'source_type', 'category_type' , 'parent', 'is_active', 'order'
- )
- search_fields = ['name']
-
- def get_form(self, request, obj=None, **kwargs):
- form = super().get_form(request, obj, **kwargs)
- return form
-
- def get_urls(self):
- urls = super().get_urls()
- custom_urls = [
- path('categories-ajax/hadiscategory/', self.admin_site.admin_view(self.ajax_categories), name='hadiscategory_ajax_categories'),
- ]
- return custom_urls + urls
- def get_categories_groupby_language(self, request=None, selected_values=(), is_multiple=False):
- print(f'--get_categories_groupby_language-> {selected_values}')
- return super().get_categories(request, selected_values, is_multiple)
-
- def ajax_update(self, request):
- data = request.POST
- src_node = self.model.objects.get(pk=int(data['srcNode']))
- other_node = self.model.objects.get(pk=int(data['otherNode']))
- print(f'--ajax_update-> {data}')
- if src_node.slug in self.base_categories or other_node.slug in self.base_categories:
- return JsonResponse({'data': _('This item can not be modifed')}, status=401)
-
- mode = data['hitMode']
- if mode == 'over':
- src_node.move_to(other_node, 'first-child')
- elif mode == 'after':
- src_node.move_to(other_node, 'right')
- elif mode == 'before':
- src_node.move_to(other_node, 'left')
-
- return JsonResponse({'data': 'ok'}, safe=False)
-
- def get_categories(self, request=None, selected_values=(), is_multiple=False):
- """
- Override the get_categories method to filter by source_type if provided in the request
- """
- categories = super().get_categories(request, selected_values, is_multiple)
-
- # If request has source_type parameter, filter the categories
- if request and request.GET.get('source_type'):
- source_type = request.GET.get('source_type')
- # Filter the categories by source_type
- filtered_categories = []
- for category in categories:
- # If it's a dictionary (serialized category)
- if isinstance(category, dict) and category.get('source_type') == source_type:
- filtered_categories.append(category)
- # If it's a model instance
- elif hasattr(category, 'source_type') and getattr(category, 'source_type') == source_type:
- filtered_categories.append(category)
- return filtered_categories
-
- return categories
-
- def ajax_categories(self, request):
- """
- Handle AJAX request for categories with source_type filtering and search
- """
- # Get source_type from request
- source_type = request.GET.get('source_type')
-
- # Get node_id if provided (for single node data)
- node_id = request.GET.get('node_id')
-
- # Get search term if provided
- search = request.GET.get('search')
-
- # Get parent level filter if provided
- parent_level = request.GET.get('parent_level')
-
- if node_id:
- # Return data for a specific node
- try:
- node = self.model.objects.get(pk=int(node_id))
- return JsonResponse({
- 'id': node.id,
- 'source_type': node.source_type,
- 'category_type': node.category_type,
- 'parent': node.parent_id,
- 'level': node.level_p # Add the level_p property
- })
- except self.model.DoesNotExist:
- return JsonResponse({'error': 'Node not found'}, status=404)
-
- # Get all categories
- queryset = self.model.objects.all()
-
- # Annotate queryset with level_p
- queryset = queryset.annotate(
- level_pp=Case(
- When(parent=None, then=Value(1)),
- When(parent__isnull=False, parent__parent=None, then=Value(2)),
- default=Value(3),
- output_field=models.IntegerField()
- )
- )
-
- # Filter by source_type if provided
- if source_type:
- queryset = queryset.filter(source_type=source_type)
-
- # Filter by search term if provided
- if search:
- queryset = queryset.filter(name__icontains=search)
-
- # Filter by parent_level if provided
- if parent_level and parent_level.isdigit():
- # Convert to integer
- level = int(parent_level)
- # Filter categories by level_p
- queryset = queryset.filter(level_pp=level)
-
- # Convert queryset to list of dictionaries for JSON response
- categories = []
- for category in queryset:
- categories.append({
- 'key': category.id,
- 'title': category.name,
- 'parent': category.parent_id,
- 'source_type': category.source_type,
- 'category_type': category.category_type,
- 'level': category.level_p,
- # Add data property to store additional information
- 'data': {
- 'parent': category.parent_id,
- 'level': category.level_p
- }
- })
- print(f'-categories-->{categories}')
- return JsonResponse(categories, safe=False)
-
- def save_model(self, request, obj, form, change):
- print(f'SAVE_MODEL CALLED: {request}/ {obj} / {form} / {change}')
- print(f'POST DATA: {request.POST}')
-
- # Get the level choice from the form data
- level_choice = request.POST.get('level_choice_hidden')
- print(f'LEVEL CHOICE: {level_choice}')
-
- # Get the parent from AJAX selection if provided
- ajax_parent = request.POST.get('ajax_parent')
- if ajax_parent and ajax_parent.isdigit():
- # Set the parent for the object
- try:
- parent_category = self.model.objects.get(pk=int(ajax_parent))
- obj.parent = parent_category
-
- # If parent is level 1, inherit its source_type
- # if parent_category.level_p == 1 and level_choice == '2':
- # obj.source_type = parent_category.source_type
-
- print(f'AJAX PARENT SET: {parent_category.id} - {parent_category.name}')
- except self.model.DoesNotExist:
- print(f'PARENT CATEGORY NOT FOUND: {ajax_parent}')
-
- # Debug form validation
- if form.is_valid():
- print("FORM IS VALID")
- else:
- print(f"FORM ERRORS: {form.errors}")
- print(f'---> {obj}')
-
- # Let the parent class handle the save
- super().save_model(request, obj, form, change)
-
- # Add a message to trigger tree reload via JavaScript
- from django.contrib import messages
- messages.success(request, "Category saved successfully. Tree will be reloaded.")
-
- # Set a flag in the request to redirect back to the category index page
- request._category_saved = True
-
- def response_add(self, request, obj, post_url_continue=None):
- """
- Override to redirect back to the category index page after adding a new category
- """
- if hasattr(request, '_category_saved') and request._category_saved:
- from django.http import HttpResponseRedirect
- from django.urls import reverse
- return HttpResponseRedirect(reverse('admin:hadis_hadiscategory_changelist'))
- return super().response_add(request, obj, post_url_continue)
-
- def response_change(self, request, obj):
- """
- Override to redirect back to the category index page after editing a category
- """
- if hasattr(request, '_category_saved') and request._category_saved:
- from django.http import HttpResponseRedirect
- from django.urls import reverse
- return HttpResponseRedirect(reverse('admin:hadis_hadiscategory_changelist'))
- return super().response_change(request, obj)
-
-
-
-=======
from django import forms
from django.utils.translation import gettext_lazy as _
from django.utils.html import format_html
@@ -437,4 +219,3 @@ class HadisCategoryAdmin(DraggableMPTTAdmin, ModelAdmin):
# Register models with the custom admin site
dovoodi_admin_site.register(HadisSect, HadisSectAdmin)
dovoodi_admin_site.register(HadisCategory, HadisCategoryAdmin)
->>>>>>> develop
diff --git a/apps/hadis/admin/hadis.py b/apps/hadis/admin/hadis.py
index 977e19d..4acc62f 100644
--- a/apps/hadis/admin/hadis.py
+++ b/apps/hadis/admin/hadis.py
@@ -1,119 +1,3 @@
-<<<<<<< HEAD
-from django.contrib import admin
-from django.utils.translation import gettext_lazy as _
-from dj_category.admin import BaseCategoryAdmin
-from ajaxdatatable.admin import AjaxDatatable
-from django.http import JsonResponse
-from django.urls import path
-from django.db.models import Q
-from django.utils.safestring import mark_safe
-from django.forms.widgets import RadioSelect
-
-from apps.hadis.models import *
-from django import forms
-from utils.json_editor_field import JsonEditorWidget
-
-# Define color choices
-COLOR_CHOICES = [
- ('red', _('Red')),
- ('blue', _('Blue')),
- ('green', _('Green')),
- ('yellow', _('Yellow')),
- ('orange', _('Orange')),
- ('purple', _('Purple')),
- ('pink', _('Pink')),
- ('brown', _('Brown')),
- ('gray', _('Gray')),
- ('black', _('Black')),
-]
-
-class ColorRadioSelect(RadioSelect):
- template_name = 'admin/widgets/color_radio.html'
- option_template_name = 'admin/widgets/color_radio_option.html'
-
-
-def get_links_schema():
- return {
- 'type': "array",
- 'format': 'table',
- 'title': ' ',
- 'items': {
- 'type': 'object',
- 'title': str(_('Link')),
- 'properties': {
- 'text': {'type': 'string', "format": "textarea",'title': str(_('text'))},
- 'link': {'type': 'string', "format": "textarea", 'title': str(_('link'))},
- }
- }
- }
-
-class HadisOverviewForm(forms.ModelForm):
- status_color = forms.ChoiceField(
- choices=COLOR_CHOICES,
- widget=ColorRadioSelect(),
- required=False
- )
-
- class Meta:
- model = HadisOverview
- fields = '__all__'
- widgets = {
- 'links': JsonEditorWidget(attrs={'schema': get_links_schema}),
- }
-
-
-
-
-
-@admin.register(HadisTag)
-class HadisTagAdmin(AjaxDatatable):
- list_display = ['title', 'status']
- search_fields = ['title']
-
-
-class ReferenceImageInline(admin.TabularInline):
- model = ReferenceImage
- extra = 1
- verbose_name_plural = _('Reference Images')
- fields = ('thumbnail', 'priority')
-
-
-@admin.register(HadisReference)
-class HadisReferenceAdmin(AjaxDatatable):
- list_display = ['hadis', 'book', 'created_at']
- list_filter = ['book']
- search_fields = ['hadis__title', 'hadis__number', 'description']
- autocomplete_fields = ['hadis', 'book']
- readonly_fields = ['created_at']
- inlines = [ReferenceImageInline]
- fieldsets = (
- (None, {
- 'fields': ('hadis', 'book', 'description')
- }),
- )
-
-
-
-
-@admin.register(HadisOverview)
-class HadisOverviewAdmin(AjaxDatatable):
- change_form_template = 'admin/hadisowerview_change_form.html'
- form = HadisOverviewForm
- ordering = ['hadis__number']
- list_display = ['hadis', 'status', 'created_at']
- search_fields = ['hadis__title', 'hadis__number', 'status_text',]
- autocomplete_fields = ['hadis', 'tags']
- fieldsets = (
- (None, {
- 'fields': ('hadis', 'status', 'status_color', 'status_text')
- }),
- (_('Reference Information'), {
- 'fields': ('address', 'share_link',),
- }),
- (_('Additional Information'), {
- 'fields': ('links', 'tags', 'created_at'),
- 'classes': ('collapse',),
-=======
from django import forms
from django.contrib import admin
from django.utils.translation import gettext_lazy as _
@@ -235,45 +119,10 @@ class HadisTagAdmin(ModelAdmin):
(_('Timestamps'), {
'fields': ('created_at', 'updated_at'),
'classes': ('collapse',)
->>>>>>> develop
}),
)
-<<<<<<< HEAD
-class HadisOverviewInline(admin.StackedInline):
- change_form_template = 'admin/hadisowerview_change_form.html'
- form = HadisOverviewForm
- model = HadisOverview
- autocomplete_fields = ['tags', ]
- can_delete = False
- verbose_name_plural = _('Hadis Overview')
- fieldsets = (
- (None, {
- 'fields': ('status', 'status_color', 'status_text', 'address', 'share_link', 'links', 'tags',),
- }),
- )
- extra = 1
- min_num = 1
-
-
-@admin.register(Hadis)
-class HadisAdmin(AjaxDatatable):
- # form = HadisForm
- list_display = ['number', 'title', 'category', 'status', 'created_at']
- list_filter = ['status', 'category']
- search_fields = ['title', 'text', 'number']
- readonly_fields = ['created_at', 'updated_at']
- autocomplete_fields = ['category']
- inlines = [HadisOverviewInline]
- fieldsets = (
- (None, {
- 'fields': ('number', 'title', 'category', 'status')
- }),
- (_('Content'), {
- 'fields': ('text', 'translation'),
- 'classes': ('collapse',),
-=======
class HadisStatusAdmin(ModelAdmin):
"""Admin for HadisStatus model"""
list_display = ('title', 'color', 'order')
@@ -316,20 +165,10 @@ class HadisAdmin(ModelAdmin):
(_('Timestamps'), {
'fields': ('created_at', 'updated_at'),
'classes': ('collapse',)
->>>>>>> develop
}),
)
-<<<<<<< HEAD
- def get_form(self, request, obj=None, **kwargs):
- form = super().get_form(request, obj, **kwargs)
- if obj is None:
- form.base_fields['category'].widget.can_add_related = False
-
- return form
-
-=======
class HadisReferenceAdmin(ModelAdmin):
"""Admin for HadisReference model"""
list_display = ('hadis', 'book_reference', 'created_at')
@@ -548,4 +387,3 @@ dovoodi_admin_site.register(ReferenceImage, ReferenceImageAdmin)
dovoodi_admin_site.register(HadisCollection, HadisCollectionAdmin)
dovoodi_admin_site.register(HadisInCollection, HadisInCollectionAdmin)
dovoodi_admin_site.register(HadisCorrection, HadisCorrectionAdmin)
->>>>>>> develop
diff --git a/apps/hadis/management/commands/README.md b/apps/hadis/management/commands/README.md
index d8f4a22..8e83cbb 100644
--- a/apps/hadis/management/commands/README.md
+++ b/apps/hadis/management/commands/README.md
@@ -2,127 +2,3 @@
## seed_hadis_data
-<<<<<<< HEAD
-This management command seeds comprehensive data for all Hadis app models with realistic sample records while maintaining proper relationships and business domain logic.
-=======
-This management command seeds comprehensive data for all Hadis app models with realistic sample records while maintaining proper relationships and business domain logic. **Enhanced with lock detection and retry logic to prevent database locks.**
->>>>>>> 932fb17 (Refactor API Documentation System and optimize Hadis data scripts)
-
-### Usage
-
-```bash
-# Basic usage - seed data with default settings
-python manage.py seed_hadis_data
-
-# Clear existing data before seeding
-python manage.py seed_hadis_data --clear
-
-# Specify custom images directory
-python manage.py seed_hadis_data --images-dir /path/to/images
-
-# Specify custom XMind file
-python manage.py seed_hadis_data --xmind-file /path/to/file.xmind
-
-# Combine options
-python manage.py seed_hadis_data --clear --images-dir scripts/seed_images --xmind-file scripts/test.xmind
-```
-
-### Options
-
-- `--clear`: Clear existing hadis data before seeding (optional)
-- `--images-dir`: Directory containing seed images (default: scripts/seed_images)
-- `--xmind-file`: Path to XMind file for categories (default: scripts/test.xmind)
-
-### What it creates
-
-1. **HadisStatus records**: Various hadis authenticity statuses (Достоверный, Хороший, etc.)
-2. **HadisTag records**: Topic tags for categorizing hadis
-3. **HadisSect records**: Shia and Sunni sects
-4. **HadisCategory records**: Hierarchical categories for both Quran and Hadith sources
-5. **Library data**: Books, categories, and collections for references
-6. **Transmitters**: Historical figures who transmitted hadis
-7. **Hadis records**: Complete hadis with translations, explanations, and relationships
-8. **Transmission chains**: Links between hadis and transmitters
-9. **References**: Book references with images
-
-### Requirements
-
-- The images directory must contain PNG files for book covers and reference images
-- The XMind file is optional but recommended for category mind maps
-- All models must be properly migrated before running
-
-<<<<<<< HEAD
-### Performance
-
-The command uses optimized batch operations to create data efficiently:
-- Bulk create/update operations for categories
-- Checks for existing records to avoid duplicates
-- Progress reporting for large datasets
-=======
-### Performance & Lock Prevention
-
-The command uses advanced techniques to prevent database locks and ensure reliable execution:
-- **Lock Detection**: Automatically detects database locks and deadlocks
-- **Retry Logic**: Retries failed operations with exponential backoff (up to 5 attempts)
-- **Step-by-step Processing**: Creates records individually with small delays to prevent locks
-- **Batch Processing**: Processes tags in small batches to avoid overwhelming the database
-- **No Large Transactions**: Avoids wrapping everything in atomic transactions that can cause locks
-- **Progress Reporting**: Detailed progress with emoji indicators and clear status messages
-- **Error Handling**: Graceful handling of duplicate records and constraint violations
->>>>>>> 932fb17 (Refactor API Documentation System and optimize Hadis data scripts)
-
-### Example Output
-
-```
-Starting Hadis data seeding...
-Found 4 seed images
-XMind file: scripts/test.xmind
-Creating Hadis Statuses...
- Created status: Достоверный
- Created status: Хороший
-...
-Creating Hadis Categories...
- Creating categories for Шииты-двунадесятники...
- Batch created 6 Quran categories
-...
-Successfully seeded all Hadis data!
-```
-<<<<<<< HEAD
-=======
-
-## test_safe_seeding
-
-A simple test command to verify that the lock detection and retry logic is working properly.
-
-### Usage
-
-```bash
-# Test the safe seeding functionality
-python manage.py test_safe_seeding
-```
-
-### What it tests
-
-- Database connectivity
-- Lock detection mechanisms
-- Retry logic for failed operations
-- Creation of test records (sect, status, tag)
-
-## Additional Commands
-
-### fix_sects
-
-Fixes any issues with sect creation by using simple English titles.
-
-```bash
-python manage.py fix_sects
-```
-
-### seed_basic_data
-
-Creates only the essential basic data (statuses, tags, sects) without the full dataset.
-
-```bash
-python manage.py seed_basic_data [--clear]
-```
->>>>>>> 932fb17 (Refactor API Documentation System and optimize Hadis data scripts)
diff --git a/apps/hadis/migrations/0001_initial.py b/apps/hadis/migrations/0001_initial.py
index 8c95bbe..6f16586 100644
--- a/apps/hadis/migrations/0001_initial.py
+++ b/apps/hadis/migrations/0001_initial.py
@@ -1,10 +1,3 @@
-<<<<<<< HEAD
-# Generated by Django 3.2.7 on 2025-03-16 23:50
-
-from django.db import migrations, models
-import django.db.models.deletion
-import mptt.fields
-=======
# Generated by Django 5.1.8 on 2025-04-03 00:05
import django.db.models.deletion
@@ -12,7 +5,6 @@ import filer.fields.image
import mptt.fields
from django.conf import settings
from django.db import migrations, models
->>>>>>> develop
class Migration(migrations.Migration):
@@ -20,67 +12,16 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
-<<<<<<< HEAD
-=======
('library', '0001_initial'),
migrations.swappable_dependency(settings.FILER_IMAGE_MODEL),
->>>>>>> develop
]
operations = [
migrations.CreateModel(
-<<<<<<< HEAD
- name='Hadis',
- fields=[
- ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('number', models.PositiveIntegerField(unique=True, verbose_name='number')),
- ('title', models.CharField(max_length=355, verbose_name='title')),
- ('text', models.TextField(verbose_name='text')),
- ('translation', models.TextField(blank=True, default='', verbose_name='translation')),
- ('status', models.BooleanField(default=True, verbose_name='visibility')),
- ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='created at')),
- ('updated_at', models.DateTimeField(auto_now=True, verbose_name='updated at')),
- ],
- options={
- 'verbose_name': 'hadis',
- 'verbose_name_plural': 'hadises',
- },
- ),
- migrations.CreateModel(
-=======
->>>>>>> develop
name='HadisTag',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=355, verbose_name='title')),
-<<<<<<< HEAD
- ],
- ),
- migrations.CreateModel(
- name='HadisTagRelation',
- fields=[
- ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('priority', models.IntegerField(default=0, verbose_name='priority')),
- ('hadis', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='hadis.hadis', verbose_name='hadis')),
- ('tag', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='hadis.hadistag', verbose_name='tag')),
- ],
- options={
- 'verbose_name': 'hadis tag relation',
- 'verbose_name_plural': 'hadis tag relations',
- 'unique_together': {('tag', 'hadis')},
- },
- ),
- migrations.CreateModel(
- name='HadisCategory',
- fields=[
- ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('name', models.CharField(max_length=512, verbose_name='name')),
- ('is_active', models.BooleanField(default=True, verbose_name='is active')),
- ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='created at')),
- ('source_type', models.CharField(blank=True, choices=[('shia', 'Shia Sources'), ('sunni', 'Sunni Sources')], default='shia', max_length=10, verbose_name='Source Type')),
- ('category_type', models.CharField(blank=True, choices=[('quran', 'Quran'), ('hadith', 'Hadith')], max_length=10, null=True, verbose_name='Category Content Type')),
- ('title', models.CharField(max_length=355, verbose_name='title')),
-=======
('status', models.BooleanField(default=True, verbose_name='status')),
],
),
@@ -93,7 +34,6 @@ class Migration(migrations.Migration):
('source_type', models.CharField(blank=True, choices=[('shia', 'Shia'), ('sunni', 'Sunni')], default='shia', max_length=10, verbose_name='Source Type')),
('category_type', models.CharField(blank=True, choices=[('quran', 'Quran'), ('hadith', 'Hadith')], max_length=10, null=True, verbose_name='Category Content Type')),
('name', models.CharField(max_length=355, verbose_name='name')),
->>>>>>> develop
('order', models.IntegerField(default=0, verbose_name='order')),
('lft', models.PositiveIntegerField(editable=False)),
('rght', models.PositiveIntegerField(editable=False)),
@@ -107,17 +47,6 @@ class Migration(migrations.Migration):
'ordering': ('order',),
},
),
-<<<<<<< HEAD
- migrations.AddField(
- model_name='hadis',
- name='category',
- field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='hadis.hadiscategory', verbose_name='category'),
- ),
- migrations.AddField(
- model_name='hadis',
- name='tags',
- field=models.ManyToManyField(related_name='hadises', through='hadis.HadisTagRelation', to='hadis.HadisTag', verbose_name='tags'),
-=======
migrations.CreateModel(
name='Hadis',
fields=[
@@ -208,6 +137,5 @@ class Migration(migrations.Migration):
'ordering': ('hadis', 'order'),
'unique_together': {('hadis', 'transmitter', 'order')},
},
->>>>>>> develop
),
- ]
+ ]
\ No newline at end of file
diff --git a/apps/hadis/models/__init__.py b/apps/hadis/models/__init__.py
index 7074a60..143131e 100644
--- a/apps/hadis/models/__init__.py
+++ b/apps/hadis/models/__init__.py
@@ -1,9 +1,5 @@
from .category import *
from .hadis import *
-<<<<<<< HEAD
-from .transmitter import *
-=======
from .transmitter import *
from .reference import *
from .version import *
->>>>>>> develop
diff --git a/apps/hadis/models/category.py b/apps/hadis/models/category.py
index 26b05da..3bbce65 100644
--- a/apps/hadis/models/category.py
+++ b/apps/hadis/models/category.py
@@ -1,30 +1,6 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
from django.core.exceptions import ValidationError
-<<<<<<< HEAD
-from dj_category.models import BaseCategoryAbstract
-
-
-class HadisCategory(BaseCategoryAbstract):
- class SourceType(models.TextChoices):
- SHIA = 'shia', _('Shia')
- SUNNI = 'sunni', _('Sunni')
-
- class ContentType(models.TextChoices):
- QURAN = 'quran', _('Quran')
- HADITH = 'hadith', _('Hadith')
-
- class LevelChoices(models.IntegerChoices):
- LEVEL_1 = 1, _('Level 1 (Root)')
- LEVEL_2 = 2, _('Level 2 (Child)')
- LEVEL_3 = 3, _('Level 3 (Grandchild)')
-
- source_type = models.CharField(max_length=10, choices=SourceType.choices, default=SourceType.SHIA, verbose_name=_('Source Type'), blank=True)
- category_type = models.CharField(max_length=10, choices=ContentType.choices, verbose_name=_('Category Content Type'), blank=True, null=True)
- name = models.CharField(max_length=355, verbose_name=_('name'))
- order = models.IntegerField(default=0, verbose_name=_('order'))
- slug = None
-=======
from mptt.models import MPTTModel, TreeForeignKey
from django.utils.text import slugify
@@ -101,17 +77,10 @@ class HadisCategory(MPTTModel):
order = models.IntegerField(default=0, verbose_name=_('order'))
xmind_file = models.FileField(upload_to='hadis/xmind_files/', verbose_name=_('xmind file'), null=True, blank=True)
slug = models.SlugField(max_length=255, null=True, blank=True)
->>>>>>> develop
content_type = None
language = None
language_id = None
-<<<<<<< HEAD
- # This field is not stored in the database, it's only used for the form
- level_choice = None
-
- class Meta:
-=======
def clean(self):
super().clean()
if self.parent and self.sect_id != self.parent.sect_id:
@@ -139,81 +108,11 @@ class HadisCategory(MPTTModel):
models.Index(fields=['parent', 'sect']),
models.Index(fields=['sect', 'order'])
]
->>>>>>> develop
verbose_name = _('Hadis Category')
verbose_name_plural = _('Hadis Categories')
ordering = ('order',)
def __str__(self):
-<<<<<<< HEAD
- return f'<{str(self.level_p)}>{self.name}'
-
- def __repr__(self):
- return f'<{str(self.level_p)}>{self.name}'
-
- def clean(self):
- super().clean()
-
- # Skip validation for new objects that haven't been saved yet
- # This allows the admin form to set these values properly
- if self.pk is None:
- return
-
- # For existing objects, apply the validation rules
- if self.level_p == 1 and self.category_type:
- raise ValidationError(_("Level 1 cannot have content type"))
-
- if self.level_p == 2 and not self.category_type:
- raise ValidationError(_("Level 2 must have content type"))
-
- if self.level_p == 3 and (self.source_type or self.category_type):
- raise ValidationError(_("Level 3 cannot have source/content type"))
-
-
- def save(self, *args, **kwargs):
- self.clean()
-
- # Get the level from the parent structure
- level = self.level_p
-
- # Apply level-specific logic
- # if level == 2 and self.parent:
- # For level 2, inherit source_type from parent
- # self.source_type = self.parent.source_type
- # elif level == 3:
- # For level 3, inherit both from parent
- # if self.parent and self.parent.parent:
- # self.source_type = self.parent.source_type
- # self.category_type = self.parent.category_type
-
- # Call the parent class's save method
- super().save(*args, **kwargs)
-
- @property
- def level_p(self):
- if not self.parent:
- return 1
- elif not self.parent.parent:
- return 2
- else:
- return 3
- def get_level_info(self):
- info = {
- 'level': self.level_p,
- 'source_type': None,
- 'category_type': None,
- }
- if self.level_p == 1:
- info['source_type'] = self.source_type
- elif self.level_p == 2:
- info['source_type'] = self.parent.source_type
- info['category_type'] = self.category_type
- return info
-
-
-
-
-=======
return f"{self.sect.sect_type}: {self.source_type} - {self.title[0]['text']}"
def get_title(self,lang):
@@ -251,4 +150,3 @@ class HadisCategory(MPTTModel):
return None
->>>>>>> develop
diff --git a/apps/hadis/models/hadis.py b/apps/hadis/models/hadis.py
index 36d692b..54f439a 100644
--- a/apps/hadis/models/hadis.py
+++ b/apps/hadis/models/hadis.py
@@ -1,19 +1,3 @@
-<<<<<<< HEAD
-
-
-from django.db import models
-from django.utils.translation import gettext_lazy as _
-from django.core.exceptions import ValidationError
-from filer.fields.image import FilerImageField
-
-
-class HadisTag(models.Model):
- title = models.CharField(max_length=355, verbose_name=_('title'))
- status = models.BooleanField(default=True, verbose_name=_('status'))
-
- def __str__(self):
- return f"{self.title}"
-=======
from enum import unique
from typing import Optional
from django.db import models
@@ -184,22 +168,11 @@ class HadisStatus(models.Model):
verbose_name = _('hadis status')
verbose_name_plural = _('hadis statuses')
ordering = ('order',)
->>>>>>> develop
class Hadis(models.Model):
-<<<<<<< HEAD
- number = models.PositiveIntegerField(verbose_name=_('number'), unique=True)
- title = models.CharField(max_length=355, verbose_name=_('title'))
- text = models.TextField(verbose_name=_('text'))
- translation = models.TextField(verbose_name=_('translation'), blank=True, default='')
-
- category = models.ForeignKey("hadis.HadisCategory", null=True, on_delete=models.SET_NULL, verbose_name=_('category'), )
-
- status = models.BooleanField(default=True, verbose_name=_('visibility'))
-=======
category = models.ForeignKey("hadis.HadisCategory", on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_('category'))
number = models.PositiveIntegerField(verbose_name=_('number'), default=1)
slug = models.SlugField(max_length=255, verbose_name=_('slug'), blank=True,unique=True)
@@ -220,37 +193,10 @@ class Hadis(models.Model):
share_link = models.CharField(max_length=255, verbose_name=_('share link'), null=True, blank=True)
explanation = models.JSONField(default = list , verbose_name=_('Explanation'))
->>>>>>> develop
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('created at'))
updated_at = models.DateTimeField(auto_now=True, verbose_name=_('updated at'))
def __str__(self):
-<<<<<<< HEAD
- return f"<{self.number}> {self.title[:32]}"
-
- @property
- def get_tags(self):
- return self.tags.all().order_by('hadistagrelation__priority')
-
- class Meta:
- verbose_name = _('hadis')
- verbose_name_plural = _('hadises')
-
-
-class HadisOverview(models.Model):
- hadis = models.OneToOneField(Hadis, on_delete=models.CASCADE, primary_key=True)
- status = models.CharField(max_length=50, verbose_name=_('status'))
- status_color = models.CharField(max_length=25, verbose_name=_('Display Status Color'))
- status_text = models.TextField(verbose_name=_('Status Text'), null=True, blank=True)
- address = models.TextField(verbose_name=_('address'), null=True, blank=True)
- links = models.JSONField(verbose_name=_('title'), null=True, blank=True, default=dict)
- tags = models.ManyToManyField("HadisTag", related_name="hadises", verbose_name=_('tags'), blank=True)
- share_link = models.CharField(max_length=255, verbose_name=_('share link'), null=True, blank=True)
- explanation = models.TextField(verbose_name=_('explanation'), null=True, blank=True)
-
- created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('created at'))
-
-=======
return f"{self.number} - {self.title[0]['text']}" if self.title else f"Hadis {self.number}"
def save(self, *args, **kwargs):
@@ -363,7 +309,6 @@ class HadisOverview(models.Model):
ordering = ('category', 'number')
->>>>>>> develop
class HadisReference(models.Model):
hadis = models.ForeignKey(
@@ -372,26 +317,6 @@ class HadisReference(models.Model):
verbose_name=_('hadis'),
related_name='references'
)
-<<<<<<< HEAD
- book = models.ForeignKey("library.Book", on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_('book'), related_name='hadis_references')
- description = models.TextField(verbose_name=_('description'), blank=True, null=True)
- created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('created at'))
-
- class Meta:
- verbose_name = _('Hadis Reference')
- verbose_name_plural = _('Hadis References')
- unique_together = ('hadis', 'book')
-
- def __str__(self):
- return f'{self.hadis.number}-{self.book.title}'
-
-class ReferenceImage(models.Model):
- reference = models.ForeignKey(HadisReference, verbose_name="Hadis Reference", on_delete=models.CASCADE)
- thumbnail = FilerImageField(
- related_name='+', on_delete=models.PROTECT, null=True, blank=True,
- verbose_name=_('thumbnail')
- )
-=======
book_reference = models.ForeignKey(
BookReference,
on_delete=models.SET_NULL,
@@ -436,7 +361,6 @@ class ReferenceImage(models.Model):
class ReferenceImage(models.Model):
reference = models.ForeignKey(HadisReference,related_name = 'images', verbose_name="Hadis Reference", on_delete=models.CASCADE)
thumbnail = models.ImageField(upload_to='hadis/reference_images/', null=True, blank=True, verbose_name=_('thumbnail'))
->>>>>>> develop
priority = models.IntegerField(
default=0,
verbose_name=_("Priority"),
@@ -445,22 +369,15 @@ class ReferenceImage(models.Model):
class Meta:
-<<<<<<< HEAD
-=======
indexes = [
# Speeds up fetching images for a reference in priority order
models.Index(fields=['reference', 'priority']),
]
->>>>>>> develop
verbose_name = _('Reference Image')
verbose_name_plural = _('Reference Images')
def __str__(self):
-<<<<<<< HEAD
- return f'{self.reference.title}-{self.id}'
-=======
return f'{self.reference.title[0]["text"]}-{self.id}'
->>>>>>> develop
def save(self, *args, **kwargs):
if ReferenceImage.objects.filter(reference=self.reference, priority=self.priority).exists():
@@ -470,9 +387,6 @@ class ReferenceImage(models.Model):
).update(priority=F('priority') + 1)
super().save(*args, **kwargs)
-<<<<<<< HEAD
-
-=======
class HadisCorrection(models.Model):
hadis = models.ForeignKey(Hadis, verbose_name=_("hadis correction"), on_delete=models.CASCADE)
@@ -574,4 +488,3 @@ class HadisCorrection(models.Model):
if isinstance(tr, dict) and tr.get('language_code') == 'en':
return tr.get('text', '')
return None
->>>>>>> develop
diff --git a/apps/hadis/models/transmitter.py b/apps/hadis/models/transmitter.py
index 6159ae6..6abb658 100644
--- a/apps/hadis/models/transmitter.py
+++ b/apps/hadis/models/transmitter.py
@@ -1,11 +1,5 @@
-<<<<<<< HEAD
-from django.db import models
-from django.utils.translation import gettext_lazy as _
-from django.core.exceptions import ValidationError
-from filer.fields.image import FilerImageField
-=======
from tabnanny import verbose
from django.db import models
from django.utils.translation import gettext_lazy as _
@@ -122,25 +116,10 @@ class TransmitterReliability(models.Model):
class Meta:
verbose_name = _('Transmitter Reliability')
verbose_name_plural = _('Transmitter Reliabilities')
->>>>>>> develop
class Transmitters(models.Model):
-<<<<<<< HEAD
- full_name = models.CharField(max_length=255)
- birth_year_hijri = models.IntegerField(verbose_name="Birth Year (Hijri)")
- death_year_hijri = models.IntegerField(verbose_name="Death Year (Hijri)")
- description = models.TextField(blank=True, null=True, verbose_name="Description")
- status = models.CharField(max_length=50, verbose_name=_('status'))
- status_color = models.CharField(max_length=25, verbose_name=_('Display Status Color'))
- thumbnail = FilerImageField(related_name="+", on_delete=models.CASCADE, help_text=_(
- 'image allowed'
- ), null=True, blank=True)
-
- def __str__(self):
- return self.full_name
-=======
# class ReliabilityLevel(models.TextChoices):
# VERY_RELIABLE = 'very_reliable', _('Very Reliable')
# RELIABLE = 'reliable', _('Reliable')
@@ -291,7 +270,6 @@ class Transmitters(models.Model):
name = self.full_name[0]
return name.get('text')
->>>>>>> develop
class HadisTransmitter(models.Model):
@@ -307,9 +285,6 @@ class HadisTransmitter(models.Model):
verbose_name=_('transmitter'),
related_name='hadises'
)
-<<<<<<< HEAD
- description = models.TextField(verbose_name=_('description'), blank=True, null=True)
-=======
narrator_layer = models.ForeignKey(
NarratorLayer,
on_delete=models.SET_NULL,
@@ -328,17 +303,11 @@ class HadisTransmitter(models.Model):
blank=True,
help_text=_('Reliability status of the narrator')
)
->>>>>>> develop
order = models.PositiveIntegerField(
default=0,
verbose_name=_('Order'),
help_text=_('Order in the chain of transmission')
)
-<<<<<<< HEAD
- created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('created at'))
-
- class Meta:
-=======
is_gap = models.BooleanField(default=False, verbose_name=_('is gap'))
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('created at'))
@@ -348,16 +317,12 @@ class HadisTransmitter(models.Model):
# Speeds up fetching transmitters for a specific hadis in order
models.Index(fields=['hadis', 'order']),
]
->>>>>>> develop
verbose_name = _('Hadis Transmitter')
verbose_name_plural = _('Hadis Transmitters')
ordering = ('hadis', 'order')
unique_together = ('hadis', 'transmitter', 'order')
def __str__(self):
-<<<<<<< HEAD
- return f'{self.hadis.number} - {self.transmitter.full_name} ({self.order})'
-=======
layer_info = f" - {self.narrator_layer}" if self.narrator_layer else ""
return f'{self.hadis.number} - {self.transmitter.full_name} ({self.order}){layer_info}'
@@ -574,4 +539,3 @@ class TransmitterOriginalText(models.Model):
if isinstance(tr, dict) and tr.get('language_code') == 'en':
return tr.get('text', '')
return None
->>>>>>> develop
diff --git a/apps/hadis/views/__init__.py b/apps/hadis/views/__init__.py
index 4fe1bee..a05e2cd 100644
--- a/apps/hadis/views/__init__.py
+++ b/apps/hadis/views/__init__.py
@@ -1,7 +1,3 @@
from .category import *
from .hadis import *
-<<<<<<< HEAD
-# from .transmitter import *
-=======
from .info import *
->>>>>>> develop
diff --git a/apps/hadis/views/hadis.py b/apps/hadis/views/hadis.py
index 04a6d3d..12bdf75 100644
--- a/apps/hadis/views/hadis.py
+++ b/apps/hadis/views/hadis.py
@@ -1,80 +1,3 @@
-<<<<<<< HEAD
-from rest_framework.permissions import IsAuthenticated
-from rest_framework.response import Response
-from django.db.models import Subquery, Count, F, OuterRef, Q, Prefetch
-from rest_framework.generics import ListAPIView, RetrieveAPIView
-from django.shortcuts import get_object_or_404
-
-
-from apps.hadis.models import *
-from apps.hadis.serializers import *
-from apps.hadis.doc import category_list_swagger, category_hadis_list_swagger, hadis_detail_swagger
-
-
-
-class CategoryHadisListView(ListAPIView):
- serializer_class = HadisSerializer
- permission_classes = (IsAuthenticated,)
-
- @category_hadis_list_swagger
- def get(self, request, *args, **kwargs):
- return super().get(request, *args, **kwargs)
- def get_queryset(self):
- categories = HadisCategory.objects.filter(id=self.kwargs['pk']).order_by('-order')
- return Hadis.objects.filter(
- Q(category__in=categories),
- status=True,
- ).prefetch_related(
- 'category',
- )
-
-
-class HadisDetailView(RetrieveAPIView):
- """
- API endpoint to retrieve detailed information about a specific hadis.
-
- Returns:
- - Hadis details (number, title, text, translation)
- - HadisOverview information (status, tags, etc.)
- - First HadisReference with its ReferenceImages
- - List of Transmitters
- """
- serializer_class = HadisDetailSerializer
- permission_classes = (IsAuthenticated,)
-
- @hadis_detail_swagger
- def get(self, request, *args, **kwargs):
- return super().get(request, *args, **kwargs)
-
- def get_object(self):
- hadis_id = self.kwargs.get('pk')
- queryset = Hadis.objects.filter(id=hadis_id)
-
- # Prefetch related data to optimize queries
- queryset = queryset.prefetch_related(
- 'hadisoverview',
- 'hadisoverview__tags',
- Prefetch(
- 'references',
- queryset=HadisReference.objects.prefetch_related(
- 'referenceimage_set',
- 'book'
- )
- ),
- Prefetch(
- 'transmitters',
- queryset=HadisTransmitter.objects.select_related('transmitter').order_by('order')
- )
- )
-
- return get_object_or_404(queryset, id=hadis_id)
-
- def get_serializer_context(self):
- context = super().get_serializer_context()
- context.update({'request': self.request})
- return context
-
-=======
from rest_framework.generics import ListAPIView, RetrieveAPIView
from django.shortcuts import get_object_or_404
from utils.pagination import NoPagination
@@ -487,4 +410,3 @@ class HadisFiltersView(ListAPIView):
}
return Response(response_data)
->>>>>>> develop
diff --git a/apps/library/migrations/0001_initial.py b/apps/library/migrations/0001_initial.py
index a9264ca..151cd03 100644
--- a/apps/library/migrations/0001_initial.py
+++ b/apps/library/migrations/0001_initial.py
@@ -1,18 +1,9 @@
-<<<<<<< HEAD
-# Generated by Django 3.2.7 on 2025-03-20 07:06
-
-from django.conf import settings
-from django.db import migrations, models
-import django.db.models.deletion
-import filer.fields.image
-=======
# Generated by Django 5.1.8 on 2025-04-03 00:05
import django.db.models.deletion
import filer.fields.image
from django.conf import settings
from django.db import migrations, models
->>>>>>> develop
class Migration(migrations.Migration):
@@ -20,40 +11,11 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
-<<<<<<< HEAD
- migrations.swappable_dependency(settings.AUTH_USER_MODEL),
-=======
->>>>>>> develop
migrations.swappable_dependency(settings.FILER_IMAGE_MODEL),
]
operations = [
migrations.CreateModel(
-<<<<<<< HEAD
- name='Book',
- fields=[
- ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('title', models.CharField(max_length=255)),
- ('slug', models.SlugField(max_length=255, unique=True)),
- ('summary', models.CharField(blank=True, help_text='could be null', max_length=512, null=True)),
- ('description', models.TextField(blank=True, help_text='could be null', null=True)),
- ('pages_count', models.CharField(help_text='eg. 34', max_length=255, null=True, verbose_name='Number of Pages')),
- ('status', models.BooleanField(default=True, verbose_name='status')),
- ('pin', models.BooleanField(default=True, verbose_name='Pin to top')),
- ('view_count', models.PositiveBigIntegerField(default=0, verbose_name='view count')),
- ('file_type', models.CharField(choices=[('pdf', 'Pdf'), ('epub', 'Epub'), ('docx', 'Docx')], default='pdf', max_length=16, verbose_name='File Type')),
- ('book_file', models.FileField(blank=True, max_length=550, null=True, upload_to='books', verbose_name='Book File')),
- ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='created at')),
- ('updated_at', models.DateTimeField(auto_now=True, verbose_name='updated at')),
- ],
- options={
- 'verbose_name': 'Book',
- 'verbose_name_plural': 'Books',
- },
- ),
- migrations.CreateModel(
-=======
->>>>>>> develop
name='Category',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
@@ -62,10 +24,6 @@ class Migration(migrations.Migration):
('status', models.BooleanField(default=True, verbose_name='status')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='created at')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='updated at')),
-<<<<<<< HEAD
- ('books', models.ManyToManyField(blank=True, related_name='related_categories_books', to='library.Book', verbose_name='Books')),
-=======
->>>>>>> develop
],
options={
'verbose_name': 'Category',
@@ -73,18 +31,6 @@ class Migration(migrations.Migration):
},
),
migrations.CreateModel(
-<<<<<<< HEAD
- name='BookDownload',
- fields=[
- ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='created at')),
- ('book', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='downloads', to='library.book', verbose_name='Book')),
- ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='book_downloads', to=settings.AUTH_USER_MODEL, verbose_name='User')),
- ],
- options={
- 'verbose_name': 'Book Download',
- 'verbose_name_plural': 'Book Downloads',
-=======
name='Book',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
@@ -107,27 +53,18 @@ class Migration(migrations.Migration):
options={
'verbose_name': 'Book',
'verbose_name_plural': 'Books',
->>>>>>> develop
},
),
migrations.CreateModel(
name='BookCollection',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-<<<<<<< HEAD
- ('title', models.JSONField(default=dict, verbose_name='title')),
-=======
('title', models.CharField(max_length=255)),
->>>>>>> develop
('summary', models.CharField(blank=True, help_text='could be null', max_length=512, null=True)),
('display_position', models.CharField(choices=[('pinned', 'Pinned'), ('middle', 'Middle Section'), ('bottom', 'Bottom Section')], default='pinned', max_length=20, verbose_name='Display Position')),
('status', models.BooleanField(default=True, verbose_name='status')),
('order', models.IntegerField(default=0, verbose_name='order')),
-<<<<<<< HEAD
- ('books', models.ManyToManyField(blank=True, related_name='related_collections_books', to='library.Book', verbose_name='Books')),
-=======
('books', models.ManyToManyField(blank=True, related_name='related_collections_books', to='library.book', verbose_name='Books')),
->>>>>>> develop
],
options={
'verbose_name': 'Book Collection',
@@ -136,23 +73,8 @@ class Migration(migrations.Migration):
),
migrations.AddField(
model_name='book',
-<<<<<<< HEAD
- name='categories',
- field=models.ManyToManyField(blank=True, related_name='related_categories', to='library.Category', verbose_name='categories'),
- ),
- migrations.AddField(
- model_name='book',
- name='collections',
- field=models.ManyToManyField(blank=True, related_name='related_collections', to='library.BookCollection', verbose_name='collections'),
- ),
- migrations.AddField(
- model_name='book',
- name='thumbnail',
- field=filer.fields.image.FilerImageField(blank=True, help_text='image allowed', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.FILER_IMAGE_MODEL),
-=======
name='collections',
field=models.ManyToManyField(blank=True, related_name='related_collections', to='library.bookcollection', verbose_name='collections'),
->>>>>>> develop
),
migrations.CreateModel(
name='BottomBookCollection',
@@ -193,12 +115,9 @@ class Migration(migrations.Migration):
},
bases=('library.bookcollection',),
),
-<<<<<<< HEAD
-=======
migrations.AddField(
model_name='book',
name='categories',
field=models.ManyToManyField(blank=True, related_name='related_categories', to='library.category', verbose_name='categories'),
),
->>>>>>> develop
- ]
+ ]
\ No newline at end of file
diff --git a/apps/quiz/admin/participant.py b/apps/quiz/admin/participant.py
index 9f9d2d1..b5fa008 100644
--- a/apps/quiz/admin/participant.py
+++ b/apps/quiz/admin/participant.py
@@ -1,23 +1,3 @@
-<<<<<<< HEAD
-from ajaxdatatable.admin import AjaxDatatable
-from django.contrib import admin
-from django.db.models import F, Q
-from django.contrib.admin import SimpleListFilter
-from django.utils.translation import gettext_lazy as _
-from apps.quiz.models import QuizParticipant, ParticipantAnswer
-from apps.account.models import User
-import datetime
-
-
-class ParticipantAnswerInline(admin.StackedInline):
- model = ParticipantAnswer
-
- readonly_fields = (
- '_correct_answer', 'question', 'at_time', 'answer_timing',
- )
-
- def _correct_answer(self, obj):
-=======
from django.contrib import admin
from django.db.models import F
from django.contrib.admin import SimpleListFilter
@@ -39,7 +19,6 @@ class ParticipantAnswerInline(StackedInline):
@display(description="Correct Answer")
def correct_answer_display(self, obj):
->>>>>>> develop
return obj.correct_answer
def has_add_permission(self, request, obj):
@@ -52,11 +31,6 @@ class ParticipantAnswerInline(StackedInline):
return super().get_queryset(request).annotate(correct_answer=F('question__correct_answer'))
-<<<<<<< HEAD
-
-
-=======
->>>>>>> develop
class UserEmailFilter(SimpleListFilter):
title = _('User Email')
parameter_name = 'user_email'
@@ -72,22 +46,6 @@ class UserEmailFilter(SimpleListFilter):
return queryset
-<<<<<<< HEAD
-@admin.register(QuizParticipant)
-class ParticipantAdmin(AjaxDatatable):
- inlines = [ParticipantAnswerInline]
- search_fields = ['user__username', 'user__fullname']
- list_display = ['quiz', 'user', 'started_at', 'ended_at', 'total_timing', 'question_score', 'timing_score',
- 'total_score']
- latest_by = 'started_at'
- list_filter = ['started_at', 'ended_at', 'quiz__status', UserEmailFilter]
-
-
-
-
-
-
-=======
class ParticipantAdmin(ModelAdmin):
inlines = [ParticipantAnswerInline]
search_fields = ['user__username', 'user__fullname']
@@ -102,4 +60,3 @@ class ParticipantAdmin(ModelAdmin):
ordering = ['-started_at']
project_admin_site.register(QuizParticipant, ParticipantAdmin)
->>>>>>> develop
diff --git a/apps/quiz/admin/question.py b/apps/quiz/admin/question.py
index 8135229..2036866 100644
--- a/apps/quiz/admin/question.py
+++ b/apps/quiz/admin/question.py
@@ -1,24 +1,3 @@
-<<<<<<< HEAD
-from ajaxdatatable.admin import AjaxDatatable
-from django import forms
-from django.contrib import admin
-
-from apps.quiz.models import Question
-
-
-class QuestionAdminForm(forms.ModelForm):
- class Meta:
- model = Question
- exclude = ()
- widgets = {
- 'correct_answer': forms.RadioSelect,
- 'question': forms.Textarea
- }
-
-
-# @admin.register(Question)
-# class QuestionAdmin(AjaxDatatable):
-=======
from django import forms
from django.contrib import admin
@@ -34,17 +13,12 @@ from utils.admin import project_admin_site
# Uncomment if you want to register Question as a standalone admin
# @admin.register(Question)
# class QuestionAdmin(ModelAdmin):
->>>>>>> develop
# list_display = ('question', 'correct_answer', 'quiz', 'priority')
# form = QuestionAdminForm
# ordering = ("priority", "id",)
# fieldsets = (
# (
-<<<<<<< HEAD
-# '', {
-=======
# None, {
->>>>>>> develop
# 'fields': (
# 'question',
# ('option1', 'option2'),
@@ -54,27 +28,11 @@ from utils.admin import project_admin_site
# },
# ),
# (
-<<<<<<< HEAD
-# '', {
-=======
# None, {
->>>>>>> develop
# 'fields': ('priority',)
# }
# )
# )
-<<<<<<< HEAD
-
-class QuestionAdminInline(admin.StackedInline):
- model = Question
- list_display = ('question', 'correct_answer', 'quiz', 'priority')
- form = QuestionAdminForm
- ordering = ("priority", "id",)
- extra = 0
- fieldsets = (
- (
- '', {
-=======
@admin.register(Question)
class QuestionAdmin(ModelAdmin):
list_display = ('question', 'correct_answer', 'quiz', 'priority')
@@ -109,7 +67,6 @@ class QuestionAdminInline(StackedInline):
fieldsets = (
(
None, {
->>>>>>> develop
'fields': (
'question',
('option1', 'option2'),
@@ -119,17 +76,10 @@ class QuestionAdminInline(StackedInline):
},
),
(
-<<<<<<< HEAD
- '', {
-=======
None, {
->>>>>>> develop
'fields': ('priority',)
}
)
)
-<<<<<<< HEAD
-=======
project_admin_site.register(Question, QuestionAdmin)
->>>>>>> develop
diff --git a/apps/quiz/admin/quiz.py b/apps/quiz/admin/quiz.py
index a17bbe1..95d7cd5 100644
--- a/apps/quiz/admin/quiz.py
+++ b/apps/quiz/admin/quiz.py
@@ -1,59 +1,3 @@
-<<<<<<< HEAD
-from ajaxdatatable.admin import AjaxDatatable
-from django.contrib import admin
-from django.db.models import Count
-from django.utils.safestring import mark_safe
-from django.utils.html import format_html
-from django.urls import reverse, path
-
-from apps.course.models import Lesson
-from apps.quiz.models import Quiz
-from .question import QuestionAdminInline
-
-
-
-
-@admin.register(Quiz)
-class QuizAdmin(AjaxDatatable):
- search_fields = ['title', 'lesson__title']
- list_display = ['title', 'description','lesson','each_question_timing', '_status', '_questions',]
- autocomplete_fields = ['lesson',]
- list_filter = ['each_question_timing',]
- inlines = [QuestionAdminInline,]
-
-
- def get_queryset(self, request):
- queryset = super().get_queryset(request)
- if request.user.groups.filter(name="Professor Group").exists():
- return queryset.filter(lesson__course__professor=request.user)
-
- return queryset
-
- def get_form(self, request, obj=None, **kwargs):
- form = super().get_form(request, obj, **kwargs)
- if obj is None:
- form.base_fields['lesson'].queryset = Lesson.objects.all() if request.user.is_staff else Lesson.objects.filter(course__professor=request.user)
- form.base_fields['lesson'].widget.can_add_related = False
-
- return form
-
-
- @admin.display(description='Status', ordering='status')
- def _status(self, obj):
- if obj.status:
- return mark_safe("Active")
-
- return mark_safe("Inactive")
-
- @admin.display(description='Questions', ordering='questions_count')
- def _questions(self, obj):
- return mark_safe(f"Questions: {obj.questions_count}")
-
- def get_queryset(self, request):
- return super().get_queryset(request).annotate(
- questions_count=Count('questions')
- )
-=======
from django.contrib import admin
from django.db.models import Count
from django.utils.safestring import mark_safe
@@ -126,4 +70,3 @@ class QuizAdmin(ModelAdmin):
return mark_safe(f'Questions: {obj.questions_count}')
project_admin_site.register(Quiz, QuizAdmin)
->>>>>>> develop
diff --git a/apps/quiz/migrations/0001_initial.py b/apps/quiz/migrations/0001_initial.py
index 229ee40..baf658e 100644
--- a/apps/quiz/migrations/0001_initial.py
+++ b/apps/quiz/migrations/0001_initial.py
@@ -1,16 +1,6 @@
-<<<<<<< HEAD
-# Generated by Django 3.2.4 on 2024-11-29 11:00
-
-from django.conf import settings
-from django.db import migrations, models
-import django.db.models.deletion
-=======
-# Generated by Django 5.1.8 on 2025-04-03 00:05
-
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
->>>>>>> develop
class Migration(migrations.Migration):
@@ -18,37 +8,13 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
-<<<<<<< HEAD
- ('course', '0005_participant_unread_messages_count'),
- migrations.swappable_dependency(settings.AUTH_USER_MODEL),
- ('account', '0004_user_skill'),
-=======
('account', '0001_initial'),
('course', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
->>>>>>> develop
]
operations = [
migrations.CreateModel(
-<<<<<<< HEAD
- name='Quiz',
- fields=[
- ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('title', models.CharField(help_text='Quiz Title', max_length=255, verbose_name='title')),
- ('each_question_timing', models.PositiveIntegerField()),
- ('status', models.BooleanField(default=True)),
- ('lesson', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='quizzes', to='course.lesson', verbose_name='lesson')),
- ],
- options={
- 'verbose_name': 'Quiz',
- 'verbose_name_plural': 'Quizzes',
- 'ordering': ('-id',),
- },
- ),
- migrations.CreateModel(
-=======
->>>>>>> develop
name='QuizRankUser',
fields=[
],
@@ -62,23 +28,6 @@ class Migration(migrations.Migration):
bases=('account.user',),
),
migrations.CreateModel(
-<<<<<<< HEAD
- name='QuizParticipant',
- fields=[
- ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('started_at', models.DateTimeField(verbose_name='started at')),
- ('ended_at', models.DateTimeField(verbose_name='ended at')),
- ('total_timing', models.PositiveIntegerField(help_text='Seconds take to finish the quiz')),
- ('question_score', models.PositiveIntegerField()),
- ('timing_score', models.PositiveIntegerField()),
- ('total_score', models.PositiveIntegerField()),
- ('quiz', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='participants', to='quiz.quiz')),
- ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='uquizzes', to=settings.AUTH_USER_MODEL, verbose_name='user')),
- ],
- options={
- 'verbose_name': 'Participant',
- 'verbose_name_plural': 'Participants',
-=======
name='Quiz',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
@@ -91,7 +40,6 @@ class Migration(migrations.Migration):
options={
'verbose_name': 'Quiz',
'verbose_name_plural': 'Quizzes',
->>>>>>> develop
'ordering': ('-id',),
},
),
@@ -116,8 +64,6 @@ class Migration(migrations.Migration):
},
),
migrations.CreateModel(
-<<<<<<< HEAD
-=======
name='QuizParticipant',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
@@ -137,20 +83,14 @@ class Migration(migrations.Migration):
},
),
migrations.CreateModel(
->>>>>>> develop
name='ParticipantAnswer',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('option_num', models.PositiveSmallIntegerField(choices=[(1, 'Option 1'), (2, 'Option 2'), (3, 'Option 3'), (4, 'Option 4')], verbose_name='selected option')),
('at_time', models.DateTimeField()),
('answer_timing', models.PositiveSmallIntegerField(default=0, verbose_name='seconds take to answer')),
-<<<<<<< HEAD
- ('participant', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='answers', to='quiz.quizparticipant')),
- ('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='quiz.question')),
-=======
('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='quiz.question')),
('participant', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='answers', to='quiz.quizparticipant')),
->>>>>>> develop
],
options={
'verbose_name': 'User Quiz Answer',
@@ -158,4 +98,4 @@ class Migration(migrations.Migration):
'ordering': ('-id',),
},
),
- ]
+ ]
\ No newline at end of file
diff --git a/apps/quiz/models/participant.py b/apps/quiz/models/participant.py
index c796d07..3400977 100644
--- a/apps/quiz/models/participant.py
+++ b/apps/quiz/models/participant.py
@@ -1,9 +1,6 @@
from django.db import models
-<<<<<<< HEAD
-=======
from django.db.models import F, Window
from django.db.models.functions import Rank
->>>>>>> develop
from apps.account.models import User
diff --git a/apps/quiz/models/quiz.py b/apps/quiz/models/quiz.py
index 21cf049..1be1190 100644
--- a/apps/quiz/models/quiz.py
+++ b/apps/quiz/models/quiz.py
@@ -5,12 +5,8 @@ from apps.account.models import User
class Quiz(models.Model):
-<<<<<<< HEAD
- lesson = models.ForeignKey("course.Lesson", verbose_name=_('lesson'), related_name='quizzes', on_delete=models.CASCADE)
-=======
lesson = models.ForeignKey("course.CourseLesson", verbose_name=_('lesson'), related_name='quizzes', on_delete=models.CASCADE)
->>>>>>> develop
title = models.CharField(max_length=255, verbose_name=_('title'), help_text="Quiz Title")
description = models.CharField(max_length=55, blank=True, null=True, verbose_name="Description")
each_question_timing = models.PositiveIntegerField()
diff --git a/apps/quiz/serializers/quiz.py b/apps/quiz/serializers/quiz.py
index 5a3538c..6156e64 100644
--- a/apps/quiz/serializers/quiz.py
+++ b/apps/quiz/serializers/quiz.py
@@ -1,11 +1,7 @@
from rest_framework import serializers
from apps.quiz.models import Question, Quiz, QuizParticipant
-<<<<<<< HEAD
-from apps.course.models import Lesson, Participant
-=======
from apps.course.models import Participant
->>>>>>> develop
@@ -27,9 +23,6 @@ class QuizListSerializer(serializers.ModelSerializer):
return False
# Check if the user has participated in this quiz
user = request.user
-<<<<<<< HEAD
- course = obj.lesson.course
-=======
# obj.lesson is now CourseLesson directly
course_lesson = obj.lesson
@@ -37,7 +30,6 @@ class QuizListSerializer(serializers.ModelSerializer):
return False
course = course_lesson.course
->>>>>>> develop
if not self._is_participant(user, course):
return False
@@ -92,10 +84,6 @@ class QuizSerializer(serializers.ModelSerializer):
return False
# Check if the user has participated in this quiz
user = request.user
-<<<<<<< HEAD
- participated = QuizParticipant.objects.filter(user=user, quiz=obj).exists()
- return not participated
-=======
# obj.lesson is now CourseLesson directly
course_lesson = obj.lesson
@@ -110,4 +98,3 @@ class QuizSerializer(serializers.ModelSerializer):
participated = QuizParticipant.objects.filter(user=user, quiz=obj).exists()
return participated
->>>>>>> develop
diff --git a/apps/quiz/views/participant.py b/apps/quiz/views/participant.py
index 1de79d9..dbe6bc3 100644
--- a/apps/quiz/views/participant.py
+++ b/apps/quiz/views/participant.py
@@ -17,10 +17,7 @@ class QuizParticipantCreateAPIView(CreateAPIView):
@swagger_auto_schema(
operation_description=doc_quiz_submit(),
-<<<<<<< HEAD
-=======
tags=["Imam-Javad - Quiz"],
->>>>>>> develop
)
def post(self, request, *args, **kwargs):
- return super().post(request, *args, **kwargs)
+ return super().post(request, *args, **kwargs)
\ No newline at end of file
diff --git a/apps/quiz/views/quiz.py b/apps/quiz/views/quiz.py
index 336314e..5dd8a00 100644
--- a/apps/quiz/views/quiz.py
+++ b/apps/quiz/views/quiz.py
@@ -17,10 +17,7 @@ class QuizDetailAPIView(RetrieveAPIView):
@swagger_auto_schema(
operation_description=doc_quiz_detail(),
-<<<<<<< HEAD
-=======
tags=["Imam-Javad - Quiz"],
->>>>>>> develop
)
def get(self, request, *args, **kwargs):
return super().get(request, *args, **kwargs)
@@ -31,7 +28,3 @@ class QuizDetailAPIView(RetrieveAPIView):
).annotate(
lesson__has_quiz=Value(True)
).select_related('lesson').first()
-
-
-
-
diff --git a/apps/transaction/migrations/0001_initial.py b/apps/transaction/migrations/0001_initial.py
index 899a98b..6fc1605 100644
--- a/apps/transaction/migrations/0001_initial.py
+++ b/apps/transaction/migrations/0001_initial.py
@@ -1,12 +1,3 @@
-<<<<<<< HEAD
-# Generated by Django 3.2.4 on 2024-11-30 22:25
-
-from django.conf import settings
-from django.db import migrations, models
-import django.db.models.deletion
-import phonenumber_field.modelfields
-import utils.validators
-=======
# Generated by Django 5.1.8 on 2025-04-03 00:05
import django.db.models.deletion
@@ -14,7 +5,6 @@ import phonenumber_field.modelfields
import utils.validators
from django.conf import settings
from django.db import migrations, models
->>>>>>> develop
class Migration(migrations.Migration):
@@ -22,13 +12,8 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
-<<<<<<< HEAD
- migrations.swappable_dependency(settings.AUTH_USER_MODEL),
- ('course', '0005_participant_unread_messages_count'),
-=======
('course', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
->>>>>>> develop
]
operations = [
@@ -49,13 +34,8 @@ class Migration(migrations.Migration):
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('fullname', models.CharField(help_text='Enter the full name of the user.', max_length=255, verbose_name='Full Name')),
-<<<<<<< HEAD
- ('email', models.EmailField(help_text="Enter the user's email address.", max_length=254, unique=True, verbose_name='Email Address')),
- ('phone_number', phonenumber_field.modelfields.PhoneNumberField(blank=True, max_length=128, null=True, region=None, unique=True, validators=[utils.validators.validate_possible_number], verbose_name='phone')),
-=======
('email', models.EmailField(help_text="Enter the user's email address.", max_length=254, verbose_name='Email Address')),
('phone_number', phonenumber_field.modelfields.PhoneNumberField(blank=True, max_length=128, null=True, region=None, validators=[utils.validators.validate_possible_number], verbose_name='phone')),
->>>>>>> develop
('gender', models.CharField(blank=True, choices=[('male', 'Male'), ('female', 'Female')], help_text="Select the user's gender.", max_length=20, null=True, verbose_name='Gender')),
('birthdate', models.DateField(blank=True, null=True, verbose_name='birthdate')),
('transaction_participant', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='participant_infos', to='transaction.transactionparticipant', verbose_name='Transaction Participant')),
diff --git a/apps/video/migrations/0001_initial.py b/apps/video/migrations/0001_initial.py
index 95cd5a1..d63e4c5 100644
--- a/apps/video/migrations/0001_initial.py
+++ b/apps/video/migrations/0001_initial.py
@@ -1,18 +1,9 @@
-<<<<<<< HEAD
-# Generated by Django 3.2.7 on 2025-03-21 22:06
-
-from django.conf import settings
-from django.db import migrations, models
-import django.db.models.deletion
-import filer.fields.image
-=======
# Generated by Django 5.1.8 on 2025-04-03 00:05
import django.db.models.deletion
import filer.fields.image
from django.conf import settings
from django.db import migrations, models
->>>>>>> develop
class Migration(migrations.Migration):
@@ -25,30 +16,6 @@ class Migration(migrations.Migration):
operations = [
migrations.CreateModel(
-<<<<<<< HEAD
- name='Video',
- fields=[
- ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('title', models.CharField(max_length=255, null=True)),
- ('slug', models.SlugField(allow_unicode=True, unique=True)),
- ('description', models.TextField(null=True)),
- ('video_type', models.CharField(choices=[('file', 'File'), ('youtube', 'Youtube')], default='file', max_length=255)),
- ('video_file', models.FileField(blank=True, null=True, upload_to='video/videos/')),
- ('video_url', models.CharField(blank=True, max_length=655, null=True)),
- ('video_time', models.TimeField()),
- ('view_count', models.PositiveBigIntegerField(default=0, verbose_name='view count')),
- ('status', models.BooleanField(default=True, verbose_name='status')),
- ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='created at')),
- ('updated_at', models.DateTimeField(auto_now=True, verbose_name='updated at')),
- ],
- options={
- 'verbose_name': 'Video',
- 'verbose_name_plural': 'Videos',
- },
- ),
- migrations.CreateModel(
-=======
->>>>>>> develop
name='VideoCategory',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
@@ -80,8 +47,6 @@ class Migration(migrations.Migration):
},
),
migrations.CreateModel(
-<<<<<<< HEAD
-=======
name='Video',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
@@ -105,7 +70,6 @@ class Migration(migrations.Migration):
},
),
migrations.CreateModel(
->>>>>>> develop
name='VideoInCollection',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
@@ -122,20 +86,6 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='videocollection',
name='videos',
-<<<<<<< HEAD
- field=models.ManyToManyField(related_name='collections', through='video.VideoInCollection', to='video.Video', verbose_name='videos'),
- ),
- migrations.AddField(
- model_name='video',
- name='categories',
- field=models.ManyToManyField(blank=True, related_name='videos', to='video.VideoCategory', verbose_name='categories'),
- ),
- migrations.AddField(
- model_name='video',
- name='thumbnail',
- field=filer.fields.image.FilerImageField(blank=True, help_text='image allowed', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.FILER_IMAGE_MODEL),
-=======
field=models.ManyToManyField(related_name='collections', through='video.VideoInCollection', to='video.video', verbose_name='videos'),
->>>>>>> develop
),
- ]
+ ]
\ No newline at end of file
diff --git a/dynamic_preferences/admin.py b/dynamic_preferences/admin.py
index 21dd326..369a0e8 100644
--- a/dynamic_preferences/admin.py
+++ b/dynamic_preferences/admin.py
@@ -8,11 +8,8 @@ from .models import GlobalPreferenceModel
from .forms import GlobalSinglePreferenceForm, SinglePerInstancePreferenceForm
from django.utils.translation import gettext_lazy as _
-<<<<<<< HEAD
-=======
from unfold.admin import ModelAdmin, TabularInline
from utils.admin import project_admin_site
->>>>>>> develop
class SectionFilter(admin.AllValuesFieldListFilter):
def __init__(self, field, request, params, model, model_admin, field_path):
@@ -45,12 +42,8 @@ class SectionFilter(admin.AllValuesFieldListFilter):
yield choice
-<<<<<<< HEAD
-class DynamicPreferenceAdmin(AjaxDatatable):
-=======
# Change DynamicPreferenceAdmin to inherit from unfold's ModelAdmin
class DynamicPreferenceAdmin(ModelAdmin):
->>>>>>> develop
list_display = (
"verbose_name",
"help_text",
@@ -58,15 +51,11 @@ class DynamicPreferenceAdmin(ModelAdmin):
fields = ("raw_value", "default_value",)
readonly_fields = ("default_value",)
change_form_template = "dynamic_preferences/dyna_change_form.html"
-<<<<<<< HEAD
-
-=======
# Unfold specific settings
search_fields = ["name", "section"]
list_filter = ["section"]
->>>>>>> develop
@admin.display(description=_('Verbose name'))
def verbose_name(self, obj):
return obj.verbose_name
@@ -112,8 +101,6 @@ class DynamicPreferenceAdmin(ModelAdmin):
class GlobalPreferenceAdmin(DynamicPreferenceAdmin):
form = GlobalSinglePreferenceForm
changelist_form = GlobalSinglePreferenceForm
-<<<<<<< HEAD
-=======
# Unfold specific customizations
list_display_links = ["verbose_name"]
@@ -129,7 +116,6 @@ class GlobalPreferenceAdmin(DynamicPreferenceAdmin):
manager = pref.registry.manager()
manager.update_db_pref(pref.section, pref.name, pref.preference.default)
reset_to_default.short_description = _("Reset selected preferences to default values")
->>>>>>> develop
def get_queryset(self, *args, **kwargs):
# Instanciate default prefs
@@ -138,14 +124,10 @@ class GlobalPreferenceAdmin(DynamicPreferenceAdmin):
return super(GlobalPreferenceAdmin, self).get_queryset(*args, **kwargs)
-<<<<<<< HEAD
-admin.site.register(GlobalPreferenceModel, GlobalPreferenceAdmin)
-=======
project_admin_site.register(GlobalPreferenceModel, GlobalPreferenceAdmin)
->>>>>>> develop
class PerInstancePreferenceAdmin(DynamicPreferenceAdmin):
@@ -155,7 +137,3 @@ class PerInstancePreferenceAdmin(DynamicPreferenceAdmin):
form = SinglePerInstancePreferenceForm
changelist_form = SinglePerInstancePreferenceForm
list_select_related = True
-<<<<<<< HEAD
-=======
-
->>>>>>> develop
diff --git a/dynamic_preferences/locale/fa/LC_MESSAGES/django.po b/dynamic_preferences/locale/fa/LC_MESSAGES/django.po
index 58e4376..dff9266 100644
--- a/dynamic_preferences/locale/fa/LC_MESSAGES/django.po
+++ b/dynamic_preferences/locale/fa/LC_MESSAGES/django.po
@@ -8,11 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-<<<<<<< HEAD
-"POT-Creation-Date: 2023-02-16 15:12+0330\n"
-=======
"POT-Creation-Date: 2025-12-23 15:18+0330\n"
->>>>>>> develop
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -22,59 +18,6 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
-<<<<<<< HEAD
-#: admin.py:69
-msgid "Default Value"
-msgstr "مقدار پیشفرض"
-
-#: admin.py:78 models.py:30
-msgid "Section Name"
-msgstr "عنوان بخش"
-
-#: apps.py:10
-msgid "Dynamic Preferences"
-msgstr "تنظیمات"
-
-#: models.py:34
-msgid "Name"
-msgstr "نام"
-
-#: models.py:37
-msgid "Raw Value"
-msgstr "مقدار"
-
-#: models.py:51
-msgid "Verbose Name"
-msgstr "نام"
-
-#: models.py:57
-msgid "Help Text"
-msgstr "متن راهنما"
-
-#: models.py:94
-msgid "Global preference"
-msgstr "تنطیمات عمومی"
-
-#: models.py:95
-msgid "Global preferences"
-msgstr "تنطیمات عمومی"
-
-#: templates/dynamic_preferences/form.html:11
-msgid "Submit"
-msgstr "ثبت"
-
-#: users/apps.py:11
-msgid "Preferences - Users"
-msgstr ""
-
-#: users/models.py:14
-msgid "user preference"
-msgstr ""
-
-#: users/models.py:15
-msgid "user preferences"
-msgstr ""
-=======
#: .\dynamic_preferences\admin.py:59
#, fuzzy
#| msgid "Verbose Name"
@@ -145,4 +88,3 @@ msgstr ""
#~ msgid "Dynamic Preferences"
#~ msgstr "تنظیمات"
->>>>>>> develop
diff --git a/utils/__init__.py b/utils/__init__.py
index dc91cb3..4ad546c 100644
--- a/utils/__init__.py
+++ b/utils/__init__.py
@@ -5,14 +5,11 @@ import mimetypes
import re
from urllib.parse import urlparse
-<<<<<<< HEAD
-=======
from django.core.files.storage import default_storage
from django.core.files.base import ContentFile
from pathlib import Path
from django.utils.text import get_valid_filename
->>>>>>> develop
from django.conf import settings
from django.core.files import File
from django.http import HttpRequest
@@ -27,9 +24,6 @@ from django.utils.text import slugify
import random
import string
-<<<<<<< HEAD
-
-=======
from django.conf import settings
from django.utils.translation import gettext_lazy as _
@@ -84,7 +78,6 @@ def environment_callback(request):
return [_("Development"), "primary"]
return [_("Production"), "primary"]
->>>>>>> develop
@@ -170,8 +163,6 @@ def generate_slug_for_model(model, value: str, recycled_count: int = 0):
return slug[:50]
-<<<<<<< HEAD
-=======
def generate_language_slugs(translations):
"""
@@ -196,7 +187,6 @@ def generate_language_slugs(translations):
print(f"Error generating slugs: {e}")
return []
->>>>>>> develop
def absolute_url(req, url):
"""
can either be a file instance or a URL string
@@ -213,8 +203,6 @@ def sizeof_fmt(num, suffix="B"):
num /= 1024.0
return f"{num:.1f} Yi{suffix}"
-<<<<<<< HEAD
-=======
def file_location_media(path: str):
"""
Resolve a media URL/relative path to absolute filesystem path under MEDIA_ROOT.
@@ -235,7 +223,6 @@ def file_location_media(path: str):
return os.path.join(media_root, path)
->>>>>>> develop
def file_location(path):
from django.conf import settings
@@ -291,16 +278,6 @@ class FileFieldSerializer(serializers.CharField):
# value not changed and here we simply return old file path
return self.get_rpath(data)
-<<<<<<< HEAD
- if data.startswith('http'):
- data = self.get_rpath(data)
-
- fpath = file_location(data)
- if not os.path.exists(fpath):
- raise serializers.ValidationError(f"File: '{fpath}' Does not exist")
-
- return File(open(fpath, 'rb'), os.path.basename(data))
-=======
# if data.startswith('http'):
# data = self.get_rpath(data)
@@ -463,7 +440,6 @@ class UploadChatMediaSerializer(serializers.Serializer):
def validate(self, attrs):
file_details = self.store_file(attrs['file'])
return file_details
->>>>>>> develop
class UploadTmpSerializer(serializers.Serializer):
@@ -472,10 +448,7 @@ class UploadTmpSerializer(serializers.Serializer):
name = serializers.CharField(read_only=True)
size = serializers.CharField(read_only=True)
mime_type = serializers.CharField(read_only=True)
-<<<<<<< HEAD
-=======
thumbnail_url = serializers.URLField(read_only=True, required=False)
->>>>>>> develop
def to_representation(self, instance):
data = super(UploadTmpSerializer, self).to_representation(instance)
@@ -484,23 +457,6 @@ class UploadTmpSerializer(serializers.Serializer):
def store_file(self, file):
from django.conf import settings
-<<<<<<< HEAD
- static_path = settings.STATIC_ROOT
-
- os.makedirs(f'{static_path}/tmp', exist_ok=True)
- fpath = f"/tmp/{secrets.token_urlsafe(4)}-{file.name}"
- shutil.move(file.temporary_file_path(), static_path + fpath)
- os.chmod(static_path + fpath, 0o644)
-
- return {
- 'file': fpath,
- 'url': absolute_url(self.context['request'], f"/static{fpath}"),
- 'name': file.name,
- 'size': sizeof_fmt(file.size),
- 'mime_type': guess_file_type(fpath)
- }
-
-=======
from utils.image_utils import (
create_thumbnail,
is_image_file,
@@ -569,17 +525,11 @@ class UploadTmpSerializer(serializers.Serializer):
return result
->>>>>>> develop
def validate(self, attrs):
file_details = self.store_file(attrs['file'])
return file_details
-<<<<<<< HEAD
-class UploadTmpMedia(GenericAPIView):
- """
- Files will remove every 1 hour
-=======
class UploadChatMedia(GenericAPIView):
"""
Upload files permanently to /media/chat/
@@ -601,7 +551,6 @@ class UploadTmpMedia(GenericAPIView):
"""
Upload files temporarily to /static/tmp/
Files will be removed every 1 hour
->>>>>>> develop
"""
parser_classes = (FormParser, MultiPartParser)
serializer_class = UploadTmpSerializer
@@ -613,8 +562,6 @@ class UploadTmpMedia(GenericAPIView):
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
return Response(serializer.data)
-<<<<<<< HEAD
-=======
# Configure filer admin after Django is fully loaded
def configure_filer_admin():
@@ -628,4 +575,3 @@ def configure_filer_admin():
pass
# This will be executed when this module is imported after Django is fully loaded
->>>>>>> develop
diff --git a/utils/json_editor_field.py b/utils/json_editor_field.py
index 5242f90..b907505 100644
--- a/utils/json_editor_field.py
+++ b/utils/json_editor_field.py
@@ -1,13 +1,4 @@
import json
-<<<<<<< HEAD
-
-from django import forms
-from django.db import models
-
-
-class JsonEditorWidget(forms.Textarea):
- template_name = 'fields/json_editor_field.html'
-=======
from typing import Any, Optional
from django import forms
@@ -71,7 +62,6 @@ class JsonEditorWidget(Widget):
attrs['title'] = name.replace('_', ' ').title()
return super().render(name, value, attrs, renderer)
->>>>>>> develop
class JsonEditorField(models.JSONField):
diff --git a/utils/redis.py b/utils/redis.py
index d34a636..7295973 100644
--- a/utils/redis.py
+++ b/utils/redis.py
@@ -1,10 +1,3 @@
-<<<<<<< HEAD
-import random
-from datetime import datetime, timedelta
-
-from redis.exceptions import RedisError
-
-=======
import json
import hashlib
import random
@@ -17,7 +10,6 @@ from redis.exceptions import RedisError
from django.conf import settings
->>>>>>> develop
from config.redis_config import RedisConfig
from utils.exceptions import ServiceUnavailableException, NotFoundException
@@ -82,9 +74,6 @@ class RedisManager(RedisConfig):
@staticmethod
def generate_otp_code() -> int:
random_code = random.randint(10000, 99999)
-<<<<<<< HEAD
- return random_code
-=======
return random_code
@@ -130,4 +119,3 @@ class OnlineClassTokenManager(RedisConfig):
query_params["token"] = token
new_query = urlencode(query_params)
return urlunparse(parsed._replace(query=new_query))
->>>>>>> develop
diff --git a/utils/schema.py b/utils/schema.py
index e4ba409..4183b56 100644
--- a/utils/schema.py
+++ b/utils/schema.py
@@ -36,10 +36,6 @@ def get_weekly_timing_schema():
}
-<<<<<<< HEAD
-
-=======
->>>>>>> develop
def get_course_feature_schema():
return {
'type': "array",
@@ -53,8 +49,6 @@ def get_course_feature_schema():
}
}
}
-<<<<<<< HEAD
-=======
def get_calender_dates_schema():
@@ -72,4 +66,3 @@ def get_calender_dates_schema():
}
}
}
->>>>>>> develop
diff --git a/utils/validators.py b/utils/validators.py
index ac831f8..62cf2e0 100644
--- a/utils/validators.py
+++ b/utils/validators.py
@@ -21,10 +21,7 @@ def validate_possible_number(phone, country=None):
return phone_number
def validate_type_code(value):
-<<<<<<< HEAD
-=======
from rest_framework import serializers
->>>>>>> develop
if not value.isdigit():
raise serializers.ValidationError('کد باید شامل اعداد باشد.')
if len(value) != 5: