621 lines
23 KiB
Python
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
|