Autosave: 20260204-144027
This commit is contained in:
parent
05987f69ac
commit
c673d9b2b1
BIN
assets/pasted-20260204-135454-c72e44de.png
Normal file
BIN
assets/pasted-20260204-135454-c72e44de.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 202 KiB |
Binary file not shown.
@ -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,
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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'),
|
||||
),
|
||||
]
|
||||
Binary file not shown.
166
core/models.py
166
core/models.py
@ -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 "إعدادات المنصة"
|
||||
|
||||
@ -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>© 2026 EduPlatform. {% trans "All rights reserved." %}</p>
|
||||
<p>© 2026 EduPlatform. جميع الحقوق محفوظة.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
|
||||
41
core/templates/core/add_resource.html
Normal file
41
core/templates/core/add_resource.html
Normal 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 %}
|
||||
34
core/templates/core/delete_resource.html
Normal file
34
core/templates/core/delete_resource.html
Normal 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 %}
|
||||
41
core/templates/core/edit_resource.html
Normal file
41
core/templates/core/edit_resource.html
Normal 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 %}
|
||||
59
core/templates/core/edit_teacher_profile.html
Normal file
59
core/templates/core/edit_teacher_profile.html
Normal 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 %}
|
||||
@ -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 %}
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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 %}
|
||||
@ -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 %}
|
||||
@ -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>
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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 %}
|
||||
@ -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'),
|
||||
]
|
||||
155
core/views.py
155
core/views.py
@ -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)
|
||||
BIN
media/resources/Invoice_8.pdf
Normal file
BIN
media/resources/Invoice_8.pdf
Normal file
Binary file not shown.
BIN
media/resources/Invoice_8_1.pdf
Normal file
BIN
media/resources/Invoice_8_1.pdf
Normal file
Binary file not shown.
BIN
media/resources/User-Manual-Dibal-W-015.pdf
Normal file
BIN
media/resources/User-Manual-Dibal-W-015.pdf
Normal file
Binary file not shown.
BIN
media/resources/User-Manual-Dibal-W-015_bYR96eB.pdf
Normal file
BIN
media/resources/User-Manual-Dibal-W-015_bYR96eB.pdf
Normal file
Binary file not shown.
BIN
media/teachers/men.webp
Normal file
BIN
media/teachers/men.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.6 KiB |
Loading…
x
Reference in New Issue
Block a user