Autosave: 20260204-144027

This commit is contained in:
Flatlogic Bot 2026-02-04 14:40:28 +00:00
parent 05987f69ac
commit c673d9b2b1
35 changed files with 1123 additions and 369 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

View File

@ -138,7 +138,7 @@ AUTH_PASSWORD_VALIDATORS = [
# Internationalization
# https://docs.djangoproject.com/en/5.2/topics/i18n/
LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = 'ar'
TIME_ZONE = 'UTC'
@ -307,4 +307,4 @@ JAZZMIN_SETTINGS = {
"use_google_fonts_cdn": True,
# Whether to show the UI customizer on the sidebar
"show_ui_builder": True,
}
}

View File

@ -17,13 +17,13 @@ class ActionsModelAdmin(admin.ModelAdmin):
return format_html(
'<div style="white-space: nowrap;">'
'<a class="btn btn-sm btn-primary" href="{}" title="Edit" style="margin-right:5px;"><i class="fas fa-edit"></i></a>'
'<a class="btn btn-sm btn-danger" href="{}" title="Delete"><i class="fas fa-trash-alt"></i></a>'
'<a class="btn btn-sm btn-primary" href="{}" title="تعديل" style="margin-right:5px;"><i class="fas fa-edit"></i></a>'
'<a class="btn btn-sm btn-danger" href="{}" title="حذف"><i class="fas fa-trash-alt"></i></a>'
'</div>',
edit_url,
delete_url
)
actions_column.short_description = 'Actions'
actions_column.short_description = 'إجراءات'
@admin.register(Classroom)
class ClassroomAdmin(ActionsModelAdmin):
@ -42,7 +42,7 @@ class SubjectAdmin(ActionsModelAdmin):
def get_teachers(self, obj):
return ", ".join([str(t) for t in obj.teachers.all()])
get_teachers.short_description = 'Teachers'
get_teachers.short_description = 'المعلمون'
@admin.register(City)
class CityAdmin(ActionsModelAdmin):
@ -57,7 +57,7 @@ class ResourceAdminForm(forms.ModelForm):
classroom = forms.ModelChoiceField(
queryset=Classroom.objects.all(),
required=True,
label="Filter by Classroom"
label="تصفية حسب الصف"
)
class Meta:
@ -92,4 +92,4 @@ class StudentAdmin(ActionsModelAdmin):
form = super().get_form(request, obj, **kwargs)
if obj and obj.classroom:
form.base_fields['subscribed_subjects'].queryset = Subject.objects.filter(classroom=obj.classroom)
return form
return form

View File

@ -1,25 +1,31 @@
from django import forms
from django.contrib.auth.models import User
from .models import Student, Subject, Classroom, City, Governorate
from .models import Student, Subject, Classroom, City, Governorate, Teacher, Resource
class StudentRegistrationForm(forms.ModelForm):
full_name = forms.CharField(max_length=150, required=True, label="Full Name")
username = forms.CharField(max_length=150, required=True)
email = forms.EmailField(required=True)
password = forms.CharField(widget=forms.PasswordInput, required=True)
password_confirm = forms.CharField(widget=forms.PasswordInput, required=True, label="Confirm Password")
full_name = forms.CharField(max_length=150, required=True, label="الاسم الكامل")
username = forms.CharField(max_length=150, required=True, label="اسم المستخدم")
email = forms.EmailField(required=True, label="البريد الإلكتروني")
password = forms.CharField(widget=forms.PasswordInput, required=True, label="كلمة المرور")
password_confirm = forms.CharField(widget=forms.PasswordInput, required=True, label="تأكيد كلمة المرور")
classroom = forms.ModelChoiceField(queryset=Classroom.objects.all(), required=True, empty_label="Select Classroom")
classroom = forms.ModelChoiceField(queryset=Classroom.objects.all(), required=True, empty_label="اختر الصف", label="الصف الدراسي")
subjects = forms.ModelMultipleChoiceField(
queryset=Subject.objects.all(),
required=False,
widget=forms.CheckboxSelectMultiple,
label="Select Subjects"
label="اختر المواد"
)
class Meta:
model = Student
fields = ['mobile_number', 'governorate', 'city', 'avatar']
labels = {
'mobile_number': 'رقم الهاتف',
'governorate': 'المحافظة',
'city': 'المدينة',
'avatar': 'الصورة الشخصية'
}
widgets = {
'avatar': forms.FileInput(attrs={'accept': 'image/*', 'capture': 'camera'}),
}
@ -65,7 +71,7 @@ class StudentRegistrationForm(forms.ModelForm):
password_confirm = cleaned_data.get("password_confirm")
if password and password_confirm and password != password_confirm:
self.add_error('password_confirm', "Passwords do not match")
self.add_error('password_confirm', "كلمات المرور غير متطابقة")
return cleaned_data
@ -91,4 +97,59 @@ class StudentRegistrationForm(forms.ModelForm):
if commit:
student.save()
student.subscribed_subjects.set(self.cleaned_data['subjects'])
return student
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="الاسم الأخير")
email = forms.EmailField(required=True, label="البريد الإلكتروني")
class Meta:
model = Teacher
fields = ['bio', 'specialization', 'avatar']
labels = {
'bio': 'نبذة عني',
'specialization': 'التخصص',
'avatar': 'الصورة الشخصية'
}
widgets = {
'bio': forms.Textarea(attrs={'rows': 4}),
}
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'
def save(self, commit=True):
teacher = super().save(commit=False)
if commit:
user = teacher.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()
teacher.save()
return teacher
class ResourceForm(forms.ModelForm):
class Meta:
model = Resource
fields = ['title_ar', 'title_en', 'resource_type', 'file', 'link']
labels = {
'title_ar': 'العنوان (عربي)',
'title_en': 'العنوان (إنجليزي)',
'resource_type': 'نوع المصدر',
'file': 'الملف',
'link': 'الرابط'
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field_name, field in self.fields.items():
field.widget.attrs['class'] = 'form-control'

View File

@ -0,0 +1,314 @@
# Generated by Django 5.2.7 on 2026-02-04 10:25
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0013_resource_resource_type_alter_resource_link'),
]
operations = [
migrations.AlterModelOptions(
name='city',
options={'verbose_name': 'مدينة', 'verbose_name_plural': 'المدن'},
),
migrations.AlterModelOptions(
name='classroom',
options={'verbose_name': 'صف دراسي', 'verbose_name_plural': 'الصفوف الدراسية'},
),
migrations.AlterModelOptions(
name='governorate',
options={'verbose_name': 'محافظة', 'verbose_name_plural': 'المحافظات'},
),
migrations.AlterModelOptions(
name='platformsettings',
options={'verbose_name': 'إعدادات المنصة', 'verbose_name_plural': 'إعدادات المنصة'},
),
migrations.AlterModelOptions(
name='resource',
options={'verbose_name': 'مصدر تعليمي', 'verbose_name_plural': 'المصادر التعليمية'},
),
migrations.AlterModelOptions(
name='student',
options={'verbose_name': 'طالب', 'verbose_name_plural': 'الطلاب'},
),
migrations.AlterModelOptions(
name='subject',
options={'verbose_name': 'مادة دراسية', 'verbose_name_plural': 'المواد الدراسية'},
),
migrations.AlterModelOptions(
name='teacher',
options={'verbose_name': 'معلم', 'verbose_name_plural': 'المعلمون'},
),
migrations.AlterModelOptions(
name='thawaniconfiguration',
options={'verbose_name': 'إعدادات ثواني', 'verbose_name_plural': 'إعدادات ثواني'},
),
migrations.AlterModelOptions(
name='wablasconfiguration',
options={'verbose_name': 'إعدادات Wablas', 'verbose_name_plural': 'إعدادات Wablas'},
),
migrations.AlterField(
model_name='city',
name='governorate',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='cities', to='core.governorate', verbose_name='المحافظة'),
),
migrations.AlterField(
model_name='city',
name='name_ar',
field=models.CharField(default='', max_length=100, verbose_name='الاسم (عربي)'),
),
migrations.AlterField(
model_name='city',
name='name_en',
field=models.CharField(default='', max_length=100, verbose_name='الاسم (إنجليزي)'),
),
migrations.AlterField(
model_name='classroom',
name='description',
field=models.TextField(blank=True, verbose_name='الوصف'),
),
migrations.AlterField(
model_name='classroom',
name='name_ar',
field=models.CharField(max_length=100, verbose_name='الاسم (عربي)'),
),
migrations.AlterField(
model_name='classroom',
name='name_en',
field=models.CharField(max_length=100, verbose_name='الاسم (إنجليزي)'),
),
migrations.AlterField(
model_name='governorate',
name='name_ar',
field=models.CharField(default='', max_length=100, verbose_name='الاسم (عربي)'),
),
migrations.AlterField(
model_name='governorate',
name='name_en',
field=models.CharField(default='', max_length=100, verbose_name='الاسم (إنجليزي)'),
),
migrations.AlterField(
model_name='platformsettings',
name='address',
field=models.TextField(blank=True, null=True, verbose_name='العنوان'),
),
migrations.AlterField(
model_name='platformsettings',
name='contact_email',
field=models.EmailField(blank=True, max_length=254, null=True, verbose_name='بريد التواصل'),
),
migrations.AlterField(
model_name='platformsettings',
name='contact_phone',
field=models.CharField(blank=True, max_length=20, null=True, verbose_name='هاتف التواصل'),
),
migrations.AlterField(
model_name='platformsettings',
name='description',
field=models.TextField(blank=True, null=True, verbose_name='وصف المنصة'),
),
migrations.AlterField(
model_name='platformsettings',
name='facebook_link',
field=models.URLField(blank=True, null=True, verbose_name='رابط فيسبوك'),
),
migrations.AlterField(
model_name='platformsettings',
name='instagram_link',
field=models.URLField(blank=True, null=True, verbose_name='رابط انستغرام'),
),
migrations.AlterField(
model_name='platformsettings',
name='logo',
field=models.ImageField(blank=True, null=True, upload_to='platform/', verbose_name='الشعار'),
),
migrations.AlterField(
model_name='platformsettings',
name='name',
field=models.CharField(default='منصتي التعليمية', max_length=100, verbose_name='اسم المنصة'),
),
migrations.AlterField(
model_name='platformsettings',
name='twitter_link',
field=models.URLField(blank=True, null=True, verbose_name='رابط تويتر'),
),
migrations.AlterField(
model_name='resource',
name='file',
field=models.FileField(blank=True, null=True, upload_to='resources/', verbose_name='الملف'),
),
migrations.AlterField(
model_name='resource',
name='link',
field=models.URLField(blank=True, help_text='رابط YouTube للفيديو، أو رابط خارجي.', verbose_name='الرابط'),
),
migrations.AlterField(
model_name='resource',
name='resource_type',
field=models.CharField(choices=[('FILE', 'ملف (PDF, Doc, إلخ)'), ('VIDEO', 'فيديو (YouTube)'), ('LINK', 'رابط خارجي')], default='FILE', max_length=10, verbose_name='نوع المصدر'),
),
migrations.AlterField(
model_name='resource',
name='subject',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='resources', to='core.subject', verbose_name='المادة'),
),
migrations.AlterField(
model_name='resource',
name='title_ar',
field=models.CharField(max_length=200, verbose_name='العنوان (عربي)'),
),
migrations.AlterField(
model_name='resource',
name='title_en',
field=models.CharField(max_length=200, verbose_name='العنوان (إنجليزي)'),
),
migrations.AlterField(
model_name='student',
name='avatar',
field=models.ImageField(blank=True, null=True, upload_to='students/', verbose_name='الصورة الشخصية'),
),
migrations.AlterField(
model_name='student',
name='city',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='core.city', verbose_name='المدينة'),
),
migrations.AlterField(
model_name='student',
name='classroom',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='students', to='core.classroom', verbose_name='الصف'),
),
migrations.AlterField(
model_name='student',
name='email_otp_code',
field=models.CharField(blank=True, max_length=6, null=True, verbose_name='رمز تفعيل البريد'),
),
migrations.AlterField(
model_name='student',
name='governorate',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='core.governorate', verbose_name='المحافظة'),
),
migrations.AlterField(
model_name='student',
name='is_email_verified',
field=models.BooleanField(default=False, verbose_name='هل البريد مفعل؟'),
),
migrations.AlterField(
model_name='student',
name='mobile_number',
field=models.CharField(blank=True, max_length=20, verbose_name='رقم الجوال'),
),
migrations.AlterField(
model_name='student',
name='phone_number',
field=models.CharField(blank=True, max_length=20, verbose_name='رقم الهاتف الأرضي'),
),
migrations.AlterField(
model_name='student',
name='subscribed_subjects',
field=models.ManyToManyField(blank=True, related_name='subscribers', to='core.subject', verbose_name='المواد المسجلة'),
),
migrations.AlterField(
model_name='subject',
name='classroom',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='subjects', to='core.classroom', verbose_name='الصف'),
),
migrations.AlterField(
model_name='subject',
name='description_ar',
field=models.TextField(blank=True, verbose_name='الوصف (عربي)'),
),
migrations.AlterField(
model_name='subject',
name='description_en',
field=models.TextField(blank=True, verbose_name='الوصف (إنجليزي)'),
),
migrations.AlterField(
model_name='subject',
name='google_drive_link',
field=models.URLField(blank=True, verbose_name='رابط Google Drive'),
),
migrations.AlterField(
model_name='subject',
name='google_meet_link',
field=models.URLField(blank=True, verbose_name='رابط Google Meet'),
),
migrations.AlterField(
model_name='subject',
name='image',
field=models.ImageField(blank=True, null=True, upload_to='subjects/', verbose_name='الصورة'),
),
migrations.AlterField(
model_name='subject',
name='name_ar',
field=models.CharField(max_length=200, verbose_name='الاسم (عربي)'),
),
migrations.AlterField(
model_name='subject',
name='name_en',
field=models.CharField(max_length=200, verbose_name='الاسم (إنجليزي)'),
),
migrations.AlterField(
model_name='subject',
name='price',
field=models.DecimalField(decimal_places=2, default=0.0, max_digits=10, verbose_name='السعر'),
),
migrations.AlterField(
model_name='subject',
name='teachers',
field=models.ManyToManyField(blank=True, related_name='subjects', to='core.teacher', verbose_name='المعلمون'),
),
migrations.AlterField(
model_name='subject',
name='youtube_live_url',
field=models.URLField(blank=True, help_text='ضع رابط بث YouTube هنا للبثوث الكبيرة (500+ طالب).', verbose_name='رابط بث YouTube'),
),
migrations.AlterField(
model_name='teacher',
name='avatar',
field=models.ImageField(blank=True, null=True, upload_to='teachers/', verbose_name='الصورة الشخصية'),
),
migrations.AlterField(
model_name='teacher',
name='bio',
field=models.TextField(blank=True, verbose_name='نبذة'),
),
migrations.AlterField(
model_name='teacher',
name='specialization',
field=models.CharField(blank=True, max_length=255, verbose_name='التخصص'),
),
migrations.AlterField(
model_name='thawaniconfiguration',
name='api_key',
field=models.CharField(help_text='Thawani Secret Key', max_length=255, verbose_name='API Key'),
),
migrations.AlterField(
model_name='thawaniconfiguration',
name='is_sandbox',
field=models.BooleanField(default=True, help_text='اختر هذا الخيار لاستخدام بيئة التجربة', verbose_name='وضع التجربة (Sandbox)'),
),
migrations.AlterField(
model_name='thawaniconfiguration',
name='publishable_key',
field=models.CharField(help_text='Thawani Publishable Key', max_length=255, verbose_name='Publishable Key'),
),
migrations.AlterField(
model_name='wablasconfiguration',
name='api_token',
field=models.CharField(max_length=255, verbose_name='API Token'),
),
migrations.AlterField(
model_name='wablasconfiguration',
name='api_url',
field=models.URLField(default='https://texas.wablas.com', verbose_name='رابط API'),
),
migrations.AlterField(
model_name='wablasconfiguration',
name='secret_key',
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Secret Key'),
),
]

