Autosave: 20260205-044359

This commit is contained in:
Flatlogic Bot 2026-02-05 04:44:00 +00:00
parent 0da3beb95c
commit abb3cc65f8
28 changed files with 1051 additions and 26 deletions

View File

@ -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,

View File

@ -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)

View File

@ -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

View File

@ -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'],
},
),
]

View File

@ -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):

View File

@ -1,7 +1,8 @@
{% load i18n static %}
{% get_current_language as LANGUAGE_CODE %}
{% get_current_language_bidi as LANGUAGE_BIDI %}
<!DOCTYPE html>
<html lang="{{ LANGUAGE_CODE }}" dir="{% if LANGUAGE_CODE == 'ar' %}rtl{% else %}ltr{% endif %}">
<html lang="{{ LANGUAGE_CODE }}" dir="{% if LANGUAGE_BIDI %}rtl{% else %}ltr{% endif %}">
<head>
<meta charset="UTF-8">
@ -9,7 +10,7 @@
<title>{% block title %}{% if LANGUAGE_CODE == 'ar' %}منصة التعليم الإلكتروني{% else %}EduPlatform{% endif %}{% endblock %}</title>
<!-- Bootstrap 5 CDN -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap{% if LANGUAGE_CODE == 'ar' %}.rtl{% endif %}.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap{% if LANGUAGE_BIDI %}.rtl{% endif %}.min.css" rel="stylesheet">
<!-- FontAwesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css">
@ -138,7 +139,7 @@
</li>
{% if user.is_staff %}
<li class="nav-item">
<a class="nav-link" href="/admin/">{% if LANGUAGE_CODE == 'ar' %}المسؤول{% else %}Admin{% endif %}</a>
<a class="nav-link" href="{% url 'admin:index' %}">{% if LANGUAGE_CODE == 'ar' %}المسؤول{% else %}Admin{% endif %}</a>
</li>
{% endif %}
</ul>
@ -177,8 +178,19 @@
</button>
<ul class="dropdown-menu dropdown-menu-end shadow-sm border-0" aria-labelledby="userDropdown" style="border-radius: 12px;">
<li><a class="dropdown-item py-2" href="{% url 'profile' %}">{% if LANGUAGE_CODE == 'ar' %}الملف الشخصي{% else %}Profile{% endif %}</a></li>
<!-- Tasks Link -->
<li><a class="dropdown-item py-2" href="{% url 'task_list' %}">
{% if LANGUAGE_CODE == 'ar' %}الواجبات{% else %}Tasks{% endif %}
</a></li>
<!-- Inbox Link -->
<li><a class="dropdown-item py-2" href="{% url 'inbox' %}">
{% if LANGUAGE_CODE == 'ar' %}البريد الوارد{% else %}Inbox{% endif %}
</a></li>
{% if user.is_staff %}
<li><a class="dropdown-item py-2" href="/admin/">{% if LANGUAGE_CODE == 'ar' %}لوحة تحكم المسؤول{% else %}Dashboard{% endif %}</a></li>
<li><a class="dropdown-item py-2" href="{% url 'admin:index' %}">{% if LANGUAGE_CODE == 'ar' %}لوحة تحكم المسؤول{% else %}Dashboard{% endif %}</a></li>
{% endif %}
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item py-2 text-danger" href="{% url 'logout' %}">{% if LANGUAGE_CODE == 'ar' %}تسجيل الخروج{% else %}Logout{% endif %}</a></li>

View File

