Autosave: 20260205-044359
This commit is contained in:
parent
0da3beb95c
commit
abb3cc65f8
Binary file not shown.
Binary file not shown.
@ -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,
|
||||
|
||||
@ -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)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
105
core/forms.py
105
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
|
||||
51
core/migrations/0017_message_task.py
Normal file
51
core/migrations/0017_message_task.py
Normal 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'],
|
||||
},
|
||||
),
|
||||
]
|
||||
BIN
core/migrations/__pycache__/0017_message_task.cpython-311.pyc
Normal file
BIN
core/migrations/__pycache__/0017_message_task.cpython-311.pyc
Normal file
Binary file not shown.
@ -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):
|
||||
|
||||
@ -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>
|
||||
|
||||
95
core/templates/core/edit_student_profile.html
Normal file
95
core/templates/core/edit_student_profile.html
Normal 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 %}
|
||||
54
core/templates/core/messages/inbox.html
Normal file
54
core/templates/core/messages/inbox.html
Normal 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 %}
|
||||
52
core/templates/core/messages/message_detail.html
Normal file
52
core/templates/core/messages/message_detail.html
Normal 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 %}
|
||||
57
core/templates/core/messages/outbox.html
Normal file
57
core/templates/core/messages/outbox.html
Normal 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 %}
|
||||
43
core/templates/core/messages/send_message.html
Normal file
43
core/templates/core/messages/send_message.html
Normal 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 %}
|
||||
@ -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>
|
||||
|
||||
38
core/templates/core/tasks/create_task.html
Normal file
38
core/templates/core/tasks/create_task.html
Normal 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 %}
|
||||
63
core/templates/core/tasks/task_detail.html
Normal file
63
core/templates/core/tasks/task_detail.html
Normal 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 %}
|
||||
99
core/templates/core/tasks/task_list.html
Normal file
99
core/templates/core/tasks/task_list.html
Normal 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 %}
|
||||
@ -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>
|
||||
|
||||
16
core/urls.py
16
core/urls.py
@ -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'),
|
||||
]
|
||||
201
core/views.py
201
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)
|
||||
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
|
||||
|
||||
BIN
media/platform/smou_logo.png
Normal file
BIN
media/platform/smou_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
BIN
media/students/scales_80px.png
Normal file
BIN
media/students/scales_80px.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.4 KiB |
60
static/css/admin_custom.css
Normal file
60
static/css/admin_custom.css
Normal 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;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user