View File

@ -18,40 +18,48 @@ class SingletonModel(models.Model):
class Teacher(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='teacher_profile')
bio = models.TextField(_("Bio"), blank=True)
avatar = models.ImageField(_("Avatar"), upload_to='teachers/', blank=True, null=True)
specialization = models.CharField(_("Specialization"), max_length=255, blank=True)
bio = models.TextField("نبذة", blank=True)
avatar = models.ImageField("الصورة الشخصية", upload_to='teachers/', blank=True, null=True)
specialization = models.CharField("التخصص", max_length=255, blank=True)
class Meta:
verbose_name = "معلم"
verbose_name_plural = "المعلمون"
def __str__(self):
return self.user.get_full_name() or self.user.username
class Classroom(models.Model):
name_en = models.CharField(_("Name (English)"), max_length=100)
name_ar = models.CharField(_("Name (Arabic)"), max_length=100)
description = models.TextField(_("Description"), blank=True)
name_en = models.CharField("الاسم (إنجليزي)", max_length=100)
name_ar = models.CharField("الاسم (عربي)", max_length=100)
description = models.TextField("الوصف", blank=True)
class Meta:
verbose_name = _("Classroom")
verbose_name_plural = _("Classrooms")
verbose_name = "صف دراسي"
verbose_name_plural = "الصفوف الدراسية"
def __str__(self):
return self.name_en
return self.name_ar or self.name_en
class Subject(models.Model):
classroom = models.ForeignKey(Classroom, on_delete=models.CASCADE, related_name='subjects')
teachers = models.ManyToManyField(Teacher, blank=True, related_name='subjects')
name_en = models.CharField(_("Name (English)"), max_length=200)
name_ar = models.CharField(_("Name (Arabic)"), max_length=200)
description_en = models.TextField(_("Description (English)"), blank=True)
description_ar = models.TextField(_("Description (Arabic)"), blank=True)
price = models.DecimalField(_("Price"), max_digits=10, decimal_places=2, default=0.00)
image = models.ImageField(_("Image"), upload_to='subjects/', blank=True, null=True)
google_drive_link = models.URLField(_("Google Drive Link"), blank=True)
google_meet_link = models.URLField(_("Google Meet Link"), blank=True)
youtube_live_url = models.URLField(_("YouTube Live URL"), blank=True, help_text=_("Paste the YouTube Live link here for large broadcasts (500+ students)."))
classroom = models.ForeignKey(Classroom, on_delete=models.CASCADE, related_name='subjects', verbose_name="الصف")
teachers = models.ManyToManyField(Teacher, blank=True, related_name='subjects', verbose_name="المعلمون")
name_en = models.CharField("الاسم (إنجليزي)", max_length=200)
name_ar = models.CharField("الاسم (عربي)", max_length=200)
description_en = models.TextField("الوصف (إنجليزي)", blank=True)
description_ar = models.TextField("الوصف (عربي)", blank=True)
price = models.DecimalField("السعر", max_digits=10, decimal_places=2, default=0.00)
image = models.ImageField("الصورة", upload_to='subjects/', blank=True, null=True)
google_drive_link = models.URLField("رابط Google Drive", blank=True)
google_meet_link = models.URLField("رابط Google Meet", blank=True)
youtube_live_url = models.URLField("رابط بث YouTube", blank=True, help_text="ضع رابط بث YouTube هنا للبثوث الكبيرة (500+ طالب).")
class Meta:
verbose_name = "مادة دراسية"
verbose_name_plural = "المواد الدراسية"
def __str__(self):
return self.name_en
return self.name_ar or self.name_en
def get_youtube_id(self):
"""Extracts the video ID from a YouTube URL."""
@ -75,21 +83,25 @@ class Subject(models.Model):
class Resource(models.Model):
RESOURCE_TYPES = (
('FILE', _('File (PDF, Doc, etc.)')),
('VIDEO', _('Video Link (YouTube)')),
('LINK', _('External Link')),
('FILE', 'ملف (PDF, Doc, إلخ)'),
('VIDEO', 'فيديو (YouTube)'),
('LINK', 'رابط خارجي'),
)
subject = models.ForeignKey(Subject, on_delete=models.CASCADE, related_name='resources')
title_en = models.CharField(_("Title (English)"), max_length=200)
title_ar = models.CharField(_("Title (Arabic)"), max_length=200)
resource_type = models.CharField(_("Resource Type"), max_length=10, choices=RESOURCE_TYPES, default='FILE')
file = models.FileField(_("File"), upload_to='resources/', blank=True, null=True)
link = models.URLField(_("Link"), blank=True, help_text=_("YouTube URL for Video type, or external URL for Link type."))
subject = models.ForeignKey(Subject, on_delete=models.CASCADE, related_name='resources', verbose_name="المادة")
title_en = models.CharField("العنوان (إنجليزي)", max_length=200)
title_ar = models.CharField("العنوان (عربي)", max_length=200)
resource_type = models.CharField("نوع المصدر", max_length=10, choices=RESOURCE_TYPES, default='FILE')
file = models.FileField("الملف", upload_to='resources/', blank=True, null=True)
link = models.URLField("الرابط", blank=True, help_text="رابط YouTube للفيديو، أو رابط خارجي.")
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
verbose_name = "مصدر تعليمي"
verbose_name_plural = "المصادر التعليمية"
def __str__(self):
return self.title_en
return self.title_ar or self.title_en
def get_youtube_id(self):
"""Extracts the video ID from the link if it is a YouTube URL."""
@ -118,42 +130,46 @@ class Resource(models.Model):
return False
class Governorate(models.Model):
name_en = models.CharField(_("Name (English)"), max_length=100, default="")
name_ar = models.CharField(_("Name (Arabic)"), max_length=100, default="")
name_en = models.CharField("الاسم (إنجليزي)", max_length=100, default="")
name_ar = models.CharField("الاسم (عربي)", max_length=100, default="")
class Meta:
verbose_name = _("Governorate")
verbose_name_plural = _("Governorates")
verbose_name = "محافظة"
verbose_name_plural = "المحافظات"
def __str__(self):
return f"{self.name_en}"
return self.name_ar or f"{self.name_en}"
class City(models.Model):
governorate = models.ForeignKey(Governorate, on_delete=models.CASCADE, related_name='cities', verbose_name=_("Governorate"), null=True, blank=True)
name_en = models.CharField(_("Name (English)"), max_length=100, default="")
name_ar = models.CharField(_("Name (Arabic)"), max_length=100, default="")
governorate = models.ForeignKey(Governorate, on_delete=models.CASCADE, related_name='cities', verbose_name="المحافظة", null=True, blank=True)
name_en = models.CharField("الاسم (إنجليزي)", max_length=100, default="")
name_ar = models.CharField("الاسم (عربي)", max_length=100, default="")
class Meta:
verbose_name = _("City")
verbose_name_plural = _("Cities")
verbose_name = "مدينة"
verbose_name_plural = "المدن"
def __str__(self):
return f"{self.name_en}"
return self.name_ar or f"{self.name_en}"
class Student(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='student_profile')
classroom = models.ForeignKey(Classroom, on_delete=models.SET_NULL, null=True, blank=True, related_name='students')
phone_number = models.CharField(_("Phone Number"), max_length=20, blank=True)
mobile_number = models.CharField(_("Mobile Number"), max_length=20, blank=True)
subscribed_subjects = models.ManyToManyField(Subject, blank=True, related_name='subscribers')
classroom = models.ForeignKey(Classroom, on_delete=models.SET_NULL, null=True, blank=True, related_name='students', verbose_name="الصف")
phone_number = models.CharField("رقم الهاتف الأرضي", max_length=20, blank=True)
mobile_number = models.CharField("رقم الجوال", max_length=20, blank=True)
subscribed_subjects = models.ManyToManyField(Subject, blank=True, related_name='subscribers', verbose_name="المواد المسجلة")
governorate = models.ForeignKey(Governorate, on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_("Governorate"))
city = models.ForeignKey(City, on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_("City"))
avatar = models.ImageField(_("Picture"), upload_to='students/', blank=True, null=True)
governorate = models.ForeignKey(Governorate, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="المحافظة")
city = models.ForeignKey(City, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="المدينة")
avatar = models.ImageField("الصورة الشخصية", upload_to='students/', blank=True, null=True)
# Email Verification
email_otp_code = models.CharField(max_length=6, blank=True, null=True)
is_email_verified = models.BooleanField(default=False)
email_otp_code = models.CharField("رمز تفعيل البريد", max_length=6, blank=True, null=True)
is_email_verified = models.BooleanField("هل البريد مفعل؟", default=False)
class Meta:
verbose_name = "طالب"
verbose_name_plural = "الطلاب"
def __str__(self):
return self.user.get_full_name() or self.user.username
@ -161,45 +177,45 @@ class Student(models.Model):
# --- Configuration Models ---
class WablasConfiguration(SingletonModel):
api_url = models.URLField(default="https://texas.wablas.com")
api_token = models.CharField(max_length=255)
secret_key = models.CharField(max_length=255, blank=True, null=True)
api_url = models.URLField("رابط API", default="https://texas.wablas.com")
api_token = models.CharField("API Token", max_length=255)
secret_key = models.CharField("Secret Key", max_length=255, blank=True, null=True)
class Meta:
verbose_name = "Wablas Configuration"
verbose_name_plural = "Wablas Configuration"
verbose_name = "إعدادات Wablas"
verbose_name_plural = "إعدادات Wablas"
def __str__(self):
return "Wablas Configuration"
return "إعدادات Wablas"
class ThawaniConfiguration(SingletonModel):
api_key = models.CharField(max_length=255, help_text="Thawani Secret Key")
publishable_key = models.CharField(max_length=255, help_text="Thawani Publishable Key")
is_sandbox = models.BooleanField(default=True, help_text="Check to use Sandbox environment")
api_key = models.CharField("API Key", max_length=255, help_text="Thawani Secret Key")
publishable_key = models.CharField("Publishable Key", max_length=255, help_text="Thawani Publishable Key")
is_sandbox = models.BooleanField("وضع التجربة (Sandbox)", default=True, help_text="اختر هذا الخيار لاستخدام بيئة التجربة")
class Meta:
verbose_name = "Thawani Configuration"
verbose_name_plural = "Thawani Configuration"
verbose_name = "إعدادات ثواني"
verbose_name_plural = "إعدادات ثواني"
def __str__(self):
return "Thawani Configuration"
return "إعدادات ثواني"
class PlatformSettings(SingletonModel):
name = models.CharField(max_length=100, default="My School Platform")
logo = models.ImageField(upload_to='platform/', blank=True, null=True)
description = models.TextField(blank=True, null=True)
contact_email = models.EmailField(blank=True, null=True)
contact_phone = models.CharField(max_length=20, blank=True, null=True)
address = models.TextField(blank=True, null=True)
name = models.CharField("اسم المنصة", max_length=100, default="منصتي التعليمية")
logo = models.ImageField("الشعار", upload_to='platform/', blank=True, null=True)
description = models.TextField("وصف المنصة", blank=True, null=True)
contact_email = models.EmailField("بريد التواصل", blank=True, null=True)
contact_phone = models.CharField("هاتف التواصل", max_length=20, blank=True, null=True)
address = models.TextField("العنوان", blank=True, null=True)
# Social Media
facebook_link = models.URLField(blank=True, null=True)
twitter_link = models.URLField(blank=True, null=True)
instagram_link = models.URLField(blank=True, null=True)
facebook_link = models.URLField("رابط فيسبوك", blank=True, null=True)
twitter_link = models.URLField("رابط تويتر", blank=True, null=True)
instagram_link = models.URLField("رابط انستغرام", blank=True, null=True)
class Meta:
verbose_name = "Platform Profile"
verbose_name_plural = "Platform Profile"
verbose_name = "إعدادات المنصة"
verbose_name_plural = "إعدادات المنصة"
def __str__(self):
return "Platform Profile"
return "إعدادات المنصة"