@ -0,0 +1,95 @@
{% extends 'base.html' %}
{% load i18n static %}
{% block title %}تعديل ملف الطالب - EduPlatform{% endblock %}
{% block content %}
<div class="container" style="margin-top: 100px; margin-bottom: 50px;">
<div class="row justify-content-center">
<div class="col-md-8 col-lg-6">
<div class="glass-card p-4 p-md-5">
<h3 class="fw-bold text-center mb-4 text-primary">تعديل الملف الشخصي</h3>
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<!-- Avatar Preview -->
<div class="text-center mb-4">
<div class="position-relative d-inline-block">
{% if form.instance.avatar %}
<img src="{{ form.instance.avatar.url }}" alt="Avatar" class="rounded-circle img-thumbnail" style="width: 120px; height: 120px; object-fit: cover;">
{% else %}
<div class="rounded-circle bg-light d-flex align-items-center justify-content-center mx-auto" style="width: 120px; height: 120px;">
<span class="text-muted display-4">{{ user.first_name|first|upper }}</span>
</div>
{% endif %}
</div>
</div>
{% for field in form %}
<div class="mb-3">
<label for="{{ field.id_for_label }}" class="form-label fw-semibold">{{ field.label }}</label>
{{ field }}
{% if field.help_text %}
<div class="form-text text-muted">{{ field.help_text }}</div>
{% endif %}
{% for error in field.errors %}
<div class="invalid-feedback d-block">
{{ error }}
</div>
{% endfor %}
</div>
{% endfor %}
<div class="d-grid gap-2 mt-4">
<button type="submit" class="btn btn-primary btn-lg rounded-pill fw-bold">
حفظ التغييرات
</button>
<a href="{% url 'profile' %}" class="btn btn-outline-secondary rounded-pill">
إلغاء
</a>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
document.addEventListener('DOMContentLoaded', function() {
// Governorate & City Logic
const governorateSelect = document.getElementById('id_governorate');
const citySelect = document.getElementById('id_city');
if (governorateSelect && citySelect) {
governorateSelect.addEventListener('change', function() {
const governorateId = this.value;
citySelect.innerHTML = '<option value="">جاري التحميل...</option>';
if (!governorateId) {
citySelect.innerHTML = '<option value="">---------</option>';
return;
}
fetch(`{% url 'get_cities_by_governorate' %}?governorate_id=${governorateId}`)
.then(response => response.json())
.then(data => {
citySelect.innerHTML = '<option value="">---------</option>';
data.forEach(city => {
const option = document.createElement('option');
option.value = city.id;
option.textContent = city.name_ar || city.name_en;
citySelect.appendChild(option);
});
})
.catch(err => {
console.error('Error fetching cities:', err);
citySelect.innerHTML = '<option value="">خطأ في تحميل المدن</option>';
});
});
}
});
</script>
{% endblock %}

View File

@ -0,0 +1,54 @@
{% extends 'base.html' %}
{% load i18n static %}
{% block title %}البريد الوارد - EduPlatform{% endblock %}
{% block content %}
<div class="container" style="margin-top: 100px; margin-bottom: 50px;">
<div class="row mb-4">
<div class="col-md-8">
<h2 class="fw-bold">البريد الوارد 📥</h2>
<p class="text-muted">الرسائل الواردة من المعلمين أو الطلاب.</p>
</div>
<div class="col-md-4 text-md-end">
<a href="{% url 'send_message' %}" class="btn btn-primary rounded-pill">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-pencil-square me-2" viewBox="0 0 16 16">
<path d="M15.502 1.94a.5.5 0 0 1 0 .706L14.459 3.69l-2-2L13.502.646a.5.5 0 0 1 .707 0l1.293 1.293zm-1.75 2.456-2-2L4.939 9.21a.5.5 0 0 0-.121.196l-.805 2.414a.25.25 0 0 0 .316.316l2.414-.805a.5.5 0 0 0 .196-.12l6.813-6.814z"/>
<path fill-rule="evenodd" d="M1 13.5A1.5 1.5 0 0 0 2.5 15h11a1.5 1.5 0 0 0 1.5-1.5v-6a.5.5 0 0 0-1 0v6a.5.5 0 0 1-.5.5h-11a.5.5 0 0 1-.5-.5v-11a.5.5 0 0 1 .5-.5H9a.5.5 0 0 0 0-1H2.5A1.5 1.5 0 0 0 1 2.5v11z"/>
</svg>
رسالة جديدة
</a>
<a href="{% url 'outbox' %}" class="btn btn-outline-secondary rounded-pill ms-2">البريد الصادر</a>
</div>
</div>
<div class="row">
<div class="col-12">
{% if messages %}
<div class="glass-card p-0 overflow-hidden">
<div class="list-group list-group-flush">
{% for message in messages %}
<a href="{% url 'message_detail' message.id %}" class="list-group-item list-group-item-action p-4 {% if not message.is_read %}bg-light border-start border-5 border-primary{% endif %}">
<div class="d-flex w-100 justify-content-between align-items-center mb-1">
<h6 class="mb-0 fw-bold {% if not message.is_read %}text-primary{% endif %}">
{{ message.sender.get_full_name|default:message.sender.username }}
</h6>
<small class="text-muted">{{ message.created_at|date:"Y-m-d H:i" }}</small>
</div>
<p class="mb-1 fw-bold">{{ message.subject }}</p>
<small class="text-muted text-truncate d-block" style="max-width: 80%;">{{ message.body|truncatechars:100 }}</small>
</a>
{% endfor %}
</div>
</div>
{% else %}
<div class="glass-card text-center py-5">
<div class="mb-3 text-muted display-4">📭</div>
<h5 class="text-muted">صندوق الوارد فارغ.</h5>
<p class="text-muted small">لم تستلم أي رسائل جديدة بعد.</p>
</div>
{% endif %}
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,52 @@
{% extends 'base.html' %}
{% load i18n static %}
{% block title %}{{ message.subject }} - EduPlatform{% endblock %}
{% block content %}
<div class="container" style="margin-top: 100px; margin-bottom: 50px;">
<div class="row justify-content-center">
<div class="col-lg-8">
<div class="mb-3">
<a href="{% url 'inbox' %}" class="text-decoration-none text-muted">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-right me-2" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M1 8a.5.5 0 0 1 .5-.5h11.793l-3.147-3.146a.5.5 0 0 1 .708-.708l4 4a.5.5 0 0 1 0 .708l-4 4a.5.5 0 0 1-.708-.708L13.293 8.5H1.5A.5.5 0 0 1 1 8z"/>
</svg>
العودة للبريد الوارد
</a>
</div>
<div class="glass-card p-4 p-md-5">
<div class="d-flex justify-content-between align-items-start mb-4 border-bottom pb-4">
<div>
<h4 class="fw-bold mb-2">{{ message.subject }}</h4>
<div class="d-flex align-items-center text-muted">
<span class="me-2">من:</span>
<span class="fw-bold text-dark">{{ message.sender.get_full_name|default:message.sender.username }}</span>
</div>
<div class="d-flex align-items-center text-muted mt-1">
<span class="me-2">إلى:</span>
<span class="text-dark">{{ message.recipient.get_full_name|default:message.recipient.username }}</span>
</div>
</div>
<div class="text-end text-muted small">
<div>{{ message.created_at|date:"Y-m-d" }}</div>
<div>{{ message.created_at|date:"H:i A" }}</div>
</div>
</div>
<div class="message-body" style="white-space: pre-wrap; line-height: 1.8;">{{ message.body }}</div>
<div class="mt-5 pt-4 border-top">
<a href="{% url 'send_message' %}?recipient={{ message.sender.id }}&subject=Re: {{ message.subject }}" class="btn btn-outline-primary rounded-pill px-4">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-reply-fill me-2" viewBox="0 0 16 16">
<path d="M5.921 11.9 1.353 8.62a.719.719 0 0 1 0-1.238L5.921 4.1A.716.716 0 0 1 7 4.719V6c1.5 0 6 0 7 8-2.5-4.5-7-4-7-4v1.281c0 .56-.606.898-1.079.62z"/>
</svg>
رد
</a>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,57 @@
{% extends 'base.html' %}
{% load i18n static %}
{% block title %}البريد الصادر - EduPlatform{% endblock %}
{% block content %}
<div class="container" style="margin-top: 100px; margin-bottom: 50px;">
<div class="row mb-4">
<div class="col-md-8">
<h2 class="fw-bold">البريد الصادر 📤</h2>
<p class="text-muted">الرسائل التي قمت بإرسالها.</p>
</div>
<div class="col-md-4 text-md-end">
<a href="{% url 'inbox' %}" class="btn btn-outline-primary rounded-pill">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-right me-2" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M1 8a.5.5 0 0 1 .5-.5h11.793l-3.147-3.146a.5.5 0 0 1 .708-.708l4 4a.5.5 0 0 1 0 .708l-4 4a.5.5 0 0 1-.708-.708L13.293 8.5H1.5A.5.5 0 0 1 1 8z"/>
</svg>
العودة للبريد الوارد
</a>
</div>
</div>
<div class="row">
<div class="col-12">
{% if messages %}
<div class="glass-card p-0 overflow-hidden">
<div class="list-group list-group-flush">
{% for message in messages %}
<div class="list-group-item p-4">
<div class="d-flex w-100 justify-content-between align-items-center mb-1">
<h6 class="mb-0">
<span class="text-muted small">إلى:</span>
{{ message.recipient.get_full_name|default:message.recipient.username }}
</h6>
<small class="text-muted">{{ message.created_at|date:"Y-m-d H:i" }}</small>
</div>
<p class="mb-1 fw-bold">{{ message.subject }}</p>
<small class="text-muted text-truncate d-block">{{ message.body|truncatechars:100 }}</small>
<div class="mt-2">
<a href="{% url 'message_detail' message.id %}" class="btn btn-sm btn-light text-muted border rounded-pill px-3">عرض الرسالة</a>
</div>
</div>
{% endfor %}
</div>
</div>
{% else %}
<div class="glass-card text-center py-5">
<div class="mb-3 text-muted display-4">📨</div>
<h5 class="text-muted">صندوق الصادر فارغ.</h5>
<p class="text-muted small">لم تقم بإرسال أي رسائل بعد.</p>
</div>
{% endif %}
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,43 @@
{% extends 'base.html' %}
{% load i18n static %}
{% block title %}إرسال رسالة جديدة - EduPlatform{% endblock %}
{% block content %}
<div class="container" style="margin-top: 100px; margin-bottom: 50px;">
<div class="row justify-content-center">
<div class="col-lg-8">
<div class="glass-card p-4 p-md-5">
<h3 class="fw-bold mb-4 text-center">إرسال رسالة جديدة ✉️</h3>
<form method="post">
{% csrf_token %}
{% for field in form %}
<div class="mb-3">
<label for="{{ field.id_for_label }}" class="form-label fw-bold">{{ field.label }}</label>
{{ field }}
{% if field.help_text %}
<div class="form-text text-muted">{{ field.help_text }}</div>
{% endif %}
{% for error in field.errors %}
<div class="text-danger small mt-1">{{ error }}</div>
{% endfor %}
</div>
{% endfor %}
<div class="d-flex justify-content-between align-items-center mt-4">
<a href="{% url 'inbox' %}" class="btn btn-outline-secondary rounded-pill px-4">إلغاء</a>
<button type="submit" class="btn btn-primary rounded-pill px-5">
إرسال
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-send ms-2" viewBox="0 0 16 16">
<path d="M15.854.146a.5.5 0 0 1 .11.54l-5.819 14.547a.75.75 0 0 1-1.329.124l-3.178-4.995L.643 7.184a.75.75 0 0 1 .124-1.33L15.314.037a.5.5 0 0 1 .54.11ZM6.636 10.07l2.761 4.338L14.13 2.576 6.636 10.07Zm6.787-8.201L1.591 6.602l4.339 2.76 7.494-7.493Z"/>
</svg>
</button>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -64,7 +64,7 @@
</div>
<div class="mt-4">
<a href="#" class="btn btn-outline-primary w-100 rounded-pill">تعديل الملف الشخصي</a>
<a href="{% url 'edit_student_profile' %}" class="btn btn-outline-primary w-100 rounded-pill">تعديل الملف الشخصي</a>
</div>
</div>
</div>
@ -102,6 +102,42 @@
</div>
</div>
<!-- Quick Access Section -->
<div class="row mb-4">
<div class="col-md-6 mb-3 mb-md-0">
<a href="{% url 'task_list' %}" class="text-decoration-none">
<div class="glass-card py-3 px-4 d-flex align-items-center justify-content-between hover-scale">
<div>
<h6 class="text-muted mb-1">الواجبات والمهام</h6>
<h5 class="fw-bold mb-0 text-dark">واجباتي</h5>
</div>
<div class="bg-warning bg-opacity-10 p-3 rounded-circle text-warning">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-clipboard-check" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M10.854 7.146a.5.5 0 0 1 0 .708l-3 3a.5.5 0 0 1-.708 0l-1.5-1.5a.5.5 0 1 1 .708-.708L7.5 9.793l2.646-2.647a.5.5 0 0 1 .708 0z"/>
<path d="M4 1.5H3a2 2 0 0 0-2 2V14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3.5a2 2 0 0 0-2-2h-1v1h1a1 1 0 0 1 1 1V14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3.5a1 1 0 0 1 1-1h1v-1z"/>
<path d="M9.5 1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5h3zm-3-1A1.5 1.5 0 0 0 5 1.5v1A1.5 1.5 0 0 0 6.5 4h3A1.5 1.5 0 0 0 11 2.5v-1A1.5 1.5 0 0 0 9.5 0h-3z"/>
</svg>
</div>
</div>
</a>
</div>
<div class="col-md-6">
<a href="{% url 'inbox' %}" class="text-decoration-none">
<div class="glass-card py-3 px-4 d-flex align-items-center justify-content-between hover-scale">
<div>
<h6 class="text-muted mb-1">البريد</h6>
<h5 class="fw-bold mb-0 text-dark">الرسائل الواردة</h5>
</div>
<div class="bg-info bg-opacity-10 p-3 rounded-circle text-info">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-envelope" viewBox="0 0 16 16">
<path d="M0 4a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V4Zm2-1a1 1 0 0 0-1 1v.217l7 4.2 7-4.2V4a1 1 0 0 0-1-1H2Zm13 2.383-4.708 2.825L15 11.105V5.383Zm-.034 6.876-5.64-3.471L8 9.583l-1.326-.795-5.64 3.47A1 1 0 0 0 2 13h12a1 1 0 0 0 .966-.741ZM1 11.105l4.708-2.897L1 5.383v5.722Z"/>
</svg>
</div>
</div>
</a>
</div>
</div>
<!-- My Subjects Section -->
<div class="mb-4">
<h4 class="fw-bold mb-3">موادي</h4>

