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, HttpResponseRedirect 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, translate_url from django.db.models import Q from django.contrib.auth.models import User from urllib.parse import urlparse from .models import Classroom, Subject, Teacher, Student, City, Resource, Package, Task, Message from .forms import StudentRegistrationForm, TeacherProfileForm, ResourceForm, StudentProfileForm, TaskForm, MessageForm 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 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}) @login_required def edit_student_profile(request): try: student = request.user.student_profile except Student.DoesNotExist: return redirect('profile') if request.method == 'POST': form = StudentProfileForm(request.POST, request.FILES, instance=student) if form.is_valid(): form.save() return redirect('profile') else: form = StudentProfileForm(instance=student) return render(request, 'core/edit_student_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) # --- Tasks Views --- @login_required def task_list(request): user = request.user if hasattr(user, 'teacher_profile'): tasks = Task.objects.filter(teacher=user.teacher_profile) is_teacher = True elif hasattr(user, 'student_profile'): tasks = Task.objects.filter(subject__in=user.student_profile.subscribed_subjects.all()) is_teacher = False else: tasks = Task.objects.none() is_teacher = False return render(request, 'core/tasks/task_list.html', {'tasks': tasks, 'is_teacher': is_teacher}) @login_required def create_task(request): try: teacher = request.user.teacher_profile except Teacher.DoesNotExist: raise PermissionDenied("يجب أن تكون معلماً لإضافة واجب.") if request.method == 'POST': form = TaskForm(request.POST, request.FILES, teacher=teacher) if form.is_valid(): task = form.save(commit=False) task.teacher = teacher task.save() return redirect('task_list') else: form = TaskForm(teacher=teacher) return render(request, 'core/tasks/create_task.html', {'form': form}) @login_required def task_detail(request, pk): task = get_object_or_404(Task, pk=pk) # Check access has_access = False if hasattr(request.user, 'teacher_profile') and task.teacher == request.user.teacher_profile: has_access = True elif hasattr(request.user, 'student_profile') and task.subject in request.user.student_profile.subscribed_subjects.all(): has_access = True if not has_access: raise PermissionDenied("ليس لديك صلاحية لعرض هذا الواجب.") return render(request, 'core/tasks/task_detail.html', {'task': task}) # --- Messages Views --- @login_required def inbox(request): messages = Message.objects.filter(recipient=request.user) return render(request, 'core/messages/inbox.html', {'messages': messages}) @login_required def outbox(request): messages = Message.objects.filter(sender=request.user) return render(request, 'core/messages/outbox.html', {'messages': messages}) @login_required def send_message(request): if request.method == 'POST': form = MessageForm(request.POST, user=request.user) if form.is_valid(): recipient_id = form.cleaned_data['recipient_id'] subject = form.cleaned_data['subject'] body = form.cleaned_data['body'] if recipient_id == 'all' and hasattr(request.user, 'teacher_profile'): # Send to all students students = Student.objects.filter( subscribed_subjects__teachers=request.user.teacher_profile ).distinct() messages_to_create = [] for student in students: messages_to_create.append(Message( sender=request.user, recipient=student.user, subject=subject, body=body )) Message.objects.bulk_create(messages_to_create) else: # Send to single user recipient = get_object_or_404(User, pk=recipient_id) Message.objects.create( sender=request.user, recipient=recipient, subject=subject, body=body ) return redirect('inbox') else: form = MessageForm(user=request.user) return render(request, 'core/messages/send_message.html', {'form': form}) @login_required def message_detail(request, pk): message = get_object_or_404(Message, pk=pk) if message.recipient != request.user and message.sender != request.user: raise PermissionDenied("ليس لديك صلاحية لعرض هذه الرسالة.") if message.recipient == request.user and not message.is_read: message.is_read = True message.save() return render(request, 'core/messages/message_detail.html', {'message': message}) def switch_language(request): current_language = translation.get_language() print(f"SWITCH_LANG: Current: {current_language}") if current_language == 'en': new_language = 'ar' else: new_language = 'en' print(f"SWITCH_LANG: New: {new_language}") translation.activate(new_language) request.session['_language'] = new_language request.session.save() # Explicit save referer = request.META.get('HTTP_REFERER') print(f"SWITCH_LANG: Referer: {referer}") response = None if referer: try: parsed = urlparse(referer) new_url = translate_url(parsed.path, new_language) print(f"SWITCH_LANG: Translated URL: {new_url}") if new_url and new_url != parsed.path: response = HttpResponseRedirect(new_url) except Exception as e: print(f"SWITCH_LANG: Error translating URL: {e}") pass if not response: # Fallback to admin with prefix fallback_url = f"/{new_language}/admin/" print(f"SWITCH_LANG: Fallback to {fallback_url}") response = HttpResponseRedirect(fallback_url) # Also set the cookie to be sure response.set_cookie(settings.LANGUAGE_COOKIE_NAME, new_language) return response