diff --git a/config/__pycache__/settings.cpython-311.pyc b/config/__pycache__/settings.cpython-311.pyc
index e67bd62..13b8aa3 100644
Binary files a/config/__pycache__/settings.cpython-311.pyc and b/config/__pycache__/settings.cpython-311.pyc differ
diff --git a/config/__pycache__/urls.cpython-311.pyc b/config/__pycache__/urls.cpython-311.pyc
index f58ff9d..04c6ce4 100644
Binary files a/config/__pycache__/urls.cpython-311.pyc and b/config/__pycache__/urls.cpython-311.pyc differ
diff --git a/config/settings.py b/config/settings.py
index 028cede..566e825 100644
--- a/config/settings.py
+++ b/config/settings.py
@@ -207,6 +207,9 @@ LOGOUT_REDIRECT_URL = 'index'
JAZZMIN_SETTINGS = {
# title of the window (Will default to current_admin_site.site_title if absent or None)
"site_title": "School Admin",
+
+ # Enable built-in language chooser
+ "language_chooser": True,
# Title on the login screen (19 chars max) (defaults to current_admin_site.site_header if absent or None)
"site_header": "School",
@@ -303,7 +306,7 @@ JAZZMIN_SETTINGS = {
# UI Tweaks #
#############
# Relative paths to custom CSS/JS scripts (must be present in static files)
- "custom_css": None,
+ "custom_css": "css/admin_custom.css",
"custom_js": None,
# Whether to link font from fonts.googleapis.com (use custom_css to supply font otherwise)
"use_google_fonts_cdn": True,
diff --git a/config/urls.py b/config/urls.py
index c0f2fb9..d522306 100644
--- a/config/urls.py
+++ b/config/urls.py
@@ -2,13 +2,20 @@ from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
+from django.conf.urls.i18n import i18n_patterns
urlpatterns = [
- path('admin/', admin.site.urls),
path('i18n/', include('django.conf.urls.i18n')),
+]
+
+urlpatterns += i18n_patterns(
+ path('admin/', admin.site.urls),
+)
+
+urlpatterns += [
path('', include('core.urls')),
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
- urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
+ urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
\ No newline at end of file
diff --git a/core/__pycache__/forms.cpython-311.pyc b/core/__pycache__/forms.cpython-311.pyc
index 0327d5e..00b8207 100644
Binary files a/core/__pycache__/forms.cpython-311.pyc and b/core/__pycache__/forms.cpython-311.pyc differ
diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc
index 3512086..d560538 100644
Binary files a/core/__pycache__/models.cpython-311.pyc and b/core/__pycache__/models.cpython-311.pyc differ
diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc
index 91670a3..42cda6c 100644
Binary files a/core/__pycache__/urls.cpython-311.pyc and b/core/__pycache__/urls.cpython-311.pyc differ
diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc
index e1e32a7..4894621 100644
Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ
diff --git a/core/forms.py b/core/forms.py
index 4b0fc9f..877bd46 100644
--- a/core/forms.py
+++ b/core/forms.py
@@ -1,6 +1,6 @@
from django import forms
from django.contrib.auth.models import User
-from .models import Student, Subject, Classroom, City, Governorate, Teacher, Resource
+from .models import Student, Subject, Classroom, City, Governorate, Teacher, Resource, Task, Message
class StudentRegistrationForm(forms.ModelForm):
full_name = forms.CharField(max_length=150, required=True, label="الاسم الكامل")
@@ -99,6 +99,54 @@ class StudentRegistrationForm(forms.ModelForm):
student.subscribed_subjects.set(self.cleaned_data['subjects'])
return student
+class StudentProfileForm(forms.ModelForm):
+ first_name = forms.CharField(max_length=30, required=True, label="الاسم الأول")
+ last_name = forms.CharField(max_length=150, required=True, label="الاسم الأخير")
+ email = forms.EmailField(required=True, label="البريد الإلكتروني")
+
+ class Meta:
+ model = Student
+ fields = ['mobile_number', 'governorate', 'city', 'classroom', 'avatar']
+ labels = {
+ 'mobile_number': 'رقم الهاتف',
+ 'governorate': 'المحافظة',
+ 'city': 'المدينة',
+ 'classroom': 'الصف الدراسي',
+ 'avatar': 'الصورة الشخصية'
+ }
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ if self.instance and self.instance.user:
+ self.fields['first_name'].initial = self.instance.user.first_name
+ self.fields['last_name'].initial = self.instance.user.last_name
+ self.fields['email'].initial = self.instance.user.email
+
+ for field_name, field in self.fields.items():
+ field.widget.attrs['class'] = 'form-control'
+
+ # Optimize city loading (dependent dropdown logic)
+ self.fields['city'].queryset = City.objects.none()
+ if 'governorate' in self.data:
+ try:
+ governorate_id = int(self.data.get('governorate'))
+ self.fields['city'].queryset = City.objects.filter(governorate_id=governorate_id)
+ except (ValueError, TypeError):
+ pass
+ elif self.instance.pk and self.instance.governorate:
+ self.fields['city'].queryset = City.objects.filter(governorate=self.instance.governorate)
+
+ def save(self, commit=True):
+ student = super().save(commit=False)
+ if commit:
+ user = student.user
+ user.first_name = self.cleaned_data['first_name']
+ user.last_name = self.cleaned_data['last_name']
+ user.email = self.cleaned_data['email']
+ user.save()
+ student.save()
+ return student
+
class TeacherProfileForm(forms.ModelForm):
first_name = forms.CharField(max_length=30, required=True, label="الاسم الأول")
last_name = forms.CharField(max_length=150, required=True, label="الاسم الأخير")
@@ -153,3 +201,58 @@ class ResourceForm(forms.ModelForm):
super().__init__(*args, **kwargs)
for field_name, field in self.fields.items():
field.widget.attrs['class'] = 'form-control'
+
+class TaskForm(forms.ModelForm):
+ class Meta:
+ model = Task
+ fields = ['title', 'description', 'subject', 'file', 'due_date']
+ widgets = {
+ 'due_date': forms.DateTimeInput(attrs={'type': 'datetime-local'}),
+ 'description': forms.Textarea(attrs={'rows': 4}),
+ }
+ labels = {
+ 'title': 'عنوان الواجب',
+ 'description': 'التفاصيل',
+ 'subject': 'المادة',
+ 'file': 'ملف مرفق (اختياري)',
+ 'due_date': 'آخر موعد للتسليم'
+ }
+
+ def __init__(self, *args, **kwargs):
+ teacher = kwargs.pop('teacher', None)
+ super().__init__(*args, **kwargs)
+ for field in self.fields.values():
+ field.widget.attrs['class'] = 'form-control'
+
+ if teacher:
+ self.fields['subject'].queryset = teacher.subjects.all()
+
+class MessageForm(forms.Form):
+ recipient_id = forms.ChoiceField(label="إلى", widget=forms.Select(attrs={'class': 'form-control'}))
+ subject = forms.CharField(label="الموضوع", max_length=255, widget=forms.TextInput(attrs={'class': 'form-control'}))
+ body = forms.CharField(label="نص الرسالة", widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 5}))
+
+ def __init__(self, *args, **kwargs):
+ user = kwargs.pop('user', None)
+ super().__init__(*args, **kwargs)
+
+ choices = []
+ if user:
+ if hasattr(user, 'teacher_profile'):
+ choices.append(('all', '📢 جميع طلابي'))
+ students = Student.objects.filter(
+ subscribed_subjects__teachers=user.teacher_profile
+ ).distinct()
+ for student in students:
+ name = student.user.get_full_name() or student.user.username
+ choices.append((str(student.user.id), f"👤 {name}"))
+
+ elif hasattr(user, 'student_profile'):
+ teachers = Teacher.objects.filter(
+ subjects__subscribers=user.student_profile
+ ).distinct()
+ for teacher in teachers:
+ name = teacher.user.get_full_name() or teacher.user.username
+ choices.append((str(teacher.user.id), f"👨🏫 {name}"))
+
+ self.fields['recipient_id'].choices = choices
\ No newline at end of file
diff --git a/core/migrations/0017_message_task.py b/core/migrations/0017_message_task.py
new file mode 100644
index 0000000..038819e
--- /dev/null
+++ b/core/migrations/0017_message_task.py
@@ -0,0 +1,51 @@
+# Generated by Django 5.2.7 on 2026-02-05 02:30
+
+import django.db.models.deletion
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('core', '0016_package_classroom'),
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Message',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('subject', models.CharField(max_length=255, verbose_name='الموضوع')),
+ ('body', models.TextField(verbose_name='نص الرسالة')),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('is_read', models.BooleanField(default=False, verbose_name='تمت القراءة')),
+ ('recipient', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='received_messages', to=settings.AUTH_USER_MODEL, verbose_name='المستقبل')),
+ ('sender', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sent_messages', to=settings.AUTH_USER_MODEL, verbose_name='المرسل')),
+ ],
+ options={
+ 'verbose_name': 'رسالة',
+ 'verbose_name_plural': 'الرسائل',
+ 'ordering': ['-created_at'],
+ },
+ ),
+ migrations.CreateModel(
+ name='Task',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('title', models.CharField(max_length=200, verbose_name='العنوان')),
+ ('description', models.TextField(verbose_name='الوصف')),
+ ('file', models.FileField(blank=True, null=True, upload_to='tasks/', verbose_name='ملف مرفق')),
+ ('due_date', models.DateTimeField(blank=True, null=True, verbose_name='تاريخ التسليم')),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('subject', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tasks', to='core.subject', verbose_name='المادة')),
+ ('teacher', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tasks', to='core.teacher', verbose_name='المعلم')),
+ ],
+ options={
+ 'verbose_name': 'واجب / مهمة',
+ 'verbose_name_plural': 'الواجبات / المهام',
+ 'ordering': ['-created_at'],
+ },
+ ),
+ ]
diff --git a/core/migrations/__pycache__/0017_message_task.cpython-311.pyc b/core/migrations/__pycache__/0017_message_task.cpython-311.pyc
new file mode 100644
index 0000000..2bc6af9
Binary files /dev/null and b/core/migrations/__pycache__/0017_message_task.cpython-311.pyc differ
diff --git a/core/models.py b/core/models.py
index 0bbdba6..bb6e8f4 100644
--- a/core/models.py
+++ b/core/models.py
@@ -184,6 +184,39 @@ class Student(models.Model):
def __str__(self):
return self.user.get_full_name() or self.user.username
+class Task(models.Model):
+ teacher = models.ForeignKey(Teacher, on_delete=models.CASCADE, related_name='tasks', verbose_name="المعلم")
+ subject = models.ForeignKey(Subject, on_delete=models.CASCADE, related_name='tasks', verbose_name="المادة")
+ title = models.CharField("العنوان", max_length=200)
+ description = models.TextField("الوصف")
+ file = models.FileField("ملف مرفق", upload_to='tasks/', blank=True, null=True)
+ due_date = models.DateTimeField("تاريخ التسليم", blank=True, null=True)
+ created_at = models.DateTimeField(auto_now_add=True)
+
+ class Meta:
+ verbose_name = "واجب / مهمة"
+ verbose_name_plural = "الواجبات / المهام"
+ ordering = ['-created_at']
+
+ def __str__(self):
+ return self.title
+
+class Message(models.Model):
+ sender = models.ForeignKey(User, on_delete=models.CASCADE, related_name='sent_messages', verbose_name="المرسل")
+ recipient = models.ForeignKey(User, on_delete=models.CASCADE, related_name='received_messages', verbose_name="المستقبل")
+ subject = models.CharField("الموضوع", max_length=255)
+ body = models.TextField("نص الرسالة")
+ created_at = models.DateTimeField(auto_now_add=True)
+ is_read = models.BooleanField("تمت القراءة", default=False)
+
+ class Meta:
+ verbose_name = "رسالة"
+ verbose_name_plural = "الرسائل"
+ ordering = ['-created_at']
+
+ def __str__(self):
+ return self.subject
+
# --- Configuration Models ---
class WablasConfiguration(SingletonModel):
diff --git a/core/templates/base.html b/core/templates/base.html
index e1a70db..8d9fd86 100644
--- a/core/templates/base.html
+++ b/core/templates/base.html
@@ -1,7 +1,8 @@
{% load i18n static %}
{% get_current_language as LANGUAGE_CODE %}
+{% get_current_language_bidi as LANGUAGE_BIDI %}
-
+
@@ -9,7 +10,7 @@
{% block title %}{% if LANGUAGE_CODE == 'ar' %}منصة التعليم الإلكتروني{% else %}EduPlatform{% endif %}{% endblock %}
-
+
@@ -138,7 +139,7 @@
{% if user.is_staff %}
- {% if LANGUAGE_CODE == 'ar' %}المسؤول{% else %}Admin{% endif %}
+ {% if LANGUAGE_CODE == 'ar' %}المسؤول{% else %}Admin{% endif %}
{% endif %}
@@ -177,8 +178,19 @@
{% if LANGUAGE_CODE == 'ar' %}الملف الشخصي{% else %}Profile{% endif %}
+
+
+
+ {% if LANGUAGE_CODE == 'ar' %}الواجبات{% else %}Tasks{% endif %}
+
+
+
+
+ {% if LANGUAGE_CODE == 'ar' %}البريد الوارد{% else %}Inbox{% endif %}
+
+
{% if user.is_staff %}
- {% if LANGUAGE_CODE == 'ar' %}لوحة تحكم المسؤول{% else %}Dashboard{% endif %}
+ {% if LANGUAGE_CODE == 'ar' %}لوحة تحكم المسؤول{% else %}Dashboard{% endif %}
{% endif %}
{% if LANGUAGE_CODE == 'ar' %}تسجيل الخروج{% else %}Logout{% endif %}
diff --git a/core/templates/core/edit_student_profile.html b/core/templates/core/edit_student_profile.html
new file mode 100644
index 0000000..894ed57
--- /dev/null
+++ b/core/templates/core/edit_student_profile.html
@@ -0,0 +1,95 @@
+{% extends 'base.html' %}
+{% load i18n static %}
+
+{% block title %}تعديل ملف الطالب - EduPlatform{% endblock %}
+
+{% block content %}
+
+
+
+
+
تعديل الملف الشخصي
+
+
+
+
+
+
+{% endblock %}
+
+{% block extra_js %}
+
+{% endblock %}
diff --git a/core/templates/core/messages/inbox.html b/core/templates/core/messages/inbox.html
new file mode 100644
index 0000000..a8d73f0
--- /dev/null
+++ b/core/templates/core/messages/inbox.html
@@ -0,0 +1,54 @@
+{% extends 'base.html' %}
+{% load i18n static %}
+
+{% block title %}البريد الوارد - EduPlatform{% endblock %}
+
+{% block content %}
+
+
+
+
البريد الوارد 📥
+
الرسائل الواردة من المعلمين أو الطلاب.
+
+
+
+
+
+
+ {% if messages %}
+
+ {% else %}
+
+
📭
+
صندوق الوارد فارغ.
+
لم تستلم أي رسائل جديدة بعد.
+
+ {% endif %}
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/core/templates/core/messages/message_detail.html b/core/templates/core/messages/message_detail.html
new file mode 100644
index 0000000..e88299a
--- /dev/null
+++ b/core/templates/core/messages/message_detail.html
@@ -0,0 +1,52 @@
+{% extends 'base.html' %}
+{% load i18n static %}
+
+{% block title %}{{ message.subject }} - EduPlatform{% endblock %}
+
+{% block content %}
+
+
+
+
+
+
+
+
+
{{ message.subject }}
+
+ من:
+ {{ message.sender.get_full_name|default:message.sender.username }}
+
+
+ إلى:
+ {{ message.recipient.get_full_name|default:message.recipient.username }}
+
+
+
+
{{ message.created_at|date:"Y-m-d" }}
+
{{ message.created_at|date:"H:i A" }}
+
+
+
+
{{ message.body }}
+
+
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/core/templates/core/messages/outbox.html b/core/templates/core/messages/outbox.html
new file mode 100644
index 0000000..31b2293
--- /dev/null
+++ b/core/templates/core/messages/outbox.html
@@ -0,0 +1,57 @@
+{% extends 'base.html' %}
+{% load i18n static %}
+
+{% block title %}البريد الصادر - EduPlatform{% endblock %}
+
+{% block content %}
+
+
+
+
البريد الصادر 📤
+
الرسائل التي قمت بإرسالها.
+
+
+
+
+
+
+ {% if messages %}
+
+
+ {% for message in messages %}
+
+
+
+ إلى:
+ {{ message.recipient.get_full_name|default:message.recipient.username }}
+
+ {{ message.created_at|date:"Y-m-d H:i" }}
+
+
{{ message.subject }}
+
{{ message.body|truncatechars:100 }}
+
+
+
+ {% endfor %}
+
+
+ {% else %}
+
+
📨
+
صندوق الصادر فارغ.
+
لم تقم بإرسال أي رسائل بعد.
+
+ {% endif %}
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/core/templates/core/messages/send_message.html b/core/templates/core/messages/send_message.html
new file mode 100644
index 0000000..cc79b38
--- /dev/null
+++ b/core/templates/core/messages/send_message.html
@@ -0,0 +1,43 @@
+{% extends 'base.html' %}
+{% load i18n static %}
+
+{% block title %}إرسال رسالة جديدة - EduPlatform{% endblock %}
+
+{% block content %}
+
+
+
+
+
إرسال رسالة جديدة ✉️
+
+
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/core/templates/core/student_dashboard.html b/core/templates/core/student_dashboard.html
index 89d6953..a36df05 100644
--- a/core/templates/core/student_dashboard.html
+++ b/core/templates/core/student_dashboard.html
@@ -64,7 +64,7 @@
@@ -102,6 +102,42 @@
+
+
+
موادي
diff --git a/core/templates/core/tasks/create_task.html b/core/templates/core/tasks/create_task.html
new file mode 100644
index 0000000..0331a6a
--- /dev/null
+++ b/core/templates/core/tasks/create_task.html
@@ -0,0 +1,38 @@
+{% extends 'base.html' %}
+{% load i18n static %}
+
+{% block title %}إضافة واجب جديد - EduPlatform{% endblock %}
+
+{% block content %}
+
+
+
+
+
إضافة واجب جديد 📝
+
+
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/core/templates/core/tasks/task_detail.html b/core/templates/core/tasks/task_detail.html
new file mode 100644
index 0000000..d886c5d
--- /dev/null
+++ b/core/templates/core/tasks/task_detail.html
@@ -0,0 +1,63 @@
+{% extends 'base.html' %}
+{% load i18n static %}
+
+{% block title %}{{ task.title }} - EduPlatform{% endblock %}
+
+{% block content %}
+
+
+
+
+
+
+
+
+
{{ task.subject.name_ar }}
+
{{ task.title }}
+
بواسطة: {{ task.teacher.user.get_full_name }}
+
+ {% if task.due_date %}
+
+
موعد التسليم
+
{{ task.due_date|date:"d M" }}
+
{{ task.due_date|date:"H:i" }}
+
+ {% endif %}
+
+
+
+
+
+
التفاصيل
+
{{ task.description }}
+
+
+ {% if task.file %}
+
+ {% endif %}
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/core/templates/core/tasks/task_list.html b/core/templates/core/tasks/task_list.html
new file mode 100644
index 0000000..6818f56
--- /dev/null
+++ b/core/templates/core/tasks/task_list.html
@@ -0,0 +1,99 @@
+{% extends 'base.html' %}
+{% load i18n static %}
+
+{% block title %}الواجبات والمهام - EduPlatform{% endblock %}
+
+{% block content %}
+
+
+
+
الواجبات والمهام 📝
+
+ {% if is_teacher %}
+ أدر واجبات طلابك ومتابعة تسليماتهم.
+ {% else %}
+ تابع واجباتك وقم بتسليمها في الوقت المحدد.
+ {% endif %}
+
+
+
+ {% if is_teacher %}
+
+
+
+
+ إضافة واجب جديد
+
+ {% endif %}
+
+
+
+
+
+ {% if tasks %}
+
+
+
+
+
+ العنوان
+ المادة
+ تاريخ التسليم
+ الحالة
+ الإجراءات
+
+
+
+ {% for task in tasks %}
+
+
+
+
+
+
{{ task.title }}
+ {{ task.created_at|date:"Y-m-d" }}
+
+
+
+
+ {{ task.subject.name_ar }}
+
+
+ {% if task.due_date %}
+ {{ task.due_date|date:"Y-m-d H:i" }}
+ {% else %}
+ غير محدد
+ {% endif %}
+
+
+ جديد
+
+
+ عرض التفاصيل
+
+
+ {% endfor %}
+
+
+
+
+ {% else %}
+
+
📋
+
لا توجد واجبات حالياً.
+ {% if is_teacher %}
+
ابدأ بإضافة واجبات لطلابك.
+ {% else %}
+
رائع! لقد أنجزت جميع مهامك.
+ {% endif %}
+
+ {% endif %}
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/core/templates/core/teacher_dashboard.html b/core/templates/core/teacher_dashboard.html
index 32787fd..3fa1877 100644
--- a/core/templates/core/teacher_dashboard.html
+++ b/core/templates/core/teacher_dashboard.html
@@ -86,6 +86,42 @@
+
+
+
صفوفي
diff --git a/core/urls.py b/core/urls.py
index dcf9f71..3943f16 100644
--- a/core/urls.py
+++ b/core/urls.py
@@ -4,7 +4,7 @@ from . import views
urlpatterns = [
path('', views.index, name='index'),
- path('set-language//', views.set_language, name='set_language'),
+ path('switch-language/', views.switch_language, name='switch_language'),
path('subject//', views.subject_detail, name='subject_detail'),
path('ajax/get-subjects-by-level/', views.get_subjects_by_level, name='get_subjects_by_level'),
path('ajax/get-cities-by-governorate/', views.get_cities_by_governorate, name='get_cities_by_governorate'),
@@ -14,6 +14,7 @@ urlpatterns = [
path('ajax/get-classroom-subjects/', views.get_classroom_subjects, name='get_classroom_subjects'),
path('profile/', views.profile, name='profile'),
path('profile/edit/teacher/', views.edit_teacher_profile, name='edit_teacher_profile'),
+ path('profile/edit/student/', views.edit_student_profile, name='edit_student_profile'),
path('logout/', views.custom_logout, name='logout'),
path('subscribe//', views.subscribe_subject, name='subscribe_subject'),
path('package//subscribe/', views.subscribe_package, name='subscribe_package'),
@@ -24,4 +25,15 @@ urlpatterns = [
path('resource//edit/', views.edit_resource, name='edit_resource'),
path('resource//delete/', views.delete_resource, name='delete_resource'),
path('resource//view/', views.view_resource, name='view_resource'),
-]
+
+ # Tasks
+ path('tasks/', views.task_list, name='task_list'),
+ path('tasks/create/', views.create_task, name='create_task'),
+ path('tasks//', views.task_detail, name='task_detail'),
+
+ # Messages
+ path('messages/inbox/', views.inbox, name='inbox'),
+ path('messages/outbox/', views.outbox, name='outbox'),
+ path('messages/send/', views.send_message, name='send_message'),
+ path('messages//', views.message_detail, name='message_detail'),
+]
\ No newline at end of file
diff --git a/core/views.py b/core/views.py
index 1b11388..aaff8f2 100644
--- a/core/views.py
+++ b/core/views.py
@@ -2,14 +2,16 @@ 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.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
+from django.urls import reverse, translate_url
from django.db.models import Q
-from .models import Classroom, Subject, Teacher, Student, City, Resource, Package
-from .forms import StudentRegistrationForm, TeacherProfileForm, ResourceForm
+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
@@ -23,16 +25,6 @@ def index(request):
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
@@ -214,6 +206,23 @@ def edit_teacher_profile(request):
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')
@@ -446,4 +455,166 @@ def view_resource(request, resource_id):
elif resource.resource_type in ['LINK', 'VIDEO']:
return redirect(resource.link)
- return redirect('subject_detail', pk=resource.subject.id)
\ No newline at end of file
+ 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
diff --git a/media/platform/smou_logo.png b/media/platform/smou_logo.png
new file mode 100644
index 0000000..54b290a
Binary files /dev/null and b/media/platform/smou_logo.png differ
diff --git a/media/students/scales_80px.png b/media/students/scales_80px.png
new file mode 100644
index 0000000..3467ba6
Binary files /dev/null and b/media/students/scales_80px.png differ
diff --git a/static/css/admin_custom.css b/static/css/admin_custom.css
new file mode 100644
index 0000000..c6fdd39
--- /dev/null
+++ b/static/css/admin_custom.css
@@ -0,0 +1,60 @@
+/* Custom Admin RTL Fixes for Jazzmin/AdminLTE */
+
+/* Ensure sidebar is on the right for RTL */
+[dir="rtl"] .main-sidebar {
+ right: 0 !important;
+ left: auto !important;
+}
+
+/* Adjust content wrapper margin */
+[dir="rtl"] .content-wrapper,
+[dir="rtl"] .main-footer,
+[dir="rtl"] .main-header {
+ margin-right: 250px !important;
+ margin-left: 0 !important;
+}
+
+/* Collapsed sidebar behavior in RTL */
+[dir="rtl"].sidebar-collapse .main-sidebar {
+ margin-right: -250px !important;
+ margin-left: 0 !important;
+}
+[dir="rtl"].sidebar-collapse .content-wrapper,
+[dir="rtl"].sidebar-collapse .main-footer,
+[dir="rtl"].sidebar-collapse .main-header {
+ margin-right: 0 !important;
+}
+
+/* Sidebar Text Alignment - Force Right Alignment */
+[dir="rtl"] .nav-sidebar .nav-item > .nav-link {
+ text-align: right !important;
+}
+
+[dir="rtl"] .nav-sidebar .nav-icon {
+ margin-left: 0.5rem !important;
+ margin-right: 0 !important;
+}
+
+[dir="rtl"] .nav-sidebar .nav-link p {
+ display: inline-block;
+ float: none;
+}
+
+/* Brand Logo Alignment */
+[dir="rtl"] .brand-link {
+ text-align: right;
+}
+[dir="rtl"] .brand-link .brand-image {
+ float: right;
+ margin-left: 0.8rem;
+ margin-right: auto;
+}
+
+/* Top Navbar Alignment */
+[dir="rtl"] .navbar-nav {
+ flex-direction: row;
+}
+[dir="rtl"] .navbar-nav.ml-auto {
+ margin-right: auto !important;
+ margin-left: 0 !important;
+}