View File

@ -1,17 +1,14 @@
{% load i18n static %}
<!DOCTYPE html>
<html lang="{{ LANGUAGE_CODE }}" dir="{% if LANGUAGE_BIDI %}rtl{% else %}ltr{% endif %}">
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}{% trans "E-Learning Platform" %}{% endblock %}</title>
<title>{% block title %}منصة التعليم الإلكتروني{% endblock %}</title>
<!-- Bootstrap 5 CDN -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
{% if LANGUAGE_BIDI %}
<!-- Bootstrap 5 CDN (RTL) -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.rtl.min.css" rel="stylesheet">
{% endif %}
<!-- Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
@ -30,7 +27,7 @@
}
body {
font-family: 'Outfit', 'Noto Kufi Arabic', sans-serif;
font-family: 'Noto Kufi Arabic', 'Outfit', sans-serif;
background-color: var(--bg-light);
color: #333;
overflow-x: hidden;
@ -88,7 +85,7 @@
content: '';
position: absolute;
top: 0;
right: 0;
right: 0; /* RTL adjusted */
width: 300px;
height: 300px;
background: radial-gradient(circle, rgba(45, 49, 250, 0.1) 0%, transparent 70%);
@ -125,44 +122,35 @@
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link" href="#">{% trans "Courses" %}</a>
<a class="nav-link" href="#">الدورات</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">{% trans "Teachers" %}</a>
<a class="nav-link" href="#">المعلمون</a>
</li>
{% if user.is_staff %}
<li class="nav-item">
<a class="nav-link" href="/admin/">{% trans "Admin" %}</a>
<a class="nav-link" href="/admin/">المسؤول</a>
</li>
{% endif %}
</ul>
<div class="d-flex align-items-center">
<div class="dropdown me-3">
<button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown">
{% if LANGUAGE_CODE == 'ar' %}العربية{% else %}English{% endif %}
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-menu-item dropdown-item" href="{% url 'set_language' 'en' %}?next={{ request.path }}">English</a></li>
<li><a class="dropdown-menu-item dropdown-item" href="{% url 'set_language' 'ar' %}?next={{ request.path }}">العربية</a></li>
</ul>
</div>
{% if user.is_authenticated %}
<div class="dropdown ms-2">
<button class="btn btn-link text-decoration-none dropdown-toggle text-dark d-flex align-items-center" type="button" id="userDropdown" data-bs-toggle="dropdown" aria-expanded="false">
<span class="fw-semibold">{{ user.username }}</span>
</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' %}">{% trans "Profile" %}</a></li>
<li><a class="dropdown-item py-2" href="{% url 'profile' %}">الملف الشخصي</a></li>
{% if user.is_staff %}
<li><a class="dropdown-item py-2" href="/admin/">{% trans "Admin Panel" %}</a></li>
<li><a class="dropdown-item py-2" href="/admin/">لوحة تحكم المسؤول</a></li>
{% endif %}
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item py-2 text-danger" href="{% url 'logout' %}">{% trans "Logout" %}</a></li>
<li><a class="dropdown-item py-2 text-danger" href="{% url 'logout' %}">تسجيل الخروج</a></li>
</ul>
</div>
{% else %}
<a href="{% url 'register_student' %}" class="btn btn-outline-primary me-2">{% trans "Register" %}</a>
<a href="{% url 'login' %}" class="btn btn-primary">{% trans "Login" %}</a>
<a href="{% url 'register_student' %}" class="btn btn-outline-primary me-2">تسجيل</a>
<a href="{% url 'login' %}" class="btn btn-primary">دخول</a>
{% endif %}
</div>
</div>
@ -173,7 +161,7 @@
<footer class="footer mt-5">
<div class="container text-center">
<p>&copy; 2026 EduPlatform. {% trans "All rights reserved." %}</p>
<p>&copy; 2026 EduPlatform. جميع الحقوق محفوظة.</p>
</div>
</footer>

View File

@ -0,0 +1,41 @@
{% extends 'base.html' %}
{% load i18n static %}
{% block title %}إضافة مصدر جديد - {{ subject.name_ar }}{% 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">
<div class="text-center mb-4">
<h3 class="fw-bold text-primary">إضافة مصدر جديد</h3>
<p class="text-muted">للمادة: {{ subject.name_ar }}</p>
</div>
<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">{{ field.help_text }}</div>
{% endif %}
{% for error in field.errors %}
<div class="text-danger small">{{ error }}</div>
{% endfor %}
</div>
{% endfor %}
<div class="d-grid gap-2 mt-4">
<button type="submit" class="btn btn-primary rounded-pill btn-lg">حفظ المصدر</button>
<a href="{% url 'subject_detail' subject.id %}" class="btn btn-outline-secondary rounded-pill">إلغاء</a>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,34 @@
{% extends 'base.html' %}
{% load i18n static %}
{% block title %}حذف المصدر - {{ resource.title_ar }}{% endblock %}
{% block content %}
<div class="container" style="margin-top: 100px; margin-bottom: 50px;">
<div class="row justify-content-center">
<div class="col-md-6 col-lg-5">
<div class="glass-card p-4 p-md-5 text-center">
<div class="mb-4 text-danger">
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill="currentColor" class="bi bi-exclamation-circle" viewBox="0 0 16 16">
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/>
<path d="M7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0zM7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 4.995z"/>
</svg>
</div>
<h3 class="fw-bold mb-3">هل أنت متأكد؟</h3>
<p class="text-muted mb-4">
أنت على وشك حذف المصدر <strong>"{{ resource.title_ar }}"</strong>. لا يمكن التراجع عن هذا الإجراء.
</p>
<form method="post">
{% csrf_token %}
<div class="d-grid gap-2">
<button type="submit" class="btn btn-danger rounded-pill btn-lg">نعم، احذف المصدر</button>
<a href="{% url 'subject_detail' resource.subject.id %}" class="btn btn-outline-secondary rounded-pill">إلغاء</a>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,41 @@
{% extends 'base.html' %}
{% load i18n static %}
{% block title %}تعديل المصدر - {{ resource.title_ar }}{% 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">
<div class="text-center mb-4">
<h3 class="fw-bold text-primary">تعديل المصدر</h3>
<p class="text-muted">المادة: {{ subject.name_ar }}</p>
</div>
<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">{{ field.help_text }}</div>
{% endif %}
{% for error in field.errors %}
<div class="text-danger small">{{ error }}</div>
{% endfor %}
</div>
{% endfor %}
<div class="d-grid gap-2 mt-4">
<button type="submit" class="btn btn-primary rounded-pill btn-lg">حفظ التغييرات</button>
<a href="{% url 'subject_detail' subject.id %}" class="btn btn-outline-secondary rounded-pill">إلغاء</a>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,59 @@
{% 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="card shadow border-0 rounded-4">
<div class="card-body p-4 p-md-5">
<h3 class="fw-bold text-center mb-4">تعديل الملف الشخصي</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>
</div>
{% endblock %}

View File

@ -1,11 +1,12 @@
{% extends 'base.html' %}
{% load i18n static %}
{% block content %}
<div class="container mt-5">
<div class="alert alert-danger">
<h4>Error</h4>
<h4>خطأ</h4>
<p>{{ message }}</p>
<a href="{% url 'profile' %}" class="btn btn-secondary">Go Back to Profile</a>
<a href="{% url 'profile' %}" class="btn btn-secondary">العودة للملف الشخصي</a>
</div>
</div>
{% endblock %}
{% endblock %}

View File

@ -1,7 +1,7 @@
{% extends "base.html" %}
{% load i18n static %}
{% block title %}{% trans "Home" %} | EduPlatform{% endblock %}
{% block title %}الرئيسية | EduPlatform{% endblock %}
{% block content %}
<section class="hero-section">
@ -9,22 +9,14 @@
<div class="row align-items-center">
<div class="col-lg-6">
<h1 class="display-4 fw-bold mb-4">
{% if LANGUAGE_CODE == 'ar' %}
تعلم من أفضل المعلمين في أي وقت وأي مكان
{% else %}
Learn from the best teachers anytime, anywhere
{% endif %}
</h1>
<p class="lead mb-5 text-muted">
{% if LANGUAGE_CODE == 'ar' %}
منصة تعليمية متكاملة تدعم الطلاب والمعلمين مع دروس حية ومصادر تعليمية متميزة.
{% else %}
A comprehensive educational platform supporting students and teachers with live classes and premium educational resources.
{% endif %}
</p>
<div class="d-flex gap-3">
<a href="#levels-section" class="btn btn-primary btn-lg px-5">{% trans "Get Started" %}</a>
<a href="#" class="btn btn-outline-secondary btn-lg px-5">{% trans "Learn More" %}</a>
<a href="#levels-section" class="btn btn-primary btn-lg px-5">ابدأ الآن</a>
<a href="#" class="btn btn-outline-secondary btn-lg px-5">اعرف المزيد</a>
</div>
</div>
<div class="col-lg-6 d-none d-lg-block">
@ -32,8 +24,8 @@
<div style="width: 400px; height: 400px; background: var(--primary-color); border-radius: 30% 70% 70% 30% / 30% 30% 70% 70%; opacity: 0.1; position: absolute; top: -50px; left: 50px;"></div>
<div style="width: 300px; height: 300px; background: var(--secondary-color); border-radius: 60% 40% 30% 70% / 60% 30% 70% 40%; opacity: 0.1; position: absolute; bottom: -50px; right: 50px;"></div>
<div class="glass-card text-center position-relative z-1">
<h3 class="fw-bold mb-3">{% trans "Ready to start?" %}</h3>
<p>{% trans "Join thousands of students learning today." %}</p>
<h3 class="fw-bold mb-3">مستعد للبدء؟</h3>
<p>انضم لآلاف الطلاب اليوم.</p>
<div class="avatar-group d-flex justify-content-center mt-4">
<div style="width: 40px; height: 40px; border-radius: 50%; background: #ccc; border: 2px solid #fff; margin-left: -10px;"></div>
<div style="width: 40px; height: 40px; border-radius: 50%; background: #ddd; border: 2px solid #fff; margin-left: -10px;"></div>
@ -49,8 +41,8 @@
<section id="levels-section" class="subjects-section py-5">
<div class="container">
<div class="text-center mb-5">
<h2 class="fw-bold">{% trans "Our Educational Programs" %}</h2>
<p class="text-muted">{% trans "Explore subjects tailored for each educational level." %}</p>
<h2 class="fw-bold">برامجنا التعليمية</h2>
<p class="text-muted">استكشف المواد المخصصة لكل مرحلة دراسية.</p>
</div>
{% for level in levels %}
@ -60,7 +52,7 @@
{{ forloop.counter }}
</div>
<h3 class="fw-bold mb-0">
{% if LANGUAGE_CODE == 'ar' %}{{ level.name_ar }}{% else %}{{ level.name_en }}{% endif %}
{{ level.name_ar }}
</h3>
<hr class="flex-grow-1 ms-4 d-none d-md-block opacity-10">
</div>
@ -78,14 +70,14 @@
</div>
<div class="p-4">
<h4 class="fw-bold mb-3">
{% if LANGUAGE_CODE == 'ar' %}{{ subject.name_ar }}{% else %}{{ subject.name_en }}{% endif %}
{{ subject.name_ar }}
</h4>
<p class="text-muted small mb-4">
{% if LANGUAGE_CODE == 'ar' %}{{ subject.description_ar|truncatewords:15 }}{% else %}{{ subject.description_en|truncatewords:15 }}{% endif %}
{{ subject.description_ar|truncatewords:15 }}
</p>
<div class="d-flex justify-content-between align-items-center">
<span class="h5 fw-bold mb-0 text-primary">${{ subject.price }}</span>
<a href="{% url 'subject_detail' subject.pk %}" class="btn btn-primary btn-sm">{% trans "Subscribe" %}</a>
<a href="{% url 'subject_detail' subject.pk %}" class="btn btn-primary btn-sm">اشتراك</a>
</div>
</div>
</div>
@ -93,7 +85,7 @@
{% empty %}
<div class="col-12">
<div class="alert alert-light border-0 text-center py-4">
{% trans "No subjects available for this level yet." %}
لا توجد مواد متاحة لهذا المستوى بعد.
</div>
</div>
{% endfor %}
@ -107,8 +99,8 @@
<section id="teachers-section" class="py-5 bg-light">
<div class="container">
<div class="text-center mb-5">
<h2 class="fw-bold">{% trans "Meet Our Teachers" %}</h2>
<p class="text-muted">{% trans "Learn from our expert instructors." %}</p>
<h2 class="fw-bold">تعرف على معلمينا</h2>
<p class="text-muted">تعلم من نخبة من المعلمين.</p>
</div>
<div class="row g-4 justify-content-center">
@ -127,20 +119,16 @@
</div>
<h5 class="fw-bold mb-1">{{ teacher.user.get_full_name|default:teacher.user.username }}</h5>
<p class="text-primary small fw-bold mb-3">{{ teacher.specialization|default:"Teacher" }}</p>
<p class="text-primary small fw-bold mb-3">{{ teacher.specialization|default:"معلم" }}</p>
<p class="text-muted small mb-4">
{{ teacher.bio|default:"No bio available."|truncatewords:20 }}
{{ teacher.bio|default:"لا توجد نبذة."|truncatewords:20 }}
</p>
<div class="mt-auto">
<!-- Optional: Add social links or 'View Profile' button here if needed -->
</div>
</div>
</div>
{% empty %}
<div class="col-12 text-center">
<p class="text-muted">{% trans "No teachers found at the moment." %}</p>
<p class="text-muted">لا يوجد معلمين حالياً.</p>
</div>
{% endfor %}
</div>
@ -153,22 +141,22 @@
<div class="col-md-4">
<div class="p-4">
<div class="mb-3 text-primary h1">🎥</div>
<h5 class="fw-bold">{% trans "Live Classes" %}</h5>
<p class="text-muted">{% trans "Join interactive sessions via Google Meet." %}</p>
<h5 class="fw-bold">دروس مباشرة</h5>
<p class="text-muted">انضم لجلسات تفاعلية عبر Google Meet.</p>
</div>
</div>
<div class="col-md-4">
<div class="p-4">
<div class="mb-3 text-primary h1">📁</div>
<h5 class="fw-bold">{% trans "Resources" %}</h5>
<p class="text-muted">{% trans "Access course materials on Google Drive." %}</p>
<h5 class="fw-bold">المصادر</h5>
<p class="text-muted">الوصول لمواد الدورة عبر Google Drive.</p>
</div>
</div>
<div class="col-md-4">
<div class="p-4">
<div class="mb-3 text-primary h1">💳</div>
<h5 class="fw-bold">{% trans "Easy Payments" %}</h5>
<p class="text-muted">{% trans "Secure payments with Thawani gateway." %}</p>
<h5 class="fw-bold">دفع سهل</h5>
<p class="text-muted">دفع آمن عبر بوابة ثواني.</p>
</div>
</div>
</div>

View File

@ -1,7 +1,7 @@
{% extends 'base.html' %}
{% load i18n static %}
{% block title %}{% trans "Live Classroom" %} - {{ subject.name_en }}{% endblock %}
{% block title %}الفصل المباشر - {{ subject.name_ar }}{% endblock %}
{% block content %}
<div class="container-fluid p-0" style="height: calc(100vh - 76px); margin-top: 76px;">
@ -12,10 +12,10 @@
<!-- Google Meet Mode (Highest Priority) -->
<div class="d-flex flex-column align-items-center justify-content-center h-100 bg-light">
<div class="text-center p-5 bg-white shadow rounded">
<h2 class="mb-4">{% trans "Live Class via Google Meet" %}</h2>
<p class="mb-4 text-muted">{% trans "This class is being held on Google Meet. Click the button below to join." %}</p>
<h2 class="mb-4">درس مباشر عبر Google Meet</h2>
<p class="mb-4 text-muted">يقام هذا الدرس عبر Google Meet. انقر الزر أدناه للانضمام.</p>
<a href="{{ subject.google_meet_link }}" target="_blank" class="btn btn-primary btn-lg">
<i class="fas fa-video me-2"></i> {% trans "Join Google Meet" %}
<i class="fas fa-video me-2"></i> انضم إلى Google Meet
</a>
</div>
</div>

View File

@ -1,7 +1,7 @@
{% extends 'base.html' %}
{% load i18n static %}
{% block title %}{% trans "Login" %}{% endblock %}
{% block title %}تسجيل الدخول{% endblock %}
{% block content %}
<div class="container hero-section">
@ -9,8 +9,8 @@
<div class="col-md-6 col-lg-5">
<div class="glass-card p-5">
<div class="text-center mb-4">
<h2 class="text-primary fw-bold">{% trans "Welcome Back" %}</h2>
<p class="text-muted">{% trans "Sign in to continue your learning journey" %}</p>
<h2 class="text-primary fw-bold">مرحباً بعودتك</h2>
<p class="text-muted">سجل الدخول لمتابعة رحلتك التعليمية</p>
</div>
<form method="post">
@ -18,29 +18,29 @@
{% if form.errors %}
<div class="alert alert-danger alert-dismissible fade show" role="alert">
{% trans "Invalid username or password." %}
اسم المستخدم أو كلمة المرور غير صحيحة.
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endif %}
<div class="form-floating mb-3">
<input type="text" name="username" autofocus autocapitalize="none" autocomplete="username" maxlength="150" required id="id_username" class="form-control" placeholder="{% trans 'Username' %}">
<label for="id_username"><i class="fas fa-user me-2"></i>{% trans "Username" %}</label>
<input type="text" name="username" autofocus autocapitalize="none" autocomplete="username" maxlength="150" required id="id_username" class="form-control" placeholder="اسم المستخدم">
<label for="id_username"><i class="fas fa-user me-2"></i>اسم المستخدم</label>
</div>
<div class="form-floating mb-4">
<input type="password" name="password" autocomplete="current-password" required id="id_password" class="form-control" placeholder="{% trans 'Password' %}">
<label for="id_password"><i class="fas fa-lock me-2"></i>{% trans "Password" %}</label>
<input type="password" name="password" autocomplete="current-password" required id="id_password" class="form-control" placeholder="كلمة المرور">
<label for="id_password"><i class="fas fa-lock me-2"></i>كلمة المرور</label>
</div>
<button type="submit" class="btn btn-primary w-100 py-3 fs-5 fw-bold shadow-sm">
{% trans "Login" %} <i class="fas fa-arrow-right ms-2"></i>
دخول <i class="fas fa-arrow-left ms-2"></i>
</button>
<div class="text-center mt-4">
<p class="text-muted mb-0">
{% trans "Don't have an account?" %}
<a href="{% url 'register_student' %}" class="text-primary text-decoration-none fw-bold">{% trans "Register Here" %}</a>
ليس لديك حساب؟
<a href="{% url 'register_student' %}" class="text-primary text-decoration-none fw-bold">سجل هنا</a>
</p>
</div>
</form>

View File

@ -1,7 +1,7 @@
{% extends 'base.html' %}
{% load i18n static %}
{% block title %}{% trans "My Profile" %} | EduPlatform{% endblock %}
{% block title %}ملفي الشخصي | EduPlatform{% endblock %}
{% block content %}
<section class="hero-section text-center pt-5 pb-5">
@ -21,9 +21,9 @@
<h2 class="mb-1">{{ user.get_full_name|default:user.username }}</h2>
<p class="text-muted mb-0">{{ user.email }}</p>
{% if student_profile and student_profile.classroom %}
<span class="badge bg-primary">{{ student_profile.classroom.name_en }}</span>
<span class="badge bg-primary">{{ student_profile.classroom.name_ar }}</span>
{% elif user.is_staff %}
<span class="badge bg-success">{% trans "Staff / Admin" %}</span>
<span class="badge bg-success">طاقم / مسؤول</span>
{% endif %}
</div>
</div>
@ -31,42 +31,42 @@
<hr class="my-4">
{% if student_profile %}
<h4 class="mb-3">{% trans "Student Details" %}</h4>
<h4 class="mb-3">تفاصيل الطالب</h4>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label text-muted">{% trans "Username" %}</label>
<label class="form-label text-muted">اسم المستخدم</label>
<p class="fw-bold">{{ user.username }}</p>
</div>
<div class="col-md-6 mb-3">
<label class="form-label text-muted">{% trans "City" %}</label>
<p class="fw-bold">{{ student_profile.city|default:"-" }}</p>
<label class="form-label text-muted">المدينة</label>
<p class="fw-bold">{{ student_profile.city.name_ar|default:"-" }}</p>
</div>
<div class="col-md-6 mb-3">
<label class="form-label text-muted">{% trans "Governorate" %}</label>
<p class="fw-bold">{{ student_profile.governorate|default:"-" }}</p>
<label class="form-label text-muted">المحافظة</label>
<p class="fw-bold">{{ student_profile.governorate.name_ar|default:"-" }}</p>
</div>
<div class="col-md-6 mb-3">
<label class="form-label text-muted">{% trans "Mobile Number" %}</label>
<label class="form-label text-muted">رقم الهاتف</label>
<p class="fw-bold">{{ student_profile.mobile_number|default:"-" }}</p>
</div>
</div>
<h4 class="mb-3 mt-4">{% trans "My Subjects" %}</h4>
<h4 class="mb-3 mt-4">موادي</h4>
{% if student_profile.subscribed_subjects.all %}
<div class="list-group">
{% for subject in student_profile.subscribed_subjects.all %}
<a href="{% url 'subject_detail' subject.pk %}" class="list-group-item list-group-item-action d-flex justify-content-between align-items-center">
{{ subject.name_en }}
<span class="badge bg-secondary rounded-pill">{% trans "View" %}</span>
{{ subject.name_ar }}
<span class="badge bg-secondary rounded-pill">عرض</span>
</a>
{% endfor %}
</div>
{% else %}
<p class="text-muted">{% trans "No subjects subscribed yet." %}</p>
<p class="text-muted">لم يتم الاشتراك في أي مواد بعد.</p>
{% endif %}
{% else %}
<div class="alert alert-info">
{% trans "You are logged in as a staff member or user without a student profile." %}
أنت مسجل دخول كعضو طاقم أو مستخدم بدون ملف طالب.
</div>
{% endif %}
</div>
@ -74,4 +74,4 @@
</div>
</div>
</section>
{% endblock %}
{% endblock %}

View File

@ -1,7 +1,7 @@
{% extends 'base.html' %}
{% load i18n static %}
{% block title %}{% trans "Student Registration" %}{% endblock %}
{% block title %}تسجيل الطالب{% endblock %}
{% block content %}
<div class="container hero-section">
@ -9,8 +9,8 @@
<div class="col-lg-8">
<div class="glass-card p-4 p-md-5">
<div class="text-center mb-5">
<h2 class="text-primary fw-bold">{% trans "Create Your Account" %}</h2>
<p class="text-muted">{% trans "Join our learning platform today" %}</p>
<h2 class="text-primary fw-bold">أنشئ حسابك</h2>
<p class="text-muted">انضم إلى منصتنا التعليمية اليوم</p>
</div>
<form method="post" enctype="multipart/form-data" id="registrationForm" class="needs-validation">
@ -34,34 +34,34 @@
<!-- Section: Personal Info -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-body p-4">
<h5 class="card-title text-primary mb-3"><i class="fas fa-user-circle me-2"></i>{% trans "Personal Information" %}</h5>
<h5 class="card-title text-primary mb-3"><i class="fas fa-user-circle me-2"></i>المعلومات الشخصية</h5>
<div class="form-floating mb-3">
{{ form.full_name }}
<label for="{{ form.full_name.id_for_label }}">{% trans "Full Name" %}</label>
<label for="{{ form.full_name.id_for_label }}">الاسم الكامل</label>
</div>
<div class="form-floating mb-3">
{{ form.username }}
<label for="{{ form.username.id_for_label }}">{% trans "Username" %}</label>
<label for="{{ form.username.id_for_label }}">اسم المستخدم</label>
</div>
<div class="form-floating mb-3">
{{ form.email }}
<label for="{{ form.email.id_for_label }}">{% trans "Email Address" %}</label>
<label for="{{ form.email.id_for_label }}">البريد الإلكتروني</label>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<div class="form-floating">
{{ form.password }}
<label for="{{ form.password.id_for_label }}">{% trans "Password" %}</label>
<label for="{{ form.password.id_for_label }}">كلمة المرور</label>
</div>
</div>
<div class="col-md-6 mb-3">
<div class="form-floating">
{{ form.password_confirm }}
<label for="{{ form.password_confirm.id_for_label }}">{% trans "Confirm Password" %}</label>
<label for="{{ form.password_confirm.id_for_label }}">تأكيد كلمة المرور</label>
</div>
</div>
</div>
@ -71,24 +71,24 @@
<!-- Section: Student Details -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-body p-4">
<h5 class="card-title text-primary mb-3"><i class="fas fa-id-card me-2"></i>{% trans "Student Profile" %}</h5>
<h5 class="card-title text-primary mb-3"><i class="fas fa-id-card me-2"></i>ملف الطالب</h5>
<div class="row">
<div class="col-md-6 mb-3">
<div class="form-floating">
{{ form.mobile_number }}
<label for="{{ form.mobile_number.id_for_label }}">{% trans "Mobile Number" %}</label>
<label for="{{ form.mobile_number.id_for_label }}">رقم الهاتف</label>
</div>
</div>
<div class="col-md-6 mb-3">
<div class="form-floating">
{{ form.governorate }}
<label for="{{ form.governorate.id_for_label }}">{% trans "Governorate" %}</label>
<label for="{{ form.governorate.id_for_label }}">المحافظة</label>
</div>
</div>
<div class="col-md-12 mb-3">
<div class="form-floating">
{{ form.city }}
<label for="{{ form.city.id_for_label }}">{% trans "City" %}</label>
<label for="{{ form.city.id_for_label }}">المدينة</label>
</div>
</div>
</div>
@ -98,21 +98,21 @@
<!-- Section: Education -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-body p-4">
<h5 class="card-title text-primary mb-3"><i class="fas fa-graduation-cap me-2"></i>{% trans "Education" %}</h5>
<h5 class="card-title text-primary mb-3"><i class="fas fa-graduation-cap me-2"></i>التعليم</h5>
<div class="mb-3">
<label class="form-label text-muted small">{% trans "Select Classroom" %}</label>
<label class="form-label text-muted small">اختر الصف الدراسي</label>
{{ form.classroom }}
</div>
<div class="mb-3">
<label class="form-label text-muted small">{% trans "Select Subjects" %}</label>
<label class="form-label text-muted small">اختر المواد</label>
<div id="subjects-container" class="border p-3 rounded bg-light" style="min-height: 100px;">
<p class="text-muted text-center mt-3 small"><i class="fas fa-chalkboard me-1"></i> {% trans "Select a classroom first." %}</p>
<p class="text-muted text-center mt-3 small"><i class="fas fa-chalkboard me-1"></i> اختر الصف أولاً.</p>
</div>
</div>
<div class="d-flex justify-content-between align-items-center p-3 bg-primary bg-opacity-10 text-primary rounded">
<h5 class="m-0">{% trans "Total Amount:" %}</h5>
<h5 class="m-0">المبلغ الإجمالي:</h5>
<h4 class="m-0 fw-bold"><span id="total-amount">0.00</span></h4>
</div>
</div>
@ -121,35 +121,35 @@
<!-- Section: Photo -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-body p-4">
<h5 class="card-title text-primary mb-3"><i class="fas fa-camera me-2"></i>{% trans "Profile Picture" %}</h5>
<h5 class="card-title text-primary mb-3"><i class="fas fa-camera me-2"></i>الصورة الشخصية</h5>
<div class="row">
<div class="col-md-6 d-flex flex-column align-items-center mb-3 mb-md-0">
<label class="mb-2 text-muted small">{% trans "Webcam Capture" %}</label>
<label class="mb-2 text-muted small">التقاط بالكاميرا</label>
<div id="video-container" style="position: relative;" class="shadow-sm rounded overflow-hidden">
<video id="video" width="240" height="180" autoplay style="background: #000;"></video>
<button type="button" id="snap" class="btn btn-sm btn-light position-absolute bottom-0 start-50 translate-middle-x mb-2 rounded-pill px-3">
<i class="fas fa-camera"></i> {% trans "Snap" %}
<i class="fas fa-camera"></i> التقاط
</button>
</div>
</div>
<div class="col-md-6 d-flex flex-column align-items-center">
<label class="mb-2 text-muted small">{% trans "Preview" %}</label>
<label class="mb-2 text-muted small">معاينة</label>
<canvas id="canvas" width="240" height="180" style="display:none;"></canvas>
<div id="preview-placeholder" class="bg-light rounded d-flex align-items-center justify-content-center text-muted small" style="width: 240px; height: 180px; border: 2px dashed #ddd;">
{% trans "No photo" %}
لا توجد صورة
</div>
<img id="photo-preview" width="240" height="180" class="rounded shadow-sm" style="display:none; border: 2px solid #4ECDC4;">
</div>
</div>
<div class="mt-4">
<label class="form-label text-muted small">{% trans "Or upload file:" %}</label>
<label class="form-label text-muted small">أو رفع ملف:</label>
{{ form.avatar }}
</div>
</div>
</div>
<button type="submit" class="btn btn-primary w-100 py-3 fs-5 fw-bold shadow-sm rounded-pill">
{% trans "Complete Registration" %} <i class="fas fa-check-circle ms-2"></i>
إتمام التسجيل <i class="fas fa-check-circle ms-2"></i>
</button>
</form>
</div>
@ -169,7 +169,7 @@ document.addEventListener('DOMContentLoaded', function() {
classroomSelect.addEventListener('change', function() {
const classroomId = this.value;
if (!classroomId) {
subjectsContainer.innerHTML = '<p class="text-muted text-center mt-3 small"><i class="fas fa-chalkboard me-1"></i> {% trans "Select a classroom first." %}</p>';
subjectsContainer.innerHTML = '<p class="text-muted text-center mt-3 small"><i class="fas fa-chalkboard me-1"></i> اختر الصف أولاً.</p>';
updateTotal();
return;
}
@ -179,7 +179,7 @@ document.addEventListener('DOMContentLoaded', function() {
.then(data => {
subjectsContainer.innerHTML = '';
if (data.length === 0) {
subjectsContainer.innerHTML = '<p class="text-warning text-center small">{% trans "No subjects found for this classroom." %}</p>';
subjectsContainer.innerHTML = '<p class="text-warning text-center small">لا توجد مواد لهذا الصف.</p>';
} else {
data.forEach(subject => {
const price = parseFloat(subject.price) || 0;
@ -188,7 +188,7 @@ document.addEventListener('DOMContentLoaded', function() {
div.innerHTML = `
<input class="form-check-input subject-checkbox" type="checkbox" name="subjects" value="${subject.id}" id="subject_${subject.id}" data-price="${price}" checked style="transform: scale(1.2); margin-top: 0.3rem;">
<label class="form-check-label d-flex justify-content-between w-100 ms-2" for="subject_${subject.id}">
<span class="fw-medium">${subject.name_en}</span>
<span class="fw-medium">${subject.name_ar || subject.name_en}</span>
<span class="badge bg-primary rounded-pill align-self-center">${price.toFixed(2)}</span>
</label>
`;
@ -199,7 +199,7 @@ document.addEventListener('DOMContentLoaded', function() {
})
.catch(err => {
console.error('Error fetching subjects:', err);
subjectsContainer.innerHTML = '<p class="text-danger small">{% trans "Error loading subjects." %}</p>';
subjectsContainer.innerHTML = '<p class="text-danger small">خطأ في تحميل المواد.</p>';
});
});
@ -224,7 +224,7 @@ document.addEventListener('DOMContentLoaded', function() {
if (governorateSelect && citySelect) {
governorateSelect.addEventListener('change', function() {
const governorateId = this.value;
citySelect.innerHTML = '<option value="">{% trans "Loading..." %}</option>';
citySelect.innerHTML = '<option value="">جاري التحميل...</option>';
if (!governorateId) {
citySelect.innerHTML = '<option value="">---------</option>';
@ -235,17 +235,16 @@ document.addEventListener('DOMContentLoaded', function() {
.then(response => response.json())
.then(data => {
citySelect.innerHTML = '<option value="">---------</option>';
const currentLang = "{{ LANGUAGE_CODE }}";
data.forEach(city => {
const option = document.createElement('option');
option.value = city.id;
option.textContent = currentLang === 'ar' ? city.name_ar : city.name_en;
option.textContent = city.name_ar || city.name_en; // Prefer Arabic
citySelect.appendChild(option);
});
})
.catch(err => {
console.error('Error fetching cities:', err);
citySelect.innerHTML = '<option value="">{% trans "Error loading cities" %}</option>';
citySelect.innerHTML = '<option value="">خطأ في تحميل المدن</option>';
});
});
}
@ -275,10 +274,10 @@ document.addEventListener('DOMContentLoaded', function() {
video.play();
})
.catch(function(err) {
showCameraError("{% trans 'Camera access denied or unavailable.' %}");
showCameraError("تم رفض الوصول للكاميرا أو غير متاحة.");
});
} else {
showCameraError("{% trans 'Browser does not support camera.' %}");
showCameraError("المتصفح لا يدعم الكاميرا.");
}
if (snap) {
@ -301,4 +300,4 @@ document.addEventListener('DOMContentLoaded', function() {
}
});
</script>
{% endblock %}
{% endblock %}

View File

@ -1,15 +1,15 @@
{% extends 'base.html' %}
{% load i18n static %}
{% block title %}{% trans "Student Dashboard" %} - EduPlatform{% endblock %}
{% block title %}لوحة تحكم الطالب - EduPlatform{% endblock %}
{% block content %}
<div class="container" style="margin-top: 100px; margin-bottom: 50px;">
<!-- Welcome Header -->
<div class="row mb-4">
<div class="col-12">
<h2 class="fw-bold">{% trans "Welcome back," %} <span style="color: var(--primary-color);">{{ user.first_name|default:user.username }}</span>! 👋</h2>
<p class="text-muted">{% trans "Here is an overview of your learning journey." %}</p>
<h2 class="fw-bold">مرحباً بعودتك، <span style="color: var(--primary-color);">{{ user.first_name|default:user.username }}</span>! 👋</h2>
<p class="text-muted">إليك نظرة عامة على رحلتك التعليمية.</p>
</div>
</div>
@ -28,43 +28,43 @@
{% if student_profile.is_mobile_verified and student_profile.is_email_verified %}
<span class="position-absolute bottom-0 end-0 bg-success border border-white rounded-circle p-2" title="Verified Account">
<span class="visually-hidden">Verified</span>
<span class="visually-hidden">مؤكد</span>
</span>
{% endif %}
</div>
<h4 class="fw-bold mb-1">{{ user.get_full_name|default:user.username }}</h4>
<p class="text-muted mb-3">{% trans "Student" %} {% if student_profile.classroom %} • {{ student_profile.classroom.name_en }}{% endif %}</p>
<p class="text-muted mb-3">طالب {% if student_profile.classroom %} • {{ student_profile.classroom.name_ar }}{% endif %}</p>
<hr class="my-4">
<div class="text-start">
<div class="mb-3">
<small class="text-muted d-block">{% trans "Email" %}</small>
<small class="text-muted d-block">البريد الإلكتروني</small>
<span>{{ user.email }}</span>
{% if student_profile.is_email_verified %}
<span class="badge bg-success ms-1">{% trans "Verified" %}</span>
<span class="badge bg-success ms-1">مؤكد</span>
{% else %}
<span class="badge bg-warning text-dark ms-1">{% trans "Unverified" %}</span>
<span class="badge bg-warning text-dark ms-1">غير مؤكد</span>
{% endif %}
</div>
<div class="mb-3">
<small class="text-muted d-block">{% trans "Phone" %}</small>
<small class="text-muted d-block">رقم الهاتف</small>
<span>{{ student_profile.mobile_number|default:"-" }}</span>
{% if student_profile.is_mobile_verified %}
<span class="badge bg-success ms-1">{% trans "Verified" %}</span>
<span class="badge bg-success ms-1">مؤكد</span>
{% else %}
<span class="badge bg-warning text-dark ms-1">{% trans "Unverified" %}</span>
<span class="badge bg-warning text-dark ms-1">غير مؤكد</span>
{% endif %}
</div>
<div class="mb-3">
<small class="text-muted d-block">{% trans "City" %}</small>
<span>{{ student_profile.city.name|default:"-" }}</span>
<small class="text-muted d-block">المدينة</small>
<span>{{ student_profile.city.name_ar|default:"-" }}</span>
</div>
</div>
<div class="mt-4">
<a href="#" class="btn btn-outline-primary w-100 rounded-pill">{% trans "Edit Profile" %}</a>
<a href="#" class="btn btn-outline-primary w-100 rounded-pill">تعديل الملف الشخصي</a>
</div>
</div>
</div>
@ -76,7 +76,7 @@
<div class="col-md-6 mb-3 mb-md-0">
<div class="glass-card py-3 px-4 d-flex align-items-center justify-content-between">
<div>
<h6 class="text-muted mb-0">{% trans "Enrolled Subjects" %}</h6>
<h6 class="text-muted mb-0">المواد المسجلة</h6>
<h2 class="fw-bold mb-0 text-primary">{{ subscribed_subjects.count }}</h2>
</div>
<div class="bg-primary bg-opacity-10 p-3 rounded-circle text-primary">
@ -89,8 +89,8 @@
<div class="col-md-6">
<div class="glass-card py-3 px-4 d-flex align-items-center justify-content-between">
<div>
<h6 class="text-muted mb-0">{% trans "Classroom" %}</h6>
<h5 class="fw-bold mb-0 text-success">{{ student_profile.classroom.name_en|default:"Not assigned" }}</h5>
<h6 class="text-muted mb-0">الصف الدراسي</h6>
<h5 class="fw-bold mb-0 text-success">{{ student_profile.classroom.name_ar|default:"غير معين" }}</h5>
</div>
<div class="bg-success bg-opacity-10 p-3 rounded-circle text-success">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-mortarboard" viewBox="0 0 16 16">
@ -104,7 +104,7 @@
<!-- My Subjects Section -->
<div class="mb-4">
<h4 class="fw-bold mb-3">{% trans "My Subjects" %}</h4>
<h4 class="fw-bold mb-3">موادي</h4>
{% if subscribed_subjects %}
<div class="row g-4">
{% for subject in subscribed_subjects %}
@ -116,27 +116,27 @@
</div>
{% else %}
<div class="bg-light d-flex align-items-center justify-content-center" style="height: 150px;">
<span class="text-muted">{% trans "No Image" %}</span>
<span class="text-muted">لا توجد صورة</span>
</div>
{% endif %}
<div class="p-3">
<h5 class="fw-bold mb-2">{{ subject.name_en }}</h5>
<p class="text-muted small mb-3 text-truncate">{{ subject.description_en }}</p>
<h5 class="fw-bold mb-2">{{ subject.name_ar }}</h5>
<p class="text-muted small mb-3 text-truncate">{{ subject.description_ar }}</p>
<div class="d-flex justify-content-between align-items-center">
<span class="badge bg-primary bg-opacity-10 text-primary">
{% for teacher in subject.teachers.all %}
{{ teacher.user.get_full_name|default:teacher.user.username }}{% if not forloop.last %}, {% endif %}
{% empty %}
{% trans "No Teacher" %}
بدون معلم
{% endfor %}
</span>
<div>
<a href="{% url 'live_classroom' subject.id %}" class="btn btn-sm btn-danger rounded-pill me-1" title="{% trans 'Join Live Class' %}">
<a href="{% url 'live_classroom' subject.id %}" class="btn btn-sm btn-danger rounded-pill me-1" title="انضم للدرس المباشر">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-camera-video-fill" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M0 5a2 2 0 0 1 2-2h7.5a2 2 0 0 1 1.983 1.738l3.11-1.382A1 1 0 0 1 16 4.269v7.461a1 1 0 0 1-1.406.913l-3.111-1.382A2 2 0 0 1 9.5 13H2a2 2 0 0 1-2-2V5z"/>
</svg>
</a>
<a href="{% url 'subject_detail' subject.id %}" class="btn btn-sm btn-primary rounded-pill">{% trans "Go to Class" %}</a>
<a href="{% url 'subject_detail' subject.id %}" class="btn btn-sm btn-primary rounded-pill">الذهاب للصف</a>
</div>
</div>
</div>
@ -146,14 +146,14 @@
</div>
{% else %}
<div class="glass-card text-center py-5">
<h5 class="text-muted">{% trans "You haven't enrolled in any subjects yet." %}</h5>
<h5 class="text-muted">لم تسجل في أي مادة بعد.</h5>
</div>
{% endif %}
</div>
<!-- Available Subjects Section -->
<div class="mb-4">
<h4 class="fw-bold mb-3">{% trans "Available Subjects" %}</h4>
<h4 class="fw-bold mb-3">المواد المتاحة</h4>
{% if available_subjects %}
<div class="row g-4">
{% for subject in available_subjects %}
@ -165,15 +165,15 @@
</div>
{% else %}
<div class="bg-light d-flex align-items-center justify-content-center" style="height: 150px;">
<span class="text-muted">{% trans "No Image" %}</span>
<span class="text-muted">لا توجد صورة</span>
</div>
{% endif %}
<div class="p-3">
<h5 class="fw-bold mb-2">{{ subject.name_en }}</h5>
<p class="text-muted small mb-3 text-truncate">{{ subject.description_en }}</p>
<h5 class="fw-bold mb-2">{{ subject.name_ar }}</h5>
<p class="text-muted small mb-3 text-truncate">{{ subject.description_ar }}</p>
<div class="d-flex justify-content-between align-items-center">
<span class="fw-bold text-success">{{ subject.price }} OMR</span>
<a href="{% url 'subscribe_subject' subject.id %}" class="btn btn-sm btn-success rounded-pill">{% trans "Subscribe" %}</a>
<a href="{% url 'subscribe_subject' subject.id %}" class="btn btn-sm btn-success rounded-pill">اشتراك</a>
</div>
</div>
</div>
@ -182,7 +182,7 @@
</div>
{% else %}
<div class="glass-card text-center py-5">
<h5 class="text-muted">{% trans "No new subjects available." %}</h5>
<h5 class="text-muted">لا توجد مواد جديدة متاحة.</h5>
</div>
{% endif %}
</div>

View File

@ -2,19 +2,29 @@
{% load i18n static %}
{% block title %}
{% if LANGUAGE_CODE == 'ar' %}{{ subject.name_ar }}{% else %}{{ subject.name_en }}{% endif %} | EduPlatform
{{ subject.name_ar }} | EduPlatform
{% endblock %}
{% block content %}
<div class="container py-5">
<nav aria-label="breadcrumb" class="mb-4">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'index' %}">{% trans "Home" %}</a></li>
<li class="breadcrumb-item active" aria-current="page">
{% if LANGUAGE_CODE == 'ar' %}{{ subject.classroom.name_ar }}{% else %}{{ subject.classroom.name_en }}{% endif %}
</li>
</ol>
</nav>
<div class="d-flex justify-content-between align-items-center mb-4">
<nav aria-label="breadcrumb">
<ol class="breadcrumb mb-0">
<li class="breadcrumb-item"><a href="{% url 'index' %}">الرئيسية</a></li>
<li class="breadcrumb-item active" aria-current="page">
{{ subject.classroom.name_ar }}
</li>
</ol>
</nav>
{% if is_teacher %}
<a href="{% url 'profile' %}" class="btn btn-outline-secondary 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>
{% endif %}
</div>
<div class="row g-5">
<div class="col-lg-8">
@ -27,13 +37,13 @@
{% endif %}
<div class="position-absolute top-0 end-0 m-4">
<span class="badge bg-primary fs-6 px-3 py-2 shadow-sm">
{% if LANGUAGE_CODE == 'ar' %}{{ subject.classroom.name_ar }}{% else %}{{ subject.classroom.name_en }}{% endif %}
{{ subject.classroom.name_ar }}
</span>
</div>
</div>
<div class="p-4 p-md-5">
<h1 class="fw-bold mb-4">
{% if LANGUAGE_CODE == 'ar' %}{{ subject.name_ar }}{% else %}{{ subject.name_en }}{% endif %}
{{ subject.name_ar }}
</h1>
{% for teacher in subject.teachers.all %}
@ -42,33 +52,71 @@
{{ teacher.user.username|first|upper }}
</div>
<div>
<p class="mb-0 text-muted small">{% trans "Teacher" %}</p>
<p class="mb-0 text-muted small">المعلم</p>
<h6 class="fw-bold mb-0">{{ teacher }}</h6>
</div>
</div>
{% empty %}
<div class="d-flex align-items-center mb-3 p-3 rounded-3 bg-light">
<div>
<p class="mb-0 text-muted small">{% trans "Teacher" %}</p>
<h6 class="fw-bold mb-0">{% trans "Not Assigned" %}</h6>
<p class="mb-0 text-muted small">المعلم</p>
<h6 class="fw-bold mb-0">غير معين</h6>
</div>
</div>
{% endfor %}
<h4 class="fw-bold mb-3 mt-4">{% trans "About this Subject" %}</h4>
<div class="lead text-muted mb-5">
{% if LANGUAGE_CODE == 'ar' %}
{{ subject.description_ar|linebreaks }}
{% else %}
{{ subject.description_en|linebreaks }}
{% endif %}
{% if subject.youtube_live_url %}
<div class="mt-5 mb-5">
<div class="ratio ratio-16x9 rounded-4 overflow-hidden shadow-sm">
<iframe src="https://www.youtube.com/embed/{{ subject.get_youtube_id }}"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowfullscreen>
</iframe>
</div>
</div>
{% endif %}
<h4 class="fw-bold mb-3 mt-4">عن هذه المادة</h4>
<div class="lead text-muted mb-5">
{{ subject.description_ar|linebreaks }}
</div>
<!-- Google Drive Link -->
{% if subject.google_drive_link %}
<div class="mb-5">
<a href="{{ subject.google_drive_link }}" target="_blank" class="btn btn-outline-success btn-lg w-100 p-4 rounded-4 d-flex align-items-center justify-content-between hover-shadow transition-all">
<div class="d-flex align-items-center">
<div class="me-3 display-6">📂</div>
<div class="text-start">
<h5 class="fw-bold mb-1">مجلد المادة على Drive</h5>
<p class="mb-0 small text-muted">الوصول للملفات والمستندات المشتركة</p>
</div>
</div>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-box-arrow-up-right" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M8.636 3.5a.5.5 0 0 0-.5-.5H1.5A1.5 1.5 0 0 0 0 4.5v10A1.5 1.5 0 0 0 1.5 16h10a1.5 1.5 0 0 0 1.5-1.5V7.864a.5.5 0 0 0-1 0V14.5a.5.5 0 0 1-.5.5h-10a.5.5 0 0 1-.5-.5v-10a.5.5 0 0 1 .5-.5h6.636a.5.5 0 0 0 .5-.5z"/>
<path fill-rule="evenodd" d="M16 .5a.5.5 0 0 0-.5-.5h-5a.5.5 0 0 0 0 1h3.793L6.146 9.146a.5.5 0 1 0 .708.708L15 1.707V5.5a.5.5 0 0 0 1 0v-5z"/>
</svg>
</a>
</div>
{% endif %}
<!-- Resources Section -->
<div class="mb-5">
<div class="d-flex align-items-center mb-4">
<h4 class="fw-bold mb-0">{% trans "Course Resources" %}</h4>
<span class="badge bg-light text-primary ms-3 border rounded-pill px-3">{{ subject.resources.count }}</span>
<div class="d-flex align-items-center justify-content-between mb-4">
<div class="d-flex align-items-center">
<h4 class="fw-bold mb-0">مصادر الدورة</h4>
<span class="badge bg-light text-primary ms-3 border rounded-pill px-3">{{ subject.resources.count }}</span>
</div>
{% if is_teacher %}
<a href="{% url 'add_resource' subject.id %}" class="btn btn-success 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>
{% if subject.resources.all %}
@ -97,28 +145,40 @@
</div>
<div>
<h6 class="mb-0 fw-semibold">
{% if LANGUAGE_CODE == 'ar' %}{{ resource.title_ar }}{% else %}{{ resource.title_en }}{% endif %}
{{ resource.title_ar }}
</h6>
<small class="text-muted">
{{ resource.get_resource_type_display }}
</small>
</div>
</div>
<button class="btn btn-sm btn-outline-primary rounded-pill px-3 view-resource-btn"
data-type="{{ resource.resource_type }}"
data-title="{% if LANGUAGE_CODE == 'ar' %}{{ resource.title_ar }}{% else %}{{ resource.title_en }}{% endif %}"
data-file-url="{% if resource.file %}{{ resource.file.url }}{% endif %}"
data-link="{% if resource.link %}{{ resource.link }}{% endif %}"
data-youtube-id="{{ resource.get_youtube_id|default:'' }}">
{% trans "View" %}
</button>
<div class="d-flex align-items-center gap-2">
{% if is_teacher %}
<a href="{% url 'edit_resource' resource.id %}" class="btn btn-sm btn-outline-secondary rounded-pill">تعديل</a>
<a href="{% url 'delete_resource' resource.id %}" class="btn btn-sm btn-outline-danger rounded-pill">حذف</a>
{% endif %}
<button class="btn btn-sm btn-outline-primary rounded-pill px-3 view-resource-btn"
data-type="{{ resource.resource_type }}"
data-title="{{ resource.title_ar }}"
data-file-url="{% if resource.file %}{{ resource.file.url }}{% endif %}"
data-view-url="{% url 'view_resource' resource.id %}"
data-link="{% if resource.link %}{{ resource.link }}{% endif %}"
data-youtube-id="{{ resource.get_youtube_id|default:'' }}">
عرض
</button>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="text-center p-5 bg-light rounded-4">
<div class="display-6 mb-3">📂</div>
<h6 class="text-muted">{% trans "No resources available yet." %}</h6>
<h6 class="text-muted">لا توجد مصادر متاحة بعد.</h6>
{% if is_teacher %}
<a href="{% url 'add_resource' subject.id %}" class="btn btn-primary rounded-pill mt-3">
إضافة أول مصدر
</a>
{% endif %}
</div>
{% endif %}
</div>
@ -135,21 +195,21 @@
<ul class="list-unstyled mb-4">
<li class="mb-3 d-flex align-items-center">
<span class="text-success me-2"></span> {% trans "Full lifetime access" %}
<span class="text-success me-2"></span> وصول كامل مدى الحياة
</li>
<li class="mb-3 d-flex align-items-center">
<span class="text-success me-2"></span> {% trans "Access on mobile and desktop" %}
<span class="text-success me-2"></span> دخول من الهاتف والكمبيوتر
</li>
<li class="mb-3 d-flex align-items-center">
<span class="text-success me-2"></span> {% trans "Certificate of completion" %}
<span class="text-success me-2"></span> شهادة إتمام
</li>
</ul>
<button class="btn btn-primary btn-lg w-100 py-3 fw-bold mb-3 shadow-sm">
{% trans "Enroll Now" %}
اشترك الآن
</button>
<p class="text-center text-muted small mb-0">
{% trans "30-Day Money-Back Guarantee" %}
ضمان استرداد الأموال خلال 30 يوم
</p>
</div>
</div>
@ -160,20 +220,15 @@
<!-- Resource Modal -->
<div class="modal fade" id="resourceModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-centered">
<div class="modal-content rounded-4 border-0 shadow-lg">
<div class="modal-dialog modal-xl modal-dialog-centered modal-dialog-scrollable">
<div class="modal-content rounded-4 border-0 shadow-lg" style="height: 90vh;">
<div class="modal-header border-0 pb-0">
<h5 class="modal-title fw-bold" id="resourceModalTitle">{% trans "Resource" %}</h5>
<h5 class="modal-title fw-bold" id="resourceModalTitle">المصدر</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body p-4" id="resourceModalBody">
<div class="modal-body p-0" id="resourceModalBody" style="background-color: #f8f9fa;">
<!-- Content injected via JS -->
</div>
<div class="modal-footer border-0 pt-0">
<a href="#" target="_blank" id="openNewTabBtn" class="btn btn-primary rounded-pill">
{% trans "Open in New Tab" %} <i class="bi bi-box-arrow-up-right ms-2"></i>
</a>
</div>
</div>
</div>
</div>
@ -186,59 +241,72 @@ document.addEventListener('DOMContentLoaded', function() {
const resourceModal = new bootstrap.Modal(document.getElementById('resourceModal'));
const modalTitle = document.getElementById('resourceModalTitle');
const modalBody = document.getElementById('resourceModalBody');
const openNewTabBtn = document.getElementById('openNewTabBtn');
document.querySelectorAll('.view-resource-btn').forEach(btn => {
btn.addEventListener('click', function() {
const type = this.dataset.type;
const title = this.dataset.title;
const fileUrl = this.dataset.fileUrl;
const viewUrl = this.dataset.viewUrl;
const link = this.dataset.link;
const youtubeId = this.dataset.youtubeId;
modalTitle.textContent = title;
openNewTabBtn.style.display = 'inline-block'; // Default to showing it
modalBody.innerHTML = ''; // Clear previous content
if (type === 'VIDEO' && youtubeId) {
modalBody.innerHTML = `
<div class="ratio ratio-16x9">
<div class="ratio ratio-16x9 h-100">
<iframe src="https://www.youtube.com/embed/${youtubeId}?autoplay=1" title="${title}" allowfullscreen></iframe>
</div>
`;
openNewTabBtn.href = link;
} else if (type === 'FILE' && fileUrl) {
// Check if it's an image or PDF for preview
const ext = fileUrl.split('.').pop().toLowerCase();
// Use viewUrl for fetching content (permission checked)
const secureUrl = viewUrl;
if (['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(ext)) {
modalBody.innerHTML = `<img src="${fileUrl}" class="img-fluid rounded" alt="${title}">`;
} else if (ext === 'pdf') {
modalBody.innerHTML = `
<div class="ratio ratio-4x3">
<iframe src="${fileUrl}" title="${title}"></iframe>
<div class="d-flex align-items-center justify-content-center h-100 p-4">
<img src="${secureUrl}" class="img-fluid rounded shadow" alt="${title}" style="max-height: 100%;">
</div>
`;
} else if (ext === 'pdf') {
// Use iframe for better PDF embedding
modalBody.innerHTML = `
<div class="w-100 h-100" style="min-height: 80vh;">
<iframe src="${secureUrl}" style="width: 100%; height: 100%; border: none;">
<div class="d-flex flex-column align-items-center justify-content-center h-100 p-5 text-center">
<p class="text-muted mb-4">متصفحك لا يدعم عرض ملفات PDF داخل الصفحة.</p>
<a href="${secureUrl}" class="btn btn-primary rounded-pill px-5 py-3" download>
<i class="bi bi-download me-2"></i> تحميل الملف
</a>
</div>
</iframe>
</div>
`;
} else {
// Other files: no preview, just download button
modalBody.innerHTML = `
<div class="text-center py-5">
<div class="d-flex flex-column align-items-center justify-content-center h-100">
<div class="mb-3 display-4">📄</div>
<p class="mb-3">{% trans "This file type cannot be previewed directly." %}</p>
<a href="${fileUrl}" class="btn btn-primary rounded-pill" download>{% trans "Download File" %}</a>
<p class="mb-3">لا يمكن معاينة هذا النوع من الملفات مباشرة.</p>
<a href="${secureUrl}" class="btn btn-primary rounded-pill px-4" download>
<i class="bi bi-download me-2"></i> تحميل الملف
</a>
</div>
`;
openNewTabBtn.style.display = 'none'; // Use the internal download button instead
}
openNewTabBtn.href = fileUrl;
} else if (type === 'LINK' && link) {
// Embed link in iframe
modalBody.innerHTML = `
<div class="text-center py-5">
<div class="mb-3 display-4">🔗</div>
<p class="mb-3">{% trans "You are about to visit an external link." %}</p>
<p class="text-primary fw-bold text-break">${link}</p>
<div class="w-100 h-100">
<iframe src="${link}" title="${title}" style="width: 100%; height: 100%; border: none;" allowfullscreen sandbox="allow-scripts allow-same-origin allow-forms allow-popups"></iframe>
</div>
`;
openNewTabBtn.href = link;
}
resourceModal.show();

View File

@ -1,15 +1,15 @@
{% extends 'base.html' %}
{% load i18n static %}
{% block title %}{% trans "Teacher Dashboard" %} - EduPlatform{% endblock %}
{% block title %}لوحة تحكم المعلم - EduPlatform{% endblock %}
{% block content %}
<div class="container" style="margin-top: 100px; margin-bottom: 50px;">
<!-- Welcome Header -->
<div class="row mb-4">
<div class="col-12">
<h2 class="fw-bold">{% trans "Welcome, Teacher" %} <span style="color: var(--primary-color);">{{ user.last_name|default:user.username }}</span>! 👨‍🏫</h2>
<p class="text-muted">{% trans "Manage your classes and students here." %}</p>
<h2 class="fw-bold">مرحباً، أستاذ <span style="color: var(--primary-color);">{{ user.last_name|default:user.username }}</span>! 👨‍🏫</h2>
<p class="text-muted">أدر صفوفك وطلابك هنا.</p>
</div>
</div>
@ -28,19 +28,26 @@
</div>
<h4 class="fw-bold mb-1">{{ user.get_full_name|default:user.username }}</h4>
<p class="text-muted mb-2">{{ teacher_profile.specialization|default:"Teacher" }}</p>
<p class="text-muted mb-2">{{ teacher_profile.specialization|default:"معلم" }}</p>
<hr class="my-4">
<div class="text-start">
<h6 class="fw-bold mb-2">{% trans "Bio" %}</h6>
<h6 class="fw-bold mb-2">نبذة</h6>
<p class="text-muted small">
{{ teacher_profile.bio|default:"No bio provided yet." }}
{{ teacher_profile.bio|default:"لم يتم تقديم نبذة بعد." }}
</p>
</div>
<div class="mt-4">
<a href="#" class="btn btn-outline-primary w-100 rounded-pill">{% trans "Edit Profile" %}</a>
<a href="{% url 'edit_teacher_profile' %}" class="btn btn-outline-primary w-100 rounded-pill">تعديل الملف الشخصي</a>
<a href="https://drive.google.com" target="_blank" class="btn btn-outline-success w-100 rounded-pill mt-2">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-hdd-network me-2" viewBox="0 0 16 16">
<path d="M4.5 5a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1zM3 4.5a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0z"/>
<path d="M0 4a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v1a2 2 0 0 1-2 2H8.5v3a1.5 1.5 0 0 1 1.5 1.5h5.5a.5.5 0 0 1 0 1H10A1.5 1.5 0 0 1 8.5 14h-1A1.5 1.5 0 0 1 6 12.5H.5a.5.5 0 0 1 0-1H6A1.5 1.5 0 0 1 7.5 10V7H2a2 2 0 0 1-2-2V4zm1 0v1a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1zm6 7.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5z"/>
</svg>
ملفاتي في Google Drive
</a>
</div>
</div>
</div>
@ -52,7 +59,7 @@
<div class="col-md-6 mb-3 mb-md-0">
<div class="glass-card py-3 px-4 d-flex align-items-center justify-content-between">
<div>
<h6 class="text-muted mb-0">{% trans "Subjects Taught" %}</h6>
<h6 class="text-muted mb-0">المواد التي تدرسها</h6>
<h2 class="fw-bold mb-0 text-primary">{{ subjects.count }}</h2>
</div>
<div class="bg-primary bg-opacity-10 p-3 rounded-circle text-primary">
@ -66,8 +73,8 @@
<div class="col-md-6">
<div class="glass-card py-3 px-4 d-flex align-items-center justify-content-between">
<div>
<h6 class="text-muted mb-0">{% trans "Status" %}</h6>
<h5 class="fw-bold mb-0 text-success">Active</h5>
<h6 class="text-muted mb-0">الحالة</h6>
<h5 class="fw-bold mb-0 text-success">نشط</h5>
</div>
<div class="bg-success bg-opacity-10 p-3 rounded-circle text-success">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-person-badge" viewBox="0 0 16 16">
@ -81,7 +88,7 @@
<!-- My Subjects Section -->
<div class="mb-4">
<h4 class="fw-bold mb-3">{% trans "My Classes" %}</h4>
<h4 class="fw-bold mb-3">صفوفي</h4>
{% if subjects %}
<div class="row g-4">
{% for subject in subjects %}
@ -93,18 +100,18 @@
</div>
{% else %}
<div class="bg-light d-flex align-items-center justify-content-center" style="height: 150px;">
<span class="text-muted">{% trans "No Image" %}</span>
<span class="text-muted">لا توجد صورة</span>
</div>
{% endif %}
<div class="p-3">
<h5 class="fw-bold mb-2">{{ subject.name_en }}</h5>
<p class="text-muted small mb-2 text-truncate">{{ subject.description_en }}</p>
<h5 class="fw-bold mb-2">{{ subject.name_ar }}</h5>
<p class="text-muted small mb-2 text-truncate">{{ subject.description_ar }}</p>
<div class="d-flex justify-content-between align-items-center mb-2">
<span class="badge bg-info bg-opacity-10 text-info">
{{ subject.classroom.name_en }}
{{ subject.classroom.name_ar }}
</span>
<span class="text-muted small">
{{ subject.subscribers.count }} {% trans "Students" %}
{{ subject.subscribers.count }} طلاب
</span>
</div>
<div class="mt-3 d-grid gap-2">
@ -112,9 +119,16 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-camera-video-fill me-2" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M0 5a2 2 0 0 1 2-2h7.5a2 2 0 0 1 1.983 1.738l3.11-1.382A1 1 0 0 1 16 4.269v7.461a1 1 0 0 1-1.406.913l-3.111-1.382A2 2 0 0 1 9.5 13H2a2 2 0 0 1-2-2V5z"/>
</svg>
{% trans "Start Live Class" %}
ابدأ الدرس المباشر
</a>
<a href="{% url 'subject_detail' subject.id %}" class="btn btn-sm btn-primary rounded-pill">{% trans "Manage Class" %}</a>
<a href="{% url 'add_resource' subject.id %}" class="btn btn-sm btn-success rounded-pill">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-plus-circle me-2" viewBox="0 0 16 16">
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/>
<path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"/>
</svg>
إضافة مصدر
</a>
<a href="{% url 'subject_detail' subject.id %}" class="btn btn-sm btn-primary rounded-pill">إدارة الصف</a>
</div>
</div>
</div>
@ -123,8 +137,8 @@
</div>
{% else %}
<div class="glass-card text-center py-5">
<h5 class="text-muted">{% trans "You are not assigned to any subjects yet." %}</h5>
<p class="text-muted small">{% trans "Contact the administrator to assign classes." %}</p>
<h5 class="text-muted">لم يتم تعيين أي مواد لك بعد.</h5>
<p class="text-muted small">تواصل مع المسؤول لتعيين الصفوف.</p>
</div>
{% endif %}
</div>

View File

@ -1,14 +1,14 @@
{% extends 'base.html' %}
{% load i18n static %}
{% block title %}{% trans "Verify Account" %}{% endblock %}
{% block title %}تفعيل الحساب{% endblock %}
{% block content %}
<div class="container hero-section">
<div class="row justify-content-center">
<div class="col-lg-6">
<div class="glass-card">
<h2 class="text-center mb-4 text-primary">{% trans "Account Verification" %}</h2>
<h2 class="text-center mb-4 text-primary">تفعيل الحساب</h2>
{% if error %}
<div class="alert alert-danger">
@ -17,36 +17,36 @@
{% endif %}
<p class="text-muted text-center mb-4">
{% trans "We have sent verification codes to your mobile number and email address. Please enter them below." %}
لقد أرسلنا رموز التحقق إلى رقم هاتفك وبريدك الإلكتروني. الرجاء إدخالها أدناه.
</p>
<form method="post">
{% csrf_token %}
<div class="mb-3">
<label class="form-label">{% trans "Mobile Verification Code" %}</label>
<input type="text" name="mobile_otp" class="form-control" placeholder="6-digit code" {% if student.is_mobile_verified %}value="Verified" disabled{% endif %} required>
<label class="form-label">رمز تحقق الهاتف</label>
<input type="text" name="mobile_otp" class="form-control" placeholder="رمز مكون من 6 أرقام" {% if student.is_mobile_verified %}value="تم التفعيل" disabled{% endif %} required>
{% if student.is_mobile_verified %}
<small class="text-success"><i class="fas fa-check-circle"></i> {% trans "Mobile Verified" %}</small>
<small class="text-success"><i class="fas fa-check-circle"></i> تم تفعيل الهاتف</small>
{% else %}
<small class="text-muted">{% trans "Sent to" %} {{ student.mobile_number }}</small>
<small class="text-muted">أرسل إلى {{ student.mobile_number }}</small>
{% endif %}
</div>
<div class="mb-3">
<label class="form-label">{% trans "Email Verification Code" %}</label>
<input type="text" name="email_otp" class="form-control" placeholder="6-digit code" {% if student.is_email_verified %}value="Verified" disabled{% endif %} required>
<label class="form-label">رمز تحقق البريد</label>
<input type="text" name="email_otp" class="form-control" placeholder="رمز مكون من 6 أرقام" {% if student.is_email_verified %}value="تم التفعيل" disabled{% endif %} required>
{% if student.is_email_verified %}
<small class="text-success"><i class="fas fa-check-circle"></i> {% trans "Email Verified" %}</small>
<small class="text-success"><i class="fas fa-check-circle"></i> تم تفعيل البريد</small>
{% else %}
<small class="text-muted">{% trans "Sent to" %} {{ student.user.email }}</small>
<small class="text-muted">أرسل إلى {{ student.user.email }}</small>
{% endif %}
</div>
<button type="submit" class="btn btn-success w-100 py-2 fs-5">{% trans "Verify" %}</button>
<button type="submit" class="btn btn-success w-100 py-2 fs-5">تفعيل</button>
</form>
</div>
</div>
</div>
</div>
{% endblock %}
{% endblock %}

View File

@ -13,9 +13,14 @@ urlpatterns = [
path('verify-otp/', views.verify_otp, name='verify_otp'),
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('logout/', views.custom_logout, name='logout'),
path('subscribe/<int:subject_id>/', views.subscribe_subject, name='subscribe_subject'),
path('payment/success/', views.payment_success, name='payment_success'),
path('payment/cancel/', views.payment_cancel, name='payment_cancel'),
path('classroom/<int:subject_id>/', views.live_classroom, name='live_classroom'),
path('subject/<int:subject_id>/add-resource/', views.add_resource, name='add_resource'),
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'),
]

View File

@ -2,17 +2,19 @@ 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
from django.http import JsonResponse, FileResponse, Http404
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 .models import Classroom, Subject, Teacher, Student, City
from .forms import StudentRegistrationForm
from .models import Classroom, Subject, Teacher, Student, City, Resource
from .forms import StudentRegistrationForm, TeacherProfileForm, ResourceForm
from .wablas import send_whatsapp_message
from .thawani import ThawaniClient
import random
import string
import mimetypes
import os
def index(request):
levels = Classroom.objects.prefetch_related('subjects').all()
@ -32,7 +34,11 @@ def set_language(request, lang_code):
def subject_detail(request, pk):
subject = get_object_or_404(Subject, pk=pk)
return render(request, 'core/subject_detail.html', {'subject': subject})
is_teacher = False
if request.user.is_authenticated and hasattr(request.user, 'teacher_profile'):
if request.user.teacher_profile in subject.teachers.all():
is_teacher = True
return render(request, 'core/subject_detail.html', {'subject': subject, 'is_teacher': is_teacher})
@staff_member_required
def get_subjects_by_level(request):
@ -46,7 +52,7 @@ def get_classroom_subjects(request):
classroom_id = request.GET.get('classroom_id')
if not classroom_id:
return JsonResponse([], safe=False)
subjects = Subject.objects.filter(classroom_id=classroom_id).values('id', 'name_en', 'price')
subjects = Subject.objects.filter(classroom_id=classroom_id).values('id', 'name_en', 'name_ar', 'price')
return JsonResponse(list(subjects), safe=False)
def get_cities_by_governorate(request):
@ -79,7 +85,7 @@ def register_student(request):
# Note: This will only work if Wablas is configured in Admin
send_whatsapp_message(
student.mobile_number,
f"Your Verification Code is: {mobile_otp}"
f"رمز التحقق الخاص بك هو: {mobile_otp}"
)
# Simulate sending OTPs (Log to console)
@ -123,14 +129,14 @@ def verify_otp(request):
student.is_mobile_verified = True
mobile_ok = True
else:
error = "Invalid Mobile OTP"
error = "رمز الهاتف غير صحيح"
if not email_ok and (error is None or "Mobile" not in error):
if not email_ok and (error is None or "الهاتف" not in error):
if entered_email_otp == student.email_otp_code:
student.is_email_verified = True
email_ok = True
else:
error = "Invalid Email OTP"
error = "رمز البريد غير صحيح"
if mobile_ok and email_ok:
student.mobile_otp_code = "" # Clear codes
@ -180,6 +186,23 @@ def profile(request):
student_profile = None
return render(request, 'core/profile.html', {'student_profile': student_profile})
@login_required
def edit_teacher_profile(request):
try:
teacher = request.user.teacher_profile
except Teacher.DoesNotExist:
return redirect('profile')
if request.method == 'POST':
form = TeacherProfileForm(request.POST, request.FILES, instance=teacher)
if form.is_valid():
form.save()
return redirect('profile')
else:
form = TeacherProfileForm(instance=teacher)
return render(request, 'core/edit_teacher_profile.html', {'form': form})
def custom_logout(request):
logout(request)
return redirect('index')
@ -206,13 +229,13 @@ def subscribe_subject(request, subject_id):
session_id = session.get('data', {}).get('session_id')
if not session_id:
return render(request, 'core/error.html', {'message': 'Could not create payment session.'})
return render(request, 'core/error.html', {'message': 'تعذر إنشاء جلسة الدفع.'})
return redirect(f"{thawani.checkout_base_url}/{session_id}")
except Exception as e:
print(f"Payment Error: {e}")
return render(request, 'core/error.html', {'message': f'Payment initialization failed: {str(e)}'})
return render(request, 'core/error.html', {'message': f'فشل بدء عملية الدفع: {str(e)}'})
@login_required
def payment_success(request):
@ -237,11 +260,11 @@ def payment_success(request):
pass # Already handled or user mismatch?
return redirect('profile')
else:
return render(request, 'core/error.html', {'message': f'Payment was not successful. Status: {payment_status}'})
return render(request, 'core/error.html', {'message': f'لم تتم عملية الدفع بنجاح. الحالة: {payment_status}'})
except Exception as e:
print(f"Payment Verification Error: {e}")
return render(request, 'core/error.html', {'message': f'Payment verification failed: {str(e)}'})
return render(request, 'core/error.html', {'message': f'فشل التحقق من الدفع: {str(e)}'})
@login_required
def payment_cancel(request):
@ -261,7 +284,7 @@ def live_classroom(request, subject_id):
is_student = True
if not (is_teacher or is_student):
raise PermissionDenied("You are not authorized to join this class.")
raise PermissionDenied("ليس لديك صلاحية الانضمام لهذا الفصل.")
# Generate Room Name
# We use a consistent room name based on Subject ID
@ -272,4 +295,106 @@ def live_classroom(request, subject_id):
'room_name': room_name,
'user_display_name': request.user.get_full_name() or request.user.username,
'is_teacher': is_teacher
})
})
@login_required
def add_resource(request, subject_id):
try:
teacher = request.user.teacher_profile
except Teacher.DoesNotExist:
raise PermissionDenied("يجب أن تكون معلماً للوصول إلى هذه الصفحة.")
subject = get_object_or_404(Subject, pk=subject_id)
if teacher not in subject.teachers.all():
raise PermissionDenied("ليس لديك صلاحية لإضافة مصادر لهذه المادة.")
if request.method == 'POST':
form = ResourceForm(request.POST, request.FILES)
if form.is_valid():
resource = form.save(commit=False)
resource.subject = subject
resource.save()
return redirect('subject_detail', pk=subject.id)
else:
form = ResourceForm()
return render(request, 'core/add_resource.html', {'form': form, 'subject': subject})
@login_required
def edit_resource(request, resource_id):
resource = get_object_or_404(Resource, pk=resource_id)
try:
teacher = request.user.teacher_profile
except Teacher.DoesNotExist:
raise PermissionDenied("يجب أن تكون معلماً للوصول إلى هذه الصفحة.")
if teacher not in resource.subject.teachers.all():
raise PermissionDenied("ليس لديك صلاحية لتعديل هذا المصدر.")
if request.method == 'POST':
form = ResourceForm(request.POST, request.FILES, instance=resource)
if form.is_valid():
form.save()
return redirect('subject_detail', pk=resource.subject.id)
else:
form = ResourceForm(instance=resource)
return render(request, 'core/edit_resource.html', {'form': form, 'resource': resource, 'subject': resource.subject})
@login_required
def delete_resource(request, resource_id):
resource = get_object_or_404(Resource, pk=resource_id)
try:
teacher = request.user.teacher_profile
except Teacher.DoesNotExist:
raise PermissionDenied("يجب أن تكون معلماً للوصول إلى هذه الصفحة.")
if teacher not in resource.subject.teachers.all():
raise PermissionDenied("ليس لديك صلاحية لحذف هذا المصدر.")
subject_id = resource.subject.id
if request.method == 'POST':
resource.delete()
return redirect('subject_detail', pk=subject_id)
return render(request, 'core/delete_resource.html', {'resource': resource})
@login_required
def view_resource(request, resource_id):
resource = get_object_or_404(Resource, pk=resource_id)
has_access = False
# Check Teacher Access
if hasattr(request.user, 'teacher_profile') and request.user.teacher_profile in resource.subject.teachers.all():
has_access = True
# Check Student Access
elif hasattr(request.user, 'student_profile') and resource.subject in request.user.student_profile.subscribed_subjects.all():
has_access = True
if not has_access:
raise PermissionDenied("ليس لديك صلاحية لعرض هذا المصدر.")
if resource.resource_type == 'FILE' and resource.file:
try:
# Open file
file_handle = resource.file.open('rb')
response = FileResponse(file_handle)
# Check for PDF to force inline display
filename = resource.file.name
if filename.lower().endswith('.pdf'):
response['Content-Type'] = 'application/pdf'
response['Content-Disposition'] = f'inline; filename="{os.path.basename(filename)}"'
return response
except FileNotFoundError:
raise Http404("الملف غير موجود على الخادم.")
elif resource.resource_type in ['LINK', 'VIDEO']:
return redirect(resource.link)
return redirect('subject_detail', pk=resource.subject.id)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
media/teachers/men.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB