449 lines
17 KiB
Python
449 lines
17 KiB
Python
from django.core.exceptions import PermissionDenied
|
|
from django.shortcuts import render, redirect, get_object_or_404
|
|
from django.utils import translation
|
|
from django.conf import settings
|
|
from django.http import JsonResponse, FileResponse, Http404
|
|
from django.contrib.admin.views.decorators import staff_member_required
|
|
from django.contrib.auth.decorators import login_required
|
|
from django.contrib.auth import logout, login
|
|
from django.urls import reverse
|
|
from django.db.models import Q
|
|
from .models import Classroom, Subject, Teacher, Student, City, Resource, Package
|
|
from .forms import StudentRegistrationForm, TeacherProfileForm, ResourceForm
|
|
from .wablas import send_whatsapp_message
|
|
from .thawani import ThawaniClient
|
|
import random
|
|
import string
|
|
import mimetypes
|
|
import os
|
|
|
|
def index(request):
|
|
levels = Classroom.objects.prefetch_related('subjects').all()
|
|
teachers = Teacher.objects.select_related('user').all()
|
|
context = {'levels': levels, 'teachers': teachers}
|
|
return render(request, 'core/index.html', context)
|
|
|
|
def set_language(request, lang_code):
|
|
next_url = request.GET.get('next', '/')
|
|
response = redirect(next_url)
|
|
if lang_code in [lang[0] for lang in settings.LANGUAGES]:
|
|
translation.activate(lang_code)
|
|
if hasattr(request, 'session'):
|
|
request.session['_language'] = lang_code
|
|
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, lang_code)
|
|
return response
|
|
|
|
def subject_detail(request, pk):
|
|
subject = get_object_or_404(Subject, pk=pk)
|
|
is_teacher = False
|
|
if request.user.is_authenticated and hasattr(request.user, 'teacher_profile'):
|
|
if request.user.teacher_profile in subject.teachers.all():
|
|
is_teacher = True
|
|
return render(request, 'core/subject_detail.html', {'subject': subject, 'is_teacher': is_teacher})
|
|
|
|
@staff_member_required
|
|
def get_subjects_by_level(request):
|
|
level_id = request.GET.get('level_id') or request.GET.get('classroom_id')
|
|
if not level_id:
|
|
return JsonResponse([], safe=False)
|
|
subjects = Subject.objects.filter(classroom_id=level_id).values('id', 'name_en', 'name_ar')
|
|
return JsonResponse(list(subjects), safe=False)
|
|
|
|
def get_classroom_subjects(request):
|
|
classroom_id = request.GET.get('classroom_id')
|
|
if not classroom_id:
|
|
return JsonResponse([], safe=False)
|
|
subjects = Subject.objects.filter(classroom_id=classroom_id).values('id', 'name_en', 'name_ar', 'price')
|
|
return JsonResponse(list(subjects), safe=False)
|
|
|
|
def get_cities_by_governorate(request):
|
|
governorate_id = request.GET.get('governorate_id')
|
|
if not governorate_id:
|
|
return JsonResponse([], safe=False)
|
|
cities = City.objects.filter(governorate_id=governorate_id).values('id', 'name_en', 'name_ar')
|
|
return JsonResponse(list(cities), safe=False)
|
|
|
|
def generate_otp():
|
|
return ''.join(random.choices(string.digits, k=6))
|
|
|
|
def register_student(request):
|
|
if request.method == 'POST':
|
|
form = StudentRegistrationForm(request.POST, request.FILES)
|
|
if form.is_valid():
|
|
student = form.save()
|
|
|
|
# Generate OTPs
|
|
mobile_otp = generate_otp()
|
|
email_otp = generate_otp()
|
|
|
|
student.mobile_otp_code = mobile_otp
|
|
student.email_otp_code = email_otp
|
|
student.save()
|
|
|
|
# Send OTP via WhatsApp
|
|
if student.mobile_number:
|
|
# Attempt to send WhatsApp message
|
|
# Note: This will only work if Wablas is configured in Admin
|
|
send_whatsapp_message(
|
|
student.mobile_number,
|
|
f"رمز التحقق الخاص بك هو: {mobile_otp}"
|
|
)
|
|
|
|
# Simulate sending OTPs (Log to console)
|
|
print(f"========================================")
|
|
print(f"SIMULATED OTP SENDING:")
|
|
print(f"User: {student.user.username}")
|
|
print(f"Mobile OTP for {student.mobile_number}: {mobile_otp}")
|
|
print(f"Email OTP for {student.user.email}: {email_otp}")
|
|
print(f"========================================")
|
|
|
|
# Log the user in
|
|
login(request, student.user)
|
|
|
|
# Redirect to verification page
|
|
return redirect('verify_otp')
|
|
else:
|
|
form = StudentRegistrationForm()
|
|
|
|
return render(request, 'core/registration.html', {'form': form})
|
|
|
|
@login_required
|
|
def verify_otp(request):
|
|
try:
|
|
student = request.user.student_profile
|
|
except Student.DoesNotExist:
|
|
return redirect('index')
|
|
|
|
if student.is_mobile_verified and student.is_email_verified:
|
|
return redirect('profile')
|
|
|
|
error = None
|
|
if request.method == 'POST':
|
|
entered_mobile_otp = request.POST.get('mobile_otp')
|
|
entered_email_otp = request.POST.get('email_otp')
|
|
|
|
mobile_ok = student.is_mobile_verified
|
|
email_ok = student.is_email_verified
|
|
|
|
if not mobile_ok:
|
|
if entered_mobile_otp == student.mobile_otp_code:
|
|
student.is_mobile_verified = True
|
|
mobile_ok = True
|
|
else:
|
|
error = "رمز الهاتف غير صحيح"
|
|
|
|
if not email_ok and (error is None or "الهاتف" not in error):
|
|
if entered_email_otp == student.email_otp_code:
|
|
student.is_email_verified = True
|
|
email_ok = True
|
|
else:
|
|
error = "رمز البريد غير صحيح"
|
|
|
|
if mobile_ok and email_ok:
|
|
student.mobile_otp_code = "" # Clear codes
|
|
student.email_otp_code = ""
|
|
student.save()
|
|
return redirect('profile')
|
|
else:
|
|
student.save() # Save partial verification if any
|
|
|
|
return render(request, 'core/verify_otp.html', {'error': error, 'student': student})
|
|
|
|
@login_required
|
|
def profile(request):
|
|
user = request.user
|
|
|
|
# Check for Teacher
|
|
if hasattr(user, 'teacher_profile'):
|
|
teacher_profile = user.teacher_profile
|
|
subjects = teacher_profile.subjects.all()
|
|
return render(request, 'core/teacher_dashboard.html', {
|
|
'teacher_profile': teacher_profile,
|
|
'subjects': subjects
|
|
})
|
|
|
|
# Check for Student
|
|
elif hasattr(user, 'student_profile'):
|
|
student_profile = user.student_profile
|
|
subscribed_subjects = student_profile.subscribed_subjects.all()
|
|
|
|
# Get available subjects (in same classroom, not yet subscribed)
|
|
available_subjects = []
|
|
if student_profile.classroom:
|
|
available_subjects = Subject.objects.filter(
|
|
classroom=student_profile.classroom
|
|
).exclude(
|
|
id__in=subscribed_subjects.values_list('id', flat=True)
|
|
)
|
|
|
|
# Get active packages
|
|
# Filter by student's classroom or global packages (classroom is None)
|
|
packages = Package.objects.filter(is_active=True)
|
|
if student_profile.classroom:
|
|
packages = packages.filter(Q(classroom=student_profile.classroom) | Q(classroom__isnull=True))
|
|
else:
|
|
# If student has no classroom, show only global packages
|
|
packages = packages.filter(classroom__isnull=True)
|
|
|
|
return render(request, 'core/student_dashboard.html', {
|
|
'student_profile': student_profile,
|
|
'subscribed_subjects': subscribed_subjects,
|
|
'available_subjects': available_subjects,
|
|
'packages': packages
|
|
})
|
|
|
|
# Fallback (Superuser or Admin without profile)
|
|
else:
|
|
student_profile = None
|
|
return render(request, 'core/profile.html', {'student_profile': student_profile})
|
|
|
|
@login_required
|
|
def edit_teacher_profile(request):
|
|
try:
|
|
teacher = request.user.teacher_profile
|
|
except Teacher.DoesNotExist:
|
|
return redirect('profile')
|
|
|
|
if request.method == 'POST':
|
|
form = TeacherProfileForm(request.POST, request.FILES, instance=teacher)
|
|
if form.is_valid():
|
|
form.save()
|
|
return redirect('profile')
|
|
else:
|
|
form = TeacherProfileForm(instance=teacher)
|
|
|
|
return render(request, 'core/edit_teacher_profile.html', {'form': form})
|
|
|
|
def custom_logout(request):
|
|
logout(request)
|
|
return redirect('index')
|
|
|
|
@login_required
|
|
def subscribe_subject(request, subject_id):
|
|
try:
|
|
student = request.user.student_profile
|
|
except Student.DoesNotExist:
|
|
return redirect('index')
|
|
|
|
subject = get_object_or_404(Subject, pk=subject_id)
|
|
|
|
# Check if already subscribed
|
|
if subject in student.subscribed_subjects.all():
|
|
return redirect('profile')
|
|
|
|
try:
|
|
thawani = ThawaniClient()
|
|
success_url = request.build_absolute_uri(reverse('payment_success')) + '?session_id={session_id}'
|
|
cancel_url = request.build_absolute_uri(reverse('payment_cancel'))
|
|
|
|
session = thawani.create_checkout_session(subject, request.user, success_url, cancel_url)
|
|
|
|
session_id = session.get('data', {}).get('session_id')
|
|
if not session_id:
|
|
return render(request, 'core/error.html', {'message': 'تعذر إنشاء جلسة الدفع.'})
|
|
|
|
return redirect(f"{thawani.checkout_base_url}/{session_id}")
|
|
|
|
except Exception as e:
|
|
print(f"Payment Error: {e}")
|
|
return render(request, 'core/error.html', {'message': f'فشل بدء عملية الدفع: {str(e)}'})
|
|
|
|
@login_required
|
|
def subscribe_package(request, package_id):
|
|
try:
|
|
student = request.user.student_profile
|
|
except Student.DoesNotExist:
|
|
return redirect('index')
|
|
|
|
package = get_object_or_404(Package, pk=package_id)
|
|
|
|
try:
|
|
thawani = ThawaniClient()
|
|
success_url = request.build_absolute_uri(reverse('payment_success')) + '?session_id={session_id}'
|
|
cancel_url = request.build_absolute_uri(reverse('payment_cancel'))
|
|
|
|
session = thawani.create_checkout_session(package, request.user, success_url, cancel_url)
|
|
|
|
session_id = session.get('data', {}).get('session_id')
|
|
if not session_id:
|
|
return render(request, 'core/error.html', {'message': 'تعذر إنشاء جلسة الدفع.'})
|
|
|
|
return redirect(f"{thawani.checkout_base_url}/{session_id}")
|
|
|
|
except Exception as e:
|
|
print(f"Payment Error: {e}")
|
|
return render(request, 'core/error.html', {'message': f'فشل بدء عملية الدفع: {str(e)}'})
|
|
|
|
@login_required
|
|
def payment_success(request):
|
|
session_id = request.GET.get('session_id')
|
|
if not session_id:
|
|
return redirect('profile')
|
|
|
|
try:
|
|
thawani = ThawaniClient()
|
|
response = thawani.get_checkout_session(session_id)
|
|
data = response.get('data', {})
|
|
payment_status = data.get('payment_status')
|
|
metadata = data.get('metadata', {})
|
|
subject_id = metadata.get('subject_id')
|
|
package_id = metadata.get('package_id')
|
|
|
|
if payment_status == 'paid':
|
|
student = request.user.student_profile
|
|
|
|
if subject_id:
|
|
try:
|
|
subject = get_object_or_404(Subject, pk=subject_id)
|
|
student.subscribed_subjects.add(subject)
|
|
except Exception:
|
|
pass
|
|
|
|
if package_id:
|
|
try:
|
|
package = get_object_or_404(Package, pk=package_id)
|
|
for subject in package.subjects.all():
|
|
student.subscribed_subjects.add(subject)
|
|
except Exception:
|
|
pass
|
|
|
|
return redirect('profile')
|
|
else:
|
|
return render(request, 'core/error.html', {'message': f'لم تتم عملية الدفع بنجاح. الحالة: {payment_status}'})
|
|
|
|
except Exception as e:
|
|
print(f"Payment Verification Error: {e}")
|
|
return render(request, 'core/error.html', {'message': f'فشل التحقق من الدفع: {str(e)}'})
|
|
|
|
@login_required
|
|
def payment_cancel(request):
|
|
return redirect('profile')
|
|
|
|
@login_required
|
|
def live_classroom(request, subject_id):
|
|
subject = get_object_or_404(Subject, pk=subject_id)
|
|
|
|
is_teacher = False
|
|
is_student = False
|
|
|
|
if hasattr(request.user, 'teacher_profile') and request.user.teacher_profile in subject.teachers.all():
|
|
is_teacher = True
|
|
|
|
if hasattr(request.user, 'student_profile') and request.user.student_profile.subscribed_subjects.filter(pk=subject_id).exists():
|
|
is_student = True
|
|
|
|
if not (is_teacher or is_student):
|
|
raise PermissionDenied("ليس لديك صلاحية الانضمام لهذا الفصل.")
|
|
|
|
# Generate Room Name
|
|
# We use a consistent room name based on Subject ID
|
|
room_name = f"EduPlatform_Live_Subject_{subject.id}"
|
|
|
|
return render(request, 'core/live_classroom.html', {
|
|
'subject': subject,
|
|
'room_name': room_name,
|
|
'user_display_name': request.user.get_full_name() or request.user.username,
|
|
'is_teacher': is_teacher
|
|
})
|
|
|
|
@login_required
|
|
def add_resource(request, subject_id):
|
|
try:
|
|
teacher = request.user.teacher_profile
|
|
except Teacher.DoesNotExist:
|
|
raise PermissionDenied("يجب أن تكون معلماً للوصول إلى هذه الصفحة.")
|
|
|
|
subject = get_object_or_404(Subject, pk=subject_id)
|
|
|
|
if teacher not in subject.teachers.all():
|
|
raise PermissionDenied("ليس لديك صلاحية لإضافة مصادر لهذه المادة.")
|
|
|
|
if request.method == 'POST':
|
|
form = ResourceForm(request.POST, request.FILES)
|
|
if form.is_valid():
|
|
resource = form.save(commit=False)
|
|
resource.subject = subject
|
|
resource.save()
|
|
return redirect('subject_detail', pk=subject.id)
|
|
else:
|
|
form = ResourceForm()
|
|
|
|
return render(request, 'core/add_resource.html', {'form': form, 'subject': subject})
|
|
|
|
@login_required
|
|
def edit_resource(request, resource_id):
|
|
resource = get_object_or_404(Resource, pk=resource_id)
|
|
try:
|
|
teacher = request.user.teacher_profile
|
|
except Teacher.DoesNotExist:
|
|
raise PermissionDenied("يجب أن تكون معلماً للوصول إلى هذه الصفحة.")
|
|
|
|
if teacher not in resource.subject.teachers.all():
|
|
raise PermissionDenied("ليس لديك صلاحية لتعديل هذا المصدر.")
|
|
|
|
if request.method == 'POST':
|
|
form = ResourceForm(request.POST, request.FILES, instance=resource)
|
|
if form.is_valid():
|
|
form.save()
|
|
return redirect('subject_detail', pk=resource.subject.id)
|
|
else:
|
|
form = ResourceForm(instance=resource)
|
|
|
|
return render(request, 'core/edit_resource.html', {'form': form, 'resource': resource, 'subject': resource.subject})
|
|
|
|
@login_required
|
|
def delete_resource(request, resource_id):
|
|
resource = get_object_or_404(Resource, pk=resource_id)
|
|
try:
|
|
teacher = request.user.teacher_profile
|
|
except Teacher.DoesNotExist:
|
|
raise PermissionDenied("يجب أن تكون معلماً للوصول إلى هذه الصفحة.")
|
|
|
|
if teacher not in resource.subject.teachers.all():
|
|
raise PermissionDenied("ليس لديك صلاحية لحذف هذا المصدر.")
|
|
|
|
subject_id = resource.subject.id
|
|
|
|
if request.method == 'POST':
|
|
resource.delete()
|
|
return redirect('subject_detail', pk=subject_id)
|
|
|
|
return render(request, 'core/delete_resource.html', {'resource': resource})
|
|
|
|
@login_required
|
|
def view_resource(request, resource_id):
|
|
resource = get_object_or_404(Resource, pk=resource_id)
|
|
|
|
has_access = False
|
|
|
|
# Check Teacher Access
|
|
if hasattr(request.user, 'teacher_profile') and request.user.teacher_profile in resource.subject.teachers.all():
|
|
has_access = True
|
|
|
|
# Check Student Access
|
|
elif hasattr(request.user, 'student_profile') and resource.subject in request.user.student_profile.subscribed_subjects.all():
|
|
has_access = True
|
|
|
|
if not has_access:
|
|
raise PermissionDenied("ليس لديك صلاحية لعرض هذا المصدر.")
|
|
|
|
if resource.resource_type == 'FILE' and resource.file:
|
|
try:
|
|
# Open file
|
|
file_handle = resource.file.open('rb')
|
|
response = FileResponse(file_handle)
|
|
|
|
# Check for PDF to force inline display
|
|
filename = resource.file.name
|
|
if filename.lower().endswith('.pdf'):
|
|
response['Content-Type'] = 'application/pdf'
|
|
response['Content-Disposition'] = f'inline; filename="{os.path.basename(filename)}"'
|
|
|
|
return response
|
|
except FileNotFoundError:
|
|
raise Http404("الملف غير موجود على الخادم.")
|
|
|
|
elif resource.resource_type in ['LINK', 'VIDEO']:
|
|
return redirect(resource.link)
|
|
|
|
return redirect('subject_detail', pk=resource.subject.id) |