View File

@ -0,0 +1,38 @@
{% extends 'base.html' %}
{% load i18n static %}
{% block title %}إضافة واجب جديد - EduPlatform{% endblock %}
{% block content %}
<div class="container" style="margin-top: 100px; margin-bottom: 50px;">
<div class="row justify-content-center">
<div class="col-lg-8">
<div class="glass-card p-4 p-md-5">
<h3 class="fw-bold mb-4 text-center">إضافة واجب جديد 📝</h3>
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{% for field in form %}
<div class="mb-3">
<label for="{{ field.id_for_label }}" class="form-label fw-bold">{{ field.label }}</label>
{{ field }}
{% if field.help_text %}
<div class="form-text text-muted">{{ field.help_text }}</div>
{% endif %}
{% for error in field.errors %}
<div class="text-danger small mt-1">{{ error }}</div>
{% endfor %}
</div>
{% endfor %}
<div class="d-flex justify-content-between align-items-center mt-4">
<a href="{% url 'task_list' %}" class="btn btn-outline-secondary rounded-pill px-4">إلغاء</a>
<button type="submit" class="btn btn-primary rounded-pill px-5">نشر الواجب</button>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,63 @@
{% extends 'base.html' %}
{% load i18n static %}
{% block title %}{{ task.title }} - EduPlatform{% endblock %}
{% block content %}
<div class="container" style="margin-top: 100px; margin-bottom: 50px;">
<div class="row justify-content-center">
<div class="col-lg-8">
<div class="mb-3">
<a href="{% url 'task_list' %}" class="text-decoration-none text-muted">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-right me-2" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M1 8a.5.5 0 0 1 .5-.5h11.793l-3.147-3.146a.5.5 0 0 1 .708-.708l4 4a.5.5 0 0 1 0 .708l-4 4a.5.5 0 0 1-.708-.708L13.293 8.5H1.5A.5.5 0 0 1 1 8z"/>
</svg>
العودة للقائمة
</a>
</div>
<div class="glass-card p-4 p-md-5">
<div class="d-flex justify-content-between align-items-start mb-4">
<div>
<span class="badge bg-primary bg-opacity-10 text-primary mb-2">{{ task.subject.name_ar }}</span>
<h2 class="fw-bold">{{ task.title }}</h2>
<p class="text-muted">بواسطة: {{ task.teacher.user.get_full_name }}</p>
</div>
{% if task.due_date %}
<div class="text-center bg-danger bg-opacity-10 p-3 rounded-3 text-danger">
<div class="small fw-bold">موعد التسليم</div>
<div class="h5 mb-0 fw-bold">{{ task.due_date|date:"d M" }}</div>
<div class="small">{{ task.due_date|date:"H:i" }}</div>
</div>
{% endif %}
</div>
<hr>
<div class="my-4">
<h5 class="fw-bold mb-3">التفاصيل</h5>
<div class="text-muted" style="white-space: pre-wrap;">{{ task.description }}</div>
</div>
{% if task.file %}
<div class="mt-5">
<h6 class="fw-bold mb-3">المرفقات</h6>
<a href="{{ task.file.url }}" class="d-flex align-items-center p-3 bg-light rounded-3 text-decoration-none text-dark border">
<div class="bg-white p-2 rounded shadow-sm me-3 text-primary">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-file-earmark-arrow-down" viewBox="0 0 16 16">
<path d="M8.5 6.5a.5.5 0 0 0-1 0v3.793L6.354 9.146a.5.5 0 1 0-.708.708l2 2a.5.5 0 0 0 .708 0l2-2a.5.5 0 0 0-.708-.708L8.5 10.293V6.5z"/>
<path d="M14 14V4.5L9.5 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2zM9.5 3A1.5 1.5 0 0 0 11 4.5h2V14a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h5.5v2z"/>
</svg>
</div>
<div>
<div class="fw-bold">تحميل الملف المرفق</div>
<small class="text-muted">انقر للتحميل</small>
</div>
</a>
</div>
{% endif %}
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,99 @@
{% extends 'base.html' %}
{% load i18n static %}
{% block title %}الواجبات والمهام - EduPlatform{% endblock %}
{% block content %}
<div class="container" style="margin-top: 100px; margin-bottom: 50px;">
<div class="row mb-4">
<div class="col-md-8">
<h2 class="fw-bold">الواجبات والمهام 📝</h2>
<p class="text-muted">
{% if is_teacher %}
أدر واجبات طلابك ومتابعة تسليماتهم.
{% else %}
تابع واجباتك وقم بتسليمها في الوقت المحدد.
{% endif %}
</p>
</div>
<div class="col-md-4 text-md-end">
{% if is_teacher %}
<a href="{% url 'create_task' %}" class="btn btn-primary rounded-pill">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-plus-lg me-2" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M8 2a.5.5 0 0 1 .5.5v5h5a.5.5 0 0 1 0 1h-5v5a.5.5 0 0 1-1 0v-5h-5a.5.5 0 0 1 0-1h5v-5A.5.5 0 0 1 8 2z"/>
</svg>
إضافة واجب جديد
</a>
{% endif %}
</div>
</div>
<div class="row">
<div class="col-12">
{% if tasks %}
<div class="glass-card p-0 overflow-hidden">
<div class="table-responsive">
<table class="table table-hover mb-0 align-middle">
<thead class="bg-light">
<tr>
<th class="py-3 ps-4">العنوان</th>
<th class="py-3">المادة</th>
<th class="py-3">تاريخ التسليم</th>
<th class="py-3">الحالة</th>
<th class="py-3 text-end pe-4">الإجراءات</th>
</tr>
</thead>
<tbody>
{% for task in tasks %}
<tr>
<td class="ps-4">
<div class="d-flex align-items-center">
<div class="bg-primary bg-opacity-10 p-2 rounded-circle me-3 text-primary">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-file-earmark-text" viewBox="0 0 16 16">
<path d="M5.5 7a.5.5 0 0 0 0 1h5a.5.5 0 0 0 0-1h-5zM5 9.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5zm0 2a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5z"/>
<path d="M9.5 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V4.5L9.5 0zm0 1v2A1.5 1.5 0 0 0 11 4.5h2V14a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h5.5z"/>
</svg>
</div>
<div>
<h6 class="fw-bold mb-0">{{ task.title }}</h6>
<small class="text-muted">{{ task.created_at|date:"Y-m-d" }}</small>
</div>
</div>
</td>
<td>
<span class="badge bg-secondary bg-opacity-10 text-dark">{{ task.subject.name_ar }}</span>
</td>
<td>
{% if task.due_date %}
<span class="text-danger fw-bold">{{ task.due_date|date:"Y-m-d H:i" }}</span>
{% else %}
<span class="text-muted">غير محدد</span>
{% endif %}
</td>
<td>
<span class="badge bg-success bg-opacity-10 text-success">جديد</span>
</td>
<td class="text-end pe-4">
<a href="{% url 'task_detail' task.id %}" class="btn btn-sm btn-outline-primary rounded-pill">عرض التفاصيل</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% else %}
<div class="glass-card text-center py-5">
<div class="mb-3 text-muted display-4">📋</div>
<h5 class="text-muted">لا توجد واجبات حالياً.</h5>
{% if is_teacher %}
<p class="text-muted small">ابدأ بإضافة واجبات لطلابك.</p>
{% else %}
<p class="text-muted small">رائع! لقد أنجزت جميع مهامك.</p>
{% endif %}
</div>
{% endif %}
</div>
</div>
</div>
{% endblock %}

