Autosave: 20260204-035622
This commit is contained in:
parent
b9bad07848
commit
776c322148
Binary file not shown.
@ -276,7 +276,7 @@ JAZZMIN_SETTINGS = {
|
|||||||
"core.Teacher": "fas fa-chalkboard-teacher",
|
"core.Teacher": "fas fa-chalkboard-teacher",
|
||||||
"core.Subject": "fas fa-book",
|
"core.Subject": "fas fa-book",
|
||||||
"core.Resource": "fas fa-file-alt",
|
"core.Resource": "fas fa-file-alt",
|
||||||
"core.EducationalLevel": "fas fa-layer-group",
|
"core.Classroom": "fas fa-layer-group",
|
||||||
},
|
},
|
||||||
# Icons that are used when one is not manually specified
|
# Icons that are used when one is not manually specified
|
||||||
"default_icon_parents": "fas fa-chevron-circle-right",
|
"default_icon_parents": "fas fa-chevron-circle-right",
|
||||||
@ -298,4 +298,4 @@ JAZZMIN_SETTINGS = {
|
|||||||
"use_google_fonts_cdn": True,
|
"use_google_fonts_cdn": True,
|
||||||
# Whether to show the UI customizer on the sidebar
|
# Whether to show the UI customizer on the sidebar
|
||||||
"show_ui_builder": True,
|
"show_ui_builder": True,
|
||||||
}
|
}
|
||||||
Binary file not shown.
BIN
core/__pycache__/forms.cpython-311.pyc
Normal file
BIN
core/__pycache__/forms.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,26 +1,57 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django import forms
|
from django import forms
|
||||||
from .models import EducationalLevel, Teacher, Subject, Resource, Student
|
from django.utils.html import format_html
|
||||||
|
from django.urls import reverse
|
||||||
|
from .models import Classroom, Teacher, Subject, Resource, Student, City, Moderate
|
||||||
|
|
||||||
@admin.register(EducationalLevel)
|
class ActionsModelAdmin(admin.ModelAdmin):
|
||||||
class EducationalLevelAdmin(admin.ModelAdmin):
|
"""
|
||||||
list_display = ('name_en', 'name_ar')
|
Mixin to add an actions column to the admin list view.
|
||||||
|
"""
|
||||||
|
def actions_column(self, obj):
|
||||||
|
app_label = obj._meta.app_label
|
||||||
|
model_name = obj._meta.model_name
|
||||||
|
|
||||||
|
edit_url = reverse(f'admin:{app_label}_{model_name}_change', args=[obj.pk])
|
||||||
|
delete_url = reverse(f'admin:{app_label}_{model_name}_delete', args=[obj.pk])
|
||||||
|
|
||||||
|
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>'
|
||||||
|
'</div>',
|
||||||
|
edit_url,
|
||||||
|
delete_url
|
||||||
|
)
|
||||||
|
actions_column.short_description = 'Actions'
|
||||||
|
|
||||||
|
@admin.register(Classroom)
|
||||||
|
class ClassroomAdmin(ActionsModelAdmin):
|
||||||
|
list_display = ('name_en', 'name_ar', 'actions_column')
|
||||||
|
|
||||||
@admin.register(Teacher)
|
@admin.register(Teacher)
|
||||||
class TeacherAdmin(admin.ModelAdmin):
|
class TeacherAdmin(ActionsModelAdmin):
|
||||||
list_display = ('user', 'specialization')
|
list_display = ('user', 'specialization', 'actions_column')
|
||||||
|
|
||||||
@admin.register(Subject)
|
@admin.register(Subject)
|
||||||
class SubjectAdmin(admin.ModelAdmin):
|
class SubjectAdmin(ActionsModelAdmin):
|
||||||
list_display = ('name_en', 'name_ar', 'level', 'price', 'teacher')
|
list_display = ('name_en', 'name_ar', 'classroom', 'price', 'teacher', 'actions_column')
|
||||||
list_filter = ('level', 'teacher')
|
list_filter = ('classroom', 'teacher')
|
||||||
search_fields = ('name_en', 'name_ar')
|
search_fields = ('name_en', 'name_ar')
|
||||||
|
|
||||||
|
@admin.register(City)
|
||||||
|
class CityAdmin(ActionsModelAdmin):
|
||||||
|
list_display = ('name', 'actions_column')
|
||||||
|
|
||||||
|
@admin.register(Moderate)
|
||||||
|
class ModerateAdmin(ActionsModelAdmin):
|
||||||
|
list_display = ('name', 'actions_column')
|
||||||
|
|
||||||
class ResourceAdminForm(forms.ModelForm):
|
class ResourceAdminForm(forms.ModelForm):
|
||||||
educational_level = forms.ModelChoiceField(
|
classroom = forms.ModelChoiceField(
|
||||||
queryset=EducationalLevel.objects.all(),
|
queryset=Classroom.objects.all(),
|
||||||
required=True,
|
required=True,
|
||||||
label="Filter by Educational Level"
|
label="Filter by Classroom"
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -30,22 +61,22 @@ class ResourceAdminForm(forms.ModelForm):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
if self.instance and self.instance.pk and self.instance.subject:
|
if self.instance and self.instance.pk and self.instance.subject:
|
||||||
self.fields['educational_level'].initial = self.instance.subject.level
|
self.fields['classroom'].initial = self.instance.subject.classroom
|
||||||
|
|
||||||
@admin.register(Resource)
|
@admin.register(Resource)
|
||||||
class ResourceAdmin(admin.ModelAdmin):
|
class ResourceAdmin(ActionsModelAdmin):
|
||||||
form = ResourceAdminForm
|
form = ResourceAdminForm
|
||||||
list_display = ('title_en', 'subject', 'created_at')
|
list_display = ('title_en', 'subject', 'created_at', 'actions_column')
|
||||||
list_filter = ('subject__level', 'subject')
|
list_filter = ('subject__classroom', 'subject')
|
||||||
fields = ('educational_level', 'subject', 'title_en', 'title_ar', 'file', 'link')
|
fields = ('classroom', 'subject', 'title_en', 'title_ar', 'file', 'link')
|
||||||
|
|
||||||
class Media:
|
class Media:
|
||||||
js = ('js/admin_resource.js',)
|
js = ('js/admin_resource.js',)
|
||||||
|
|
||||||
@admin.register(Student)
|
@admin.register(Student)
|
||||||
class StudentAdmin(admin.ModelAdmin):
|
class StudentAdmin(ActionsModelAdmin):
|
||||||
list_display = ('user', 'level', 'phone_number')
|
list_display = ('user', 'classroom', 'mobile_number', 'city', 'moderate', 'actions_column')
|
||||||
list_filter = ('level',)
|
list_filter = ('classroom', 'city', 'moderate')
|
||||||
filter_horizontal = ('subscribed_subjects',)
|
filter_horizontal = ('subscribed_subjects',)
|
||||||
|
|
||||||
class Media:
|
class Media:
|
||||||
@ -53,6 +84,6 @@ class StudentAdmin(admin.ModelAdmin):
|
|||||||
|
|
||||||
def get_form(self, request, obj=None, **kwargs):
|
def get_form(self, request, obj=None, **kwargs):
|
||||||
form = super().get_form(request, obj, **kwargs)
|
form = super().get_form(request, obj, **kwargs)
|
||||||
if obj and obj.level:
|
if obj and obj.classroom:
|
||||||
form.base_fields['subscribed_subjects'].queryset = Subject.objects.filter(level=obj.level)
|
form.base_fields['subscribed_subjects'].queryset = Subject.objects.filter(classroom=obj.classroom)
|
||||||
return form
|
return form
|
||||||
57
core/forms.py
Normal file
57
core/forms.py
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
from django import forms
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from .models import Student, Subject, Classroom
|
||||||
|
|
||||||
|
class StudentRegistrationForm(forms.ModelForm):
|
||||||
|
first_name = forms.CharField(max_length=30, required=True, label="Full Name (First)")
|
||||||
|
last_name = forms.CharField(max_length=30, required=True, label="Full Name (Last)")
|
||||||
|
username = forms.CharField(max_length=150, required=True)
|
||||||
|
email = forms.EmailField(required=True)
|
||||||
|
password = forms.CharField(widget=forms.PasswordInput, required=True)
|
||||||
|
|
||||||
|
classroom = forms.ModelChoiceField(queryset=Classroom.objects.all(), required=True, empty_label="Select Classroom")
|
||||||
|
subjects = forms.ModelMultipleChoiceField(
|
||||||
|
queryset=Subject.objects.all(),
|
||||||
|
required=False,
|
||||||
|
widget=forms.CheckboxSelectMultiple,
|
||||||
|
label="Select Subjects"
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Student
|
||||||
|
fields = ['mobile_number', 'moderate', 'city', 'avatar']
|
||||||
|
widgets = {
|
||||||
|
'avatar': forms.FileInput(attrs={'accept': 'image/*', 'capture': 'camera'}), # generic hint, but we'll use custom JS
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
# optimize subjects loading
|
||||||
|
self.fields['subjects'].queryset = Subject.objects.none()
|
||||||
|
|
||||||
|
if 'classroom' in self.data:
|
||||||
|
try:
|
||||||
|
classroom_id = int(self.data.get('classroom'))
|
||||||
|
self.fields['subjects'].queryset = Subject.objects.filter(classroom_id=classroom_id)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
elif self.instance.pk and self.instance.classroom:
|
||||||
|
self.fields['subjects'].queryset = Subject.objects.filter(classroom=self.instance.classroom)
|
||||||
|
|
||||||
|
def save(self, commit=True):
|
||||||
|
# We need to save the User first, then the Student
|
||||||
|
user = User.objects.create_user(
|
||||||
|
username=self.cleaned_data['username'],
|
||||||
|
email=self.cleaned_data['email'],
|
||||||
|
password=self.cleaned_data['password'],
|
||||||
|
first_name=self.cleaned_data['first_name'],
|
||||||
|
last_name=self.cleaned_data['last_name']
|
||||||
|
)
|
||||||
|
student = super().save(commit=False)
|
||||||
|
student.user = user
|
||||||
|
student.classroom = self.cleaned_data['classroom']
|
||||||
|
|
||||||
|
if commit:
|
||||||
|
student.save()
|
||||||
|
student.subscribed_subjects.set(self.cleaned_data['subjects'])
|
||||||
|
return student
|
||||||
@ -1,6 +1,6 @@
|
|||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from core.models import EducationalLevel, Subject, Teacher
|
from core.models import Classroom, Subject, Teacher
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = 'Seeds the database with sample data'
|
help = 'Seeds the database with sample data'
|
||||||
@ -14,13 +14,13 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
teacher, _ = Teacher.objects.get_or_create(user=user, specialization='Mathematics')
|
teacher, _ = Teacher.objects.get_or_create(user=user, specialization='Mathematics')
|
||||||
|
|
||||||
# Create Educational Levels
|
# Create Classrooms
|
||||||
l1, _ = EducationalLevel.objects.get_or_create(
|
l1, _ = Classroom.objects.get_or_create(
|
||||||
name_en='Primary School',
|
name_en='Primary School',
|
||||||
name_ar='المرحلة الابتدائية',
|
name_ar='المرحلة الابتدائية',
|
||||||
description='Grades 1-6'
|
description='Grades 1-6'
|
||||||
)
|
)
|
||||||
l2, _ = EducationalLevel.objects.get_or_create(
|
l2, _ = Classroom.objects.get_or_create(
|
||||||
name_en='High School',
|
name_en='High School',
|
||||||
name_ar='المرحلة الثانوية',
|
name_ar='المرحلة الثانوية',
|
||||||
description='Grades 10-12'
|
description='Grades 10-12'
|
||||||
@ -28,7 +28,7 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
# Create Subjects
|
# Create Subjects
|
||||||
Subject.objects.get_or_create(
|
Subject.objects.get_or_create(
|
||||||
level=l1,
|
classroom=l1,
|
||||||
teacher=teacher,
|
teacher=teacher,
|
||||||
name_en='Basic Math',
|
name_en='Basic Math',
|
||||||
name_ar='الرياضيات الأساسية',
|
name_ar='الرياضيات الأساسية',
|
||||||
@ -37,7 +37,7 @@ class Command(BaseCommand):
|
|||||||
price=20.00
|
price=20.00
|
||||||
)
|
)
|
||||||
Subject.objects.get_or_create(
|
Subject.objects.get_or_create(
|
||||||
level=l2,
|
classroom=l2,
|
||||||
teacher=teacher,
|
teacher=teacher,
|
||||||
name_en='Physics',
|
name_en='Physics',
|
||||||
name_ar='الفيزياء',
|
name_ar='الفيزياء',
|
||||||
@ -46,7 +46,7 @@ class Command(BaseCommand):
|
|||||||
price=50.00
|
price=50.00
|
||||||
)
|
)
|
||||||
Subject.objects.get_or_create(
|
Subject.objects.get_or_create(
|
||||||
level=l2,
|
classroom=l2,
|
||||||
teacher=teacher,
|
teacher=teacher,
|
||||||
name_en='Chemistry',
|
name_en='Chemistry',
|
||||||
name_ar='الكيمياء',
|
name_ar='الكيمياء',
|
||||||
@ -55,4 +55,4 @@ class Command(BaseCommand):
|
|||||||
price=45.00
|
price=45.00
|
||||||
)
|
)
|
||||||
|
|
||||||
self.stdout.write(self.style.SUCCESS('Successfully seeded sample data'))
|
self.stdout.write(self.style.SUCCESS('Successfully seeded sample data'))
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2026-02-03 19:10
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('core', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameModel(
|
||||||
|
old_name='EducationalLevel',
|
||||||
|
new_name='Classroom',
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='classroom',
|
||||||
|
options={'verbose_name': 'Classroom', 'verbose_name_plural': 'Classrooms'},
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2026-02-03 19:15
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('core', '0002_rename_educationallevel_classroom_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='student',
|
||||||
|
old_name='level',
|
||||||
|
new_name='classroom',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='subject',
|
||||||
|
old_name='level',
|
||||||
|
new_name='classroom',
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2026-02-04 02:54
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('core', '0003_rename_level_student_classroom_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='student',
|
||||||
|
name='avatar',
|
||||||
|
field=models.ImageField(blank=True, null=True, upload_to='students/', verbose_name='Picture'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='student',
|
||||||
|
name='city',
|
||||||
|
field=models.CharField(blank=True, choices=[('cairo', 'Cairo'), ('giza', 'Giza'), ('alexandria', 'Alexandria')], max_length=50, verbose_name='City'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='student',
|
||||||
|
name='moderate',
|
||||||
|
field=models.CharField(blank=True, choices=[('morning', 'Morning'), ('evening', 'Evening')], max_length=50, verbose_name='Moderate'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,51 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2026-02-04 03:46
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('core', '0004_student_avatar_student_city_student_moderate'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='City',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=100, verbose_name='Name')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'City',
|
||||||
|
'verbose_name_plural': 'Cities',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Moderate',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=100, verbose_name='Name')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Moderate',
|
||||||
|
'verbose_name_plural': 'Moderates',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='student',
|
||||||
|
name='mobile_number',
|
||||||
|
field=models.CharField(blank=True, max_length=20, verbose_name='Mobile Number'),
|
||||||
|
),
|
||||||
|
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='City'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='student',
|
||||||
|
name='moderate',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='core.moderate', verbose_name='Moderate'),
|
||||||
|
),
|
||||||
|
]
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -11,16 +11,20 @@ class Teacher(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.user.get_full_name() or self.user.username
|
return self.user.get_full_name() or self.user.username
|
||||||
|
|
||||||
class EducationalLevel(models.Model):
|
class Classroom(models.Model):
|
||||||
name_en = models.CharField(_("Name (English)"), max_length=100)
|
name_en = models.CharField(_("Name (English)"), max_length=100)
|
||||||
name_ar = models.CharField(_("Name (Arabic)"), max_length=100)
|
name_ar = models.CharField(_("Name (Arabic)"), max_length=100)
|
||||||
description = models.TextField(_("Description"), blank=True)
|
description = models.TextField(_("Description"), blank=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("Classroom")
|
||||||
|
verbose_name_plural = _("Classrooms")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name_en
|
return self.name_en
|
||||||
|
|
||||||
class Subject(models.Model):
|
class Subject(models.Model):
|
||||||
level = models.ForeignKey(EducationalLevel, on_delete=models.CASCADE, related_name='subjects')
|
classroom = models.ForeignKey(Classroom, on_delete=models.CASCADE, related_name='subjects')
|
||||||
teacher = models.ForeignKey(Teacher, on_delete=models.SET_NULL, null=True, blank=True, related_name='subjects')
|
teacher = models.ForeignKey(Teacher, on_delete=models.SET_NULL, null=True, blank=True, related_name='subjects')
|
||||||
name_en = models.CharField(_("Name (English)"), max_length=200)
|
name_en = models.CharField(_("Name (English)"), max_length=200)
|
||||||
name_ar = models.CharField(_("Name (Arabic)"), max_length=200)
|
name_ar = models.CharField(_("Name (Arabic)"), max_length=200)
|
||||||
@ -45,11 +49,36 @@ class Resource(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.title_en
|
return self.title_en
|
||||||
|
|
||||||
|
class City(models.Model):
|
||||||
|
name = models.CharField(_("Name"), max_length=100)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("City")
|
||||||
|
verbose_name_plural = _("Cities")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
class Moderate(models.Model):
|
||||||
|
name = models.CharField(_("Name"), max_length=100)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("Moderate")
|
||||||
|
verbose_name_plural = _("Moderates")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
class Student(models.Model):
|
class Student(models.Model):
|
||||||
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='student_profile')
|
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='student_profile')
|
||||||
level = models.ForeignKey(EducationalLevel, on_delete=models.SET_NULL, null=True, blank=True, related_name='students')
|
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)
|
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')
|
subscribed_subjects = models.ManyToManyField(Subject, blank=True, related_name='subscribers')
|
||||||
|
|
||||||
|
moderate = models.ForeignKey(Moderate, on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_("Moderate"))
|
||||||
|
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)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.user.get_full_name() or self.user.username
|
return self.user.get_full_name() or self.user.username
|
||||||
@ -130,9 +130,11 @@
|
|||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="#">{% trans "Teachers" %}</a>
|
<a class="nav-link" href="#">{% trans "Teachers" %}</a>
|
||||||
</li>
|
</li>
|
||||||
|
{% if user.is_staff %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="/admin/">{% trans "Admin" %}</a>
|
<a class="nav-link" href="/admin/">{% trans "Admin" %}</a>
|
||||||
</li>
|
</li>
|
||||||
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<div class="dropdown me-3">
|
<div class="dropdown me-3">
|
||||||
@ -145,8 +147,21 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
<span class="me-3">{% trans "Hello" %}, {{ user.username }}</span>
|
<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>
|
||||||
|
{% if user.is_staff %}
|
||||||
|
<li><a class="dropdown-item py-2" href="/admin/">{% trans "Admin Panel" %}</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>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
<a href="{% url 'register_student' %}" class="btn btn-outline-primary me-2">{% trans "Register" %}</a>
|
||||||
<a href="/admin/login/" class="btn btn-primary">{% trans "Login" %}</a>
|
<a href="/admin/login/" class="btn btn-primary">{% trans "Login" %}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
77
core/templates/core/profile.html
Normal file
77
core/templates/core/profile.html
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load i18n static %}
|
||||||
|
|
||||||
|
{% block title %}{% trans "My Profile" %} | EduPlatform{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<section class="hero-section text-center pt-5 pb-5">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<div class="glass-card text-start">
|
||||||
|
<div class="d-flex align-items-center mb-4">
|
||||||
|
{% if student_profile and student_profile.avatar %}
|
||||||
|
<img src="{{ student_profile.avatar.url }}" alt="Profile" class="rounded-circle me-3" width="100" height="100" style="object-fit: cover; border: 3px solid var(--primary-color);">
|
||||||
|
{% else %}
|
||||||
|
<div class="rounded-circle bg-secondary d-flex align-items-center justify-content-center me-3" style="width: 100px; height: 100px; color: white; font-size: 2rem;">
|
||||||
|
{{ user.username|make_list|first|upper }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div>
|
||||||
|
<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>
|
||||||
|
{% elif user.is_staff %}
|
||||||
|
<span class="badge bg-success">{% trans "Staff / Admin" %}</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr class="my-4">
|
||||||
|
|
||||||
|
{% if student_profile %}
|
||||||
|
<h4 class="mb-3">{% trans "Student Details" %}</h4>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label text-muted">{% trans "Username" %}</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>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label text-muted">{% trans "Shift (Moderate)" %}</label>
|
||||||
|
<p class="fw-bold">{{ student_profile.moderate|default:"-" }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label text-muted">{% trans "Mobile Number" %}</label>
|
||||||
|
<p class="fw-bold">{{ student_profile.mobile_number|default:"-" }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4 class="mb-3 mt-4">{% trans "My Subjects" %}</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>
|
||||||
|
</a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<p class="text-muted">{% trans "No subjects subscribed yet." %}</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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endblock %}
|
||||||
262
core/templates/core/registration.html
Normal file
262
core/templates/core/registration.html
Normal file
@ -0,0 +1,262 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load i18n static %}
|
||||||
|
|
||||||
|
{% block title %}{% trans "Student Registration" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container hero-section">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<div class="glass-card">
|
||||||
|
<h2 class="text-center mb-4 text-primary">{% trans "Student Registration" %}</h2>
|
||||||
|
|
||||||
|
<form method="post" enctype="multipart/form-data" id="registrationForm">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
{% if form.errors %}
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
{{ form.errors }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- User Details -->
|
||||||
|
<h5 class="mb-3 border-bottom pb-2">{% trans "Personal Information" %}</h5>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label">{{ form.first_name.label }}</label>
|
||||||
|
{{ form.first_name }}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label">{{ form.last_name.label }}</label>
|
||||||
|
{{ form.last_name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">{{ form.username.label }}</label>
|
||||||
|
{{ form.username }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">{{ form.email.label }}</label>
|
||||||
|
{{ form.email }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">{{ form.password.label }}</label>
|
||||||
|
{{ form.password }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Student Details -->
|
||||||
|
<h5 class="mb-3 border-bottom pb-2 mt-4">{% trans "Student Details" %}</h5>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<label class="form-label">{{ form.mobile_number.label }}</label>
|
||||||
|
{{ form.mobile_number }}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<label class="form-label">{{ form.moderate.label }}</label>
|
||||||
|
{{ form.moderate }}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<label class="form-label">{{ form.city.label }}</label>
|
||||||
|
{{ form.city }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Classroom & Subjects -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">{{ form.classroom.label }}</label>
|
||||||
|
{{ form.classroom }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">{% trans "Subjects" %}</label>
|
||||||
|
<div id="subjects-container" class="border p-3 rounded bg-white" style="min-height: 100px;">
|
||||||
|
<p class="text-muted text-center mt-3">{% trans "Select a classroom to see subjects." %}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4 p-3 bg-primary text-white rounded">
|
||||||
|
<h4 class="m-0">{% trans "Total Amount:" %}</h4>
|
||||||
|
<h4 class="m-0"><span id="total-amount">0.00</span></h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Webcam -->
|
||||||
|
<h5 class="mb-3 border-bottom pb-2">{% trans "Profile Picture" %}</h5>
|
||||||
|
<div class="mb-4">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 d-flex flex-column align-items-center">
|
||||||
|
<label class="mb-2">{% trans "Webcam" %}</label>
|
||||||
|
<div id="video-container" style="position: relative;">
|
||||||
|
<video id="video" width="240" height="180" autoplay style="border: 2px solid #ddd; border-radius: 8px; 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">
|
||||||
|
📸 {% trans "Snap" %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 d-flex flex-column align-items-center">
|
||||||
|
<label class="mb-2">{% trans "Preview" %}</label>
|
||||||
|
<canvas id="canvas" width="240" height="180" style="display:none;"></canvas>
|
||||||
|
<div id="preview-placeholder" style="width: 240px; height: 180px; border: 2px dashed #ddd; border-radius: 8px; display: flex; align-items: center; justify-content: center; color: #aaa;">
|
||||||
|
{% trans "No photo taken" %}
|
||||||
|
</div>
|
||||||
|
<img id="photo-preview" width="240" height="180" class="rounded" style="display:none; border: 2px solid #4ECDC4;">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-3">
|
||||||
|
<label class="form-label small">{% trans "Or upload file:" %}</label>
|
||||||
|
{{ form.avatar }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary w-100 py-2 fs-5">{% trans "Register Now" %}</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_js %}
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// 1. Add Bootstrap classes to inputs
|
||||||
|
const inputs = document.querySelectorAll('input, select');
|
||||||
|
inputs.forEach(input => {
|
||||||
|
if (input.type !== 'checkbox' && input.type !== 'radio' && input.type !== 'file') {
|
||||||
|
input.classList.add('form-control');
|
||||||
|
} else if (input.type === 'file') {
|
||||||
|
input.classList.add('form-control');
|
||||||
|
} else if (input.type === 'checkbox') {
|
||||||
|
input.classList.add('form-check-input');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. Classroom & Subjects Logic
|
||||||
|
const classroomSelect = document.getElementById('id_classroom');
|
||||||
|
const subjectsContainer = document.getElementById('subjects-container');
|
||||||
|
const totalAmountSpan = document.getElementById('total-amount');
|
||||||
|
|
||||||
|
classroomSelect.addEventListener('change', function() {
|
||||||
|
const classroomId = this.value;
|
||||||
|
if (!classroomId) {
|
||||||
|
subjectsContainer.innerHTML = '<p class="text-muted text-center mt-3">{% trans "Select a classroom to see subjects." %}</p>';
|
||||||
|
updateTotal();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch(`{% url 'get_classroom_subjects' %}?classroom_id=${classroomId}`)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
subjectsContainer.innerHTML = '';
|
||||||
|
if (data.length === 0) {
|
||||||
|
subjectsContainer.innerHTML = '<p class="text-warning text-center">{% trans "No subjects found for this classroom." %}</p>';
|
||||||
|
} else {
|
||||||
|
data.forEach(subject => {
|
||||||
|
// Default price 0 if null
|
||||||
|
const price = parseFloat(subject.price) || 0;
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.className = 'form-check mb-2';
|
||||||
|
div.innerHTML = `
|
||||||
|
<input class="form-check-input subject-checkbox" type="checkbox" name="subjects" value="${subject.id}" id="subject_${subject.id}" data-price="${price}" checked>
|
||||||
|
<label class="form-check-label d-flex justify-content-between" for="subject_${subject.id}">
|
||||||
|
<span>${subject.name_en}</span>
|
||||||
|
<span class="badge bg-secondary rounded-pill">${price.toFixed(2)}</span>
|
||||||
|
</label>
|
||||||
|
`;
|
||||||
|
subjectsContainer.appendChild(div);
|
||||||
|
});
|
||||||
|
updateTotal();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error('Error fetching subjects:', err);
|
||||||
|
subjectsContainer.innerHTML = '<p class="text-danger">{% trans "Error loading subjects." %}</p>';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Event delegation for dynamically created checkboxes
|
||||||
|
subjectsContainer.addEventListener('change', function(e) {
|
||||||
|
if (e.target.classList.contains('subject-checkbox')) {
|
||||||
|
updateTotal();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function updateTotal() {
|
||||||
|
let total = 0;
|
||||||
|
document.querySelectorAll('.subject-checkbox:checked').forEach(cb => {
|
||||||
|
total += parseFloat(cb.dataset.price) || 0;
|
||||||
|
});
|
||||||
|
totalAmountSpan.textContent = total.toFixed(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Webcam Logic
|
||||||
|
const video = document.getElementById('video');
|
||||||
|
const videoContainer = document.getElementById('video-container');
|
||||||
|
const canvas = document.getElementById('canvas');
|
||||||
|
const snap = document.getElementById('snap');
|
||||||
|
const photoPreview = document.getElementById('photo-preview');
|
||||||
|
const previewPlaceholder = document.getElementById('preview-placeholder');
|
||||||
|
const avatarInput = document.getElementById('id_avatar');
|
||||||
|
|
||||||
|
function showCameraError(message) {
|
||||||
|
videoContainer.innerHTML = `
|
||||||
|
<div class="alert alert-warning p-2 small text-center" style="height: 180px; display: flex; flex-direction: column; justify-content: center; align-items: center;">
|
||||||
|
<i class="fas fa-exclamation-triangle mb-2" style="font-size: 1.5rem;"></i>
|
||||||
|
<div>${message}</div>
|
||||||
|
<div class="mt-1 text-muted small">{% trans "Please use the file upload below." %}</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request camera access
|
||||||
|
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
|
||||||
|
navigator.mediaDevices.getUserMedia({ video: true })
|
||||||
|
.then(function(stream) {
|
||||||
|
video.srcObject = stream;
|
||||||
|
video.play();
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
console.log("An error occurred: " + err);
|
||||||
|
let msg = "{% trans 'Camera access denied.' %}";
|
||||||
|
if (err.name === 'NotAllowedError') {
|
||||||
|
msg = "{% trans 'Permission denied. Allow camera access.' %}";
|
||||||
|
} else if (err.name === 'NotFoundError') {
|
||||||
|
msg = "{% trans 'No camera found.' %}";
|
||||||
|
} else if (window.isSecureContext === false) {
|
||||||
|
msg = "{% trans 'Camera requires HTTPS.' %}";
|
||||||
|
}
|
||||||
|
showCameraError(msg);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
showCameraError("{% trans 'Browser does not support camera API or not secure.' %}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (snap) {
|
||||||
|
snap.addEventListener('click', function() {
|
||||||
|
if (video.srcObject) {
|
||||||
|
const context = canvas.getContext('2d');
|
||||||
|
context.drawImage(video, 0, 0, 240, 180);
|
||||||
|
|
||||||
|
canvas.toBlob(function(blob) {
|
||||||
|
// Create a File object
|
||||||
|
const file = new File([blob], "webcam_capture.jpg", { type: "image/jpeg" });
|
||||||
|
|
||||||
|
// Create a DataTransfer to manipulate the file input
|
||||||
|
const dataTransfer = new DataTransfer();
|
||||||
|
dataTransfer.items.add(file);
|
||||||
|
avatarInput.files = dataTransfer.files;
|
||||||
|
|
||||||
|
// Show preview
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
photoPreview.src = url;
|
||||||
|
photoPreview.style.display = 'block';
|
||||||
|
previewPlaceholder.style.display = 'none';
|
||||||
|
}, 'image/jpeg', 0.95);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
@ -11,7 +11,7 @@
|
|||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li class="breadcrumb-item"><a href="{% url 'index' %}">{% trans "Home" %}</a></li>
|
<li class="breadcrumb-item"><a href="{% url 'index' %}">{% trans "Home" %}</a></li>
|
||||||
<li class="breadcrumb-item active" aria-current="page">
|
<li class="breadcrumb-item active" aria-current="page">
|
||||||
{% if LANGUAGE_CODE == 'ar' %}{{ subject.level.name_ar }}{% else %}{{ subject.level.name_en }}{% endif %}
|
{% if LANGUAGE_CODE == 'ar' %}{{ subject.classroom.name_ar }}{% else %}{{ subject.classroom.name_en }}{% endif %}
|
||||||
</li>
|
</li>
|
||||||
</ol>
|
</ol>
|
||||||
</nav>
|
</nav>
|
||||||
@ -27,7 +27,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="position-absolute top-0 end-0 m-4">
|
<div class="position-absolute top-0 end-0 m-4">
|
||||||
<span class="badge bg-primary fs-6 px-3 py-2 shadow-sm">
|
<span class="badge bg-primary fs-6 px-3 py-2 shadow-sm">
|
||||||
{% if LANGUAGE_CODE == 'ar' %}{{ subject.level.name_ar }}{% else %}{{ subject.level.name_en }}{% endif %}
|
{% if LANGUAGE_CODE == 'ar' %}{{ subject.classroom.name_ar }}{% else %}{{ subject.classroom.name_en }}{% endif %}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -6,4 +6,8 @@ urlpatterns = [
|
|||||||
path('set-language/<str:lang_code>/', views.set_language, name='set_language'),
|
path('set-language/<str:lang_code>/', views.set_language, name='set_language'),
|
||||||
path('subject/<int:pk>/', views.subject_detail, name='subject_detail'),
|
path('subject/<int:pk>/', views.subject_detail, name='subject_detail'),
|
||||||
path('ajax/get-subjects-by-level/', views.get_subjects_by_level, name='get_subjects_by_level'),
|
path('ajax/get-subjects-by-level/', views.get_subjects_by_level, name='get_subjects_by_level'),
|
||||||
]
|
path('register/', views.register_student, name='register_student'),
|
||||||
|
path('ajax/get-classroom-subjects/', views.get_classroom_subjects, name='get_classroom_subjects'),
|
||||||
|
path('profile/', views.profile, name='profile'),
|
||||||
|
path('logout/', views.custom_logout, name='logout'),
|
||||||
|
]
|
||||||
@ -3,11 +3,14 @@ from django.utils import translation
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.http import JsonResponse
|
from django.http import JsonResponse
|
||||||
from django.contrib.admin.views.decorators import staff_member_required
|
from django.contrib.admin.views.decorators import staff_member_required
|
||||||
from .models import EducationalLevel, Subject, Teacher
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.contrib.auth import logout
|
||||||
|
from .models import Classroom, Subject, Teacher, Student
|
||||||
|
from .forms import StudentRegistrationForm
|
||||||
|
|
||||||
def index(request):
|
def index(request):
|
||||||
# Fetch levels with their related subjects using prefetch_related for efficiency
|
# Fetch levels with their related subjects using prefetch_related for efficiency
|
||||||
levels = EducationalLevel.objects.prefetch_related('subjects').all()
|
levels = Classroom.objects.prefetch_related('subjects').all()
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'levels': levels,
|
'levels': levels,
|
||||||
@ -32,8 +35,41 @@ def subject_detail(request, pk):
|
|||||||
|
|
||||||
@staff_member_required
|
@staff_member_required
|
||||||
def get_subjects_by_level(request):
|
def get_subjects_by_level(request):
|
||||||
level_id = request.GET.get('level_id')
|
level_id = request.GET.get('level_id') or request.GET.get('classroom_id')
|
||||||
if not level_id:
|
if not level_id:
|
||||||
return JsonResponse([], safe=False)
|
return JsonResponse([], safe=False)
|
||||||
subjects = Subject.objects.filter(level_id=level_id).values('id', 'name_en', 'name_ar')
|
subjects = Subject.objects.filter(classroom_id=level_id).values('id', 'name_en', 'name_ar')
|
||||||
return JsonResponse(list(subjects), safe=False)
|
return JsonResponse(list(subjects), safe=False)
|
||||||
|
|
||||||
|
def get_classroom_subjects(request):
|
||||||
|
"""Public endpoint for registration form"""
|
||||||
|
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')
|
||||||
|
return JsonResponse(list(subjects), safe=False)
|
||||||
|
|
||||||
|
def register_student(request):
|
||||||
|
if request.method == 'POST':
|
||||||
|
form = StudentRegistrationForm(request.POST, request.FILES)
|
||||||
|
if form.is_valid():
|
||||||
|
student = form.save()
|
||||||
|
# Redirect to success page or login
|
||||||
|
return redirect('index') # Placeholder
|
||||||
|
else:
|
||||||
|
form = StudentRegistrationForm()
|
||||||
|
|
||||||
|
return render(request, 'core/registration.html', {'form': form})
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def profile(request):
|
||||||
|
try:
|
||||||
|
student_profile = request.user.student_profile
|
||||||
|
except Student.DoesNotExist:
|
||||||
|
student_profile = None
|
||||||
|
|
||||||
|
return render(request, 'core/profile.html', {'student_profile': student_profile})
|
||||||
|
|
||||||
|
def custom_logout(request):
|
||||||
|
logout(request)
|
||||||
|
return redirect('index')
|
||||||
|
|||||||
@ -2,12 +2,12 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
$(function() {
|
$(function() {
|
||||||
console.log("Admin Resource JS loaded");
|
console.log("Admin Resource JS loaded");
|
||||||
var $levelSelect = $('#id_educational_level');
|
var $levelSelect = $('#id_classroom');
|
||||||
var $subjectSelect = $('#id_subject');
|
var $subjectSelect = $('#id_subject');
|
||||||
|
|
||||||
// Check if elements exist
|
// Check if elements exist
|
||||||
if (!$levelSelect.length) {
|
if (!$levelSelect.length) {
|
||||||
console.warn("Educational Level field #id_educational_level not found");
|
console.warn("Classroom field #id_classroom not found");
|
||||||
}
|
}
|
||||||
if (!$subjectSelect.length) {
|
if (!$subjectSelect.length) {
|
||||||
console.warn("Subject field #id_subject not found");
|
console.warn("Subject field #id_subject not found");
|
||||||
@ -17,7 +17,7 @@
|
|||||||
var isArabic = $('html').attr('lang') === 'ar' || $('body').hasClass('rtl');
|
var isArabic = $('html').attr('lang') === 'ar' || $('body').hasClass('rtl');
|
||||||
|
|
||||||
function updateSubjects(levelId, currentSubjectId) {
|
function updateSubjects(levelId, currentSubjectId) {
|
||||||
console.log("Updating subjects for level:", levelId);
|
console.log("Updating subjects for classroom:", levelId);
|
||||||
// If no level selected, clear subjects
|
// If no level selected, clear subjects
|
||||||
if (!levelId) {
|
if (!levelId) {
|
||||||
$subjectSelect.html('<option value="">---------</option>');
|
$subjectSelect.html('<option value="">---------</option>');
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
(function($) {
|
(function($) {
|
||||||
'use strict';
|
'use strict';
|
||||||
$(function() {
|
$(function() {
|
||||||
var $levelSelect = $('#id_level');
|
var $levelSelect = $('#id_classroom');
|
||||||
var $subjectSelect = $('#id_subscribed_subjects');
|
var $subjectSelect = $('#id_subscribed_subjects');
|
||||||
// Check if we are in the admin change form
|
// Check if we are in the admin change form
|
||||||
if (!$levelSelect.length || !$subjectSelect.length) return;
|
if (!$levelSelect.length || !$subjectSelect.length) return;
|
||||||
@ -75,4 +75,4 @@
|
|||||||
updateSubjects($levelSelect.val(), selectedValues);
|
updateSubjects($levelSelect.val(), selectedValues);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})(django.jQuery);
|
})(django.jQuery);
|
||||||
@ -2,12 +2,12 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
$(function() {
|
$(function() {
|
||||||
console.log("Admin Resource JS loaded");
|
console.log("Admin Resource JS loaded");
|
||||||
var $levelSelect = $('#id_educational_level');
|
var $levelSelect = $('#id_classroom');
|
||||||
var $subjectSelect = $('#id_subject');
|
var $subjectSelect = $('#id_subject');
|
||||||
|
|
||||||
// Check if elements exist
|
// Check if elements exist
|
||||||
if (!$levelSelect.length) {
|
if (!$levelSelect.length) {
|
||||||
console.warn("Educational Level field #id_educational_level not found");
|
console.warn("Classroom field #id_classroom not found");
|
||||||
}
|
}
|
||||||
if (!$subjectSelect.length) {
|
if (!$subjectSelect.length) {
|
||||||
console.warn("Subject field #id_subject not found");
|
console.warn("Subject field #id_subject not found");
|
||||||
@ -17,7 +17,7 @@
|
|||||||
var isArabic = $('html').attr('lang') === 'ar' || $('body').hasClass('rtl');
|
var isArabic = $('html').attr('lang') === 'ar' || $('body').hasClass('rtl');
|
||||||
|
|
||||||
function updateSubjects(levelId, currentSubjectId) {
|
function updateSubjects(levelId, currentSubjectId) {
|
||||||
console.log("Updating subjects for level:", levelId);
|
console.log("Updating subjects for classroom:", levelId);
|
||||||
// If no level selected, clear subjects
|
// If no level selected, clear subjects
|
||||||
if (!levelId) {
|
if (!levelId) {
|
||||||
$subjectSelect.html('<option value="">---------</option>');
|
$subjectSelect.html('<option value="">---------</option>');
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
(function($) {
|
(function($) {
|
||||||
'use strict';
|
'use strict';
|
||||||
$(function() {
|
$(function() {
|
||||||
var $levelSelect = $('#id_level');
|
var $levelSelect = $('#id_classroom');
|
||||||
var $subjectSelect = $('#id_subscribed_subjects');
|
var $subjectSelect = $('#id_subscribed_subjects');
|
||||||
// Check if we are in the admin change form
|
// Check if we are in the admin change form
|
||||||
if (!$levelSelect.length || !$subjectSelect.length) return;
|
if (!$levelSelect.length || !$subjectSelect.length) return;
|
||||||
@ -75,4 +75,4 @@
|
|||||||
updateSubjects($levelSelect.val(), selectedValues);
|
updateSubjects($levelSelect.val(), selectedValues);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})(django.jQuery);
|
})(django.jQuery);
|
||||||
Loading…
x
Reference in New Issue
Block a user