38146-vm/core/views.py
2026-02-05 04:44:00 +00:00

621 lines
23 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, 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