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 %} {% endif %} @@ -177,8 +178,19 @@