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)