View File

@ -86,6 +86,42 @@
</div>
</div>
<!-- Quick Access Section -->
<div class="row mb-4">
<div class="col-md-6 mb-3 mb-md-0">
<a href="{% url 'task_list' %}" class="text-decoration-none">
<div class="glass-card py-3 px-4 d-flex align-items-center justify-content-between hover-scale">
<div>
<h6 class="text-muted mb-1">الواجبات والمهام</h6>
<h5 class="fw-bold mb-0 text-dark">إدارة الواجبات</h5>
</div>
<div class="bg-warning bg-opacity-10 p-3 rounded-circle text-warning">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-clipboard-check" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M10.854 7.146a.5.5 0 0 1 0 .708l-3 3a.5.5 0 0 1-.708 0l-1.5-1.5a.5.5 0 1 1 .708-.708L7.5 9.793l2.646-2.647a.5.5 0 0 1 .708 0z"/>
<path d="M4 1.5H3a2 2 0 0 0-2 2V14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3.5a2 2 0 0 0-2-2h-1v1h1a1 1 0 0 1 1 1V14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3.5a1 1 0 0 1 1-1h1v-1z"/>
<path d="M9.5 1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5h3zm-3-1A1.5 1.5 0 0 0 5 1.5v1A1.5 1.5 0 0 0 6.5 4h3A1.5 1.5 0 0 0 11 2.5v-1A1.5 1.5 0 0 0 9.5 0h-3z"/>
</svg>
</div>
</div>
</a>
</div>
<div class="col-md-6">
<a href="{% url 'inbox' %}" class="text-decoration-none">
<div class="glass-card py-3 px-4 d-flex align-items-center justify-content-between hover-scale">
<div>
<h6 class="text-muted mb-1">البريد</h6>
<h5 class="fw-bold mb-0 text-dark">الرسائل الواردة</h5>
</div>
<div class="bg-info bg-opacity-10 p-3 rounded-circle text-info">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-envelope" viewBox="0 0 16 16">
<path d="M0 4a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V4Zm2-1a1 1 0 0 0-1 1v.217l7 4.2 7-4.2V4a1 1 0 0 0-1-1H2Zm13 2.383-4.708 2.825L15 11.105V5.383Zm-.034 6.876-5.64-3.471L8 9.583l-1.326-.795-5.64 3.47A1 1 0 0 0 2 13h12a1 1 0 0 0 .966-.741ZM1 11.105l4.708-2.897L1 5.383v5.722Z"/>
</svg>
</div>
</div>
</a>
</div>
</div>
<!-- My Subjects Section -->
<div class="mb-4">
<h4 class="fw-bold mb-3">صفوفي</h4>

View File

@ -4,7 +4,7 @@ from . import views
urlpatterns = [
path('', views.index, name='index'),
path('set-language/<str:lang_code>/', views.set_language, name='set_language'),
path('switch-language/', views.switch_language, name='switch_language'),
path('subject/<int:pk>/', 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/<int:subject_id>/', views.subscribe_subject, name='subscribe_subject'),
path('package/<int:package_id>/subscribe/', views.subscribe_package, name='subscribe_package'),
@ -24,4 +25,15 @@ urlpatterns = [
path('resource/<int:resource_id>/edit/', views.edit_resource, name='edit_resource'),
path('resource/<int:resource_id>/delete/', views.delete_resource, name='delete_resource'),
path('resource/<int:resource_id>/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/<int:pk>/', 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/<int:pk>/', views.message_detail, name='message_detail'),
]

View File

@ -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)
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

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -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;
}