Upload 111 files
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .env.example +3 -0
- Dockerfile +21 -0
- attendance/__init__.py +0 -0
- attendance/__pycache__/__init__.cpython-314.pyc +0 -0
- attendance/__pycache__/apps.cpython-314.pyc +0 -0
- attendance/admin.py +7 -0
- attendance/apps.py +5 -0
- attendance/forms.py +11 -0
- attendance/migrations/0001_initial.py +24 -0
- attendance/migrations/__init__.py +0 -0
- attendance/models.py +24 -0
- attendance/urls.py +8 -0
- attendance/views.py +42 -0
- classes/__init__.py +0 -0
- classes/__pycache__/__init__.cpython-314.pyc +0 -0
- classes/__pycache__/apps.cpython-314.pyc +0 -0
- classes/admin.py +6 -0
- classes/apps.py +5 -0
- classes/migrations/0001_initial.py +20 -0
- classes/migrations/__init__.py +0 -0
- classes/models.py +21 -0
- classes/urls.py +8 -0
- classes/views.py +30 -0
- core/__init__.py +0 -0
- core/__pycache__/__init__.cpython-314.pyc +0 -0
- core/__pycache__/apps.cpython-314.pyc +0 -0
- core/admin.py +1 -0
- core/apps.py +5 -0
- core/migrations/__init__.py +0 -0
- core/models.py +0 -0
- core/urls.py +9 -0
- core/views.py +22 -0
- docker-compose.yml +13 -0
- grades/__init__.py +0 -0
- grades/__pycache__/__init__.cpython-314.pyc +0 -0
- grades/__pycache__/apps.cpython-314.pyc +0 -0
- grades/admin.py +7 -0
- grades/apps.py +5 -0
- grades/forms.py +10 -0
- grades/migrations/0001_initial.py +26 -0
- grades/migrations/__init__.py +0 -0
- grades/models.py +37 -0
- grades/urls.py +8 -0
- grades/views.py +26 -0
- manage.py +14 -0
- notices/__init__.py +0 -0
- notices/__pycache__/__init__.cpython-314.pyc +0 -0
- notices/admin.py +7 -0
- notices/apps.py +13 -0
- notices/forms.py +10 -0
.env.example
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
SECRET_KEY=your-secret-key-here
|
| 2 |
+
DEBUG=True
|
| 3 |
+
ALLOWED_HOSTS=localhost,127.0.0.1
|
Dockerfile
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.11-slim
|
| 2 |
+
|
| 3 |
+
WORKDIR /app
|
| 4 |
+
|
| 5 |
+
ENV PYTHONDONTWRITEBYTECODE=1
|
| 6 |
+
ENV PYTHONUNBUFFERED=1
|
| 7 |
+
|
| 8 |
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 9 |
+
gcc \
|
| 10 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 11 |
+
|
| 12 |
+
COPY requirements.txt .
|
| 13 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 14 |
+
|
| 15 |
+
COPY . .
|
| 16 |
+
|
| 17 |
+
RUN python manage.py collectstatic --noinput
|
| 18 |
+
|
| 19 |
+
EXPOSE 8000
|
| 20 |
+
|
| 21 |
+
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]
|
attendance/__init__.py
ADDED
|
File without changes
|
attendance/__pycache__/__init__.cpython-314.pyc
ADDED
|
Binary file (184 Bytes). View file
|
|
|
attendance/__pycache__/apps.cpython-314.pyc
ADDED
|
Binary file (553 Bytes). View file
|
|
|
attendance/admin.py
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.contrib import admin
|
| 2 |
+
from .models import Attendance
|
| 3 |
+
|
| 4 |
+
@admin.register(Attendance)
|
| 5 |
+
class AttendanceAdmin(admin.ModelAdmin):
|
| 6 |
+
list_display = ['student', 'date', 'status', 'school_class']
|
| 7 |
+
list_filter = ['status', 'date', 'school_class']
|
attendance/apps.py
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.apps import AppConfig
|
| 2 |
+
|
| 3 |
+
class AttendanceConfig(AppConfig):
|
| 4 |
+
default_auto_field = 'django.db.models.BigAutoField'
|
| 5 |
+
name = 'attendance'
|
attendance/forms.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django import forms
|
| 2 |
+
from .models import Attendance
|
| 3 |
+
|
| 4 |
+
class AttendanceForm(forms.ModelForm):
|
| 5 |
+
class Meta:
|
| 6 |
+
model = Attendance
|
| 7 |
+
fields = ['student', 'school_class', 'date', 'status', 'note']
|
| 8 |
+
widgets = {
|
| 9 |
+
'date': forms.DateInput(attrs={'type': 'date'}),
|
| 10 |
+
'note': forms.Textarea(attrs={'rows': 2}),
|
| 11 |
+
}
|
attendance/migrations/0001_initial.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.db import migrations, models
|
| 2 |
+
import django.db.models.deletion
|
| 3 |
+
|
| 4 |
+
class Migration(migrations.Migration):
|
| 5 |
+
initial = True
|
| 6 |
+
dependencies = [
|
| 7 |
+
('students', '0001_initial'),
|
| 8 |
+
('classes', '0001_initial'),
|
| 9 |
+
]
|
| 10 |
+
operations = [
|
| 11 |
+
migrations.CreateModel(
|
| 12 |
+
name='Attendance',
|
| 13 |
+
fields=[
|
| 14 |
+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
| 15 |
+
('date', models.DateField()),
|
| 16 |
+
('status', models.CharField(choices=[('present', 'Present'), ('absent', 'Absent'), ('late', 'Late'), ('excused', 'Excused')], default='present', max_length=10)),
|
| 17 |
+
('note', models.TextField(blank=True)),
|
| 18 |
+
('created_at', models.DateTimeField(auto_now_add=True)),
|
| 19 |
+
('student', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='attendances', to='students.student')),
|
| 20 |
+
('school_class', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='attendances', to='classes.schoolclass')),
|
| 21 |
+
],
|
| 22 |
+
options={'ordering': ['-date'], 'unique_together': {('student', 'date')}},
|
| 23 |
+
),
|
| 24 |
+
]
|
attendance/migrations/__init__.py
ADDED
|
File without changes
|
attendance/models.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.db import models
|
| 2 |
+
from students.models import Student
|
| 3 |
+
from classes.models import SchoolClass
|
| 4 |
+
|
| 5 |
+
class Attendance(models.Model):
|
| 6 |
+
STATUS_CHOICES = [
|
| 7 |
+
('present', 'Present'),
|
| 8 |
+
('absent', 'Absent'),
|
| 9 |
+
('late', 'Late'),
|
| 10 |
+
('excused', 'Excused'),
|
| 11 |
+
]
|
| 12 |
+
student = models.ForeignKey(Student, on_delete=models.CASCADE, related_name='attendances')
|
| 13 |
+
school_class = models.ForeignKey(SchoolClass, on_delete=models.CASCADE, related_name='attendances')
|
| 14 |
+
date = models.DateField()
|
| 15 |
+
status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='present')
|
| 16 |
+
note = models.TextField(blank=True)
|
| 17 |
+
created_at = models.DateTimeField(auto_now_add=True)
|
| 18 |
+
|
| 19 |
+
def __str__(self):
|
| 20 |
+
return f"{self.student} - {self.date} - {self.status}"
|
| 21 |
+
|
| 22 |
+
class Meta:
|
| 23 |
+
unique_together = ['student', 'date']
|
| 24 |
+
ordering = ['-date']
|
attendance/urls.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.urls import path
|
| 2 |
+
from . import views
|
| 3 |
+
|
| 4 |
+
urlpatterns = [
|
| 5 |
+
path('', views.attendance_list, name='attendance_list'),
|
| 6 |
+
path('mark/', views.mark_attendance, name='mark_attendance'),
|
| 7 |
+
path('report/', views.attendance_report, name='attendance_report'),
|
| 8 |
+
]
|
attendance/views.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.shortcuts import render, redirect
|
| 2 |
+
from django.contrib.auth.decorators import login_required
|
| 3 |
+
from django.contrib import messages
|
| 4 |
+
from .models import Attendance
|
| 5 |
+
from .forms import AttendanceForm
|
| 6 |
+
from students.models import Student
|
| 7 |
+
from classes.models import SchoolClass
|
| 8 |
+
from django.utils import timezone
|
| 9 |
+
|
| 10 |
+
@login_required
|
| 11 |
+
def attendance_list(request):
|
| 12 |
+
today = timezone.now().date()
|
| 13 |
+
records = Attendance.objects.select_related('student', 'school_class').filter(date=today)
|
| 14 |
+
return render(request, 'attendance/list.html', {'records': records, 'today': today})
|
| 15 |
+
|
| 16 |
+
@login_required
|
| 17 |
+
def mark_attendance(request):
|
| 18 |
+
form = AttendanceForm(request.POST or None)
|
| 19 |
+
if form.is_valid():
|
| 20 |
+
form.save()
|
| 21 |
+
messages.success(request, 'Attendance marked!')
|
| 22 |
+
return redirect('attendance_list')
|
| 23 |
+
classes = SchoolClass.objects.all()
|
| 24 |
+
return render(request, 'attendance/mark.html', {'form': form, 'classes': classes})
|
| 25 |
+
|
| 26 |
+
@login_required
|
| 27 |
+
def attendance_report(request):
|
| 28 |
+
class_id = request.GET.get('class_id')
|
| 29 |
+
date_from = request.GET.get('date_from')
|
| 30 |
+
date_to = request.GET.get('date_to')
|
| 31 |
+
records = Attendance.objects.select_related('student', 'school_class').all()
|
| 32 |
+
if class_id:
|
| 33 |
+
records = records.filter(school_class_id=class_id)
|
| 34 |
+
if date_from:
|
| 35 |
+
records = records.filter(date__gte=date_from)
|
| 36 |
+
if date_to:
|
| 37 |
+
records = records.filter(date__lte=date_to)
|
| 38 |
+
classes = SchoolClass.objects.all()
|
| 39 |
+
return render(request, 'attendance/report.html', {
|
| 40 |
+
'records': records, 'classes': classes,
|
| 41 |
+
'class_id': class_id, 'date_from': date_from, 'date_to': date_to
|
| 42 |
+
})
|
classes/__init__.py
ADDED
|
File without changes
|
classes/__pycache__/__init__.cpython-314.pyc
ADDED
|
Binary file (181 Bytes). View file
|
|
|
classes/__pycache__/apps.cpython-314.pyc
ADDED
|
Binary file (544 Bytes). View file
|
|
|
classes/admin.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.contrib import admin
|
| 2 |
+
from .models import SchoolClass
|
| 3 |
+
|
| 4 |
+
@admin.register(SchoolClass)
|
| 5 |
+
class SchoolClassAdmin(admin.ModelAdmin):
|
| 6 |
+
list_display = ['name', 'grade_level', 'section', 'academic_year']
|
classes/apps.py
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.apps import AppConfig
|
| 2 |
+
|
| 3 |
+
class ClassesConfig(AppConfig):
|
| 4 |
+
default_auto_field = 'django.db.models.BigAutoField'
|
| 5 |
+
name = 'classes'
|
classes/migrations/0001_initial.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.db import migrations, models
|
| 2 |
+
|
| 3 |
+
class Migration(migrations.Migration):
|
| 4 |
+
initial = True
|
| 5 |
+
dependencies = []
|
| 6 |
+
operations = [
|
| 7 |
+
migrations.CreateModel(
|
| 8 |
+
name='SchoolClass',
|
| 9 |
+
fields=[
|
| 10 |
+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
| 11 |
+
('name', models.CharField(max_length=100)),
|
| 12 |
+
('grade_level', models.IntegerField()),
|
| 13 |
+
('section', models.CharField(default='A', max_length=10)),
|
| 14 |
+
('room_number', models.CharField(blank=True, max_length=20)),
|
| 15 |
+
('academic_year', models.CharField(default='2024-2025', max_length=20)),
|
| 16 |
+
('created_at', models.DateTimeField(auto_now_add=True)),
|
| 17 |
+
],
|
| 18 |
+
options={'ordering': ['grade_level', 'section'], 'verbose_name': 'Class', 'verbose_name_plural': 'Classes'},
|
| 19 |
+
),
|
| 20 |
+
]
|
classes/migrations/__init__.py
ADDED
|
File without changes
|
classes/models.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.db import models
|
| 2 |
+
|
| 3 |
+
class SchoolClass(models.Model):
|
| 4 |
+
name = models.CharField(max_length=100)
|
| 5 |
+
grade_level = models.IntegerField()
|
| 6 |
+
section = models.CharField(max_length=10, default='A')
|
| 7 |
+
room_number = models.CharField(max_length=20, blank=True)
|
| 8 |
+
academic_year = models.CharField(max_length=20, default='2024-2025')
|
| 9 |
+
created_at = models.DateTimeField(auto_now_add=True)
|
| 10 |
+
|
| 11 |
+
def __str__(self):
|
| 12 |
+
return f"{self.name} - {self.section}"
|
| 13 |
+
|
| 14 |
+
@property
|
| 15 |
+
def student_count(self):
|
| 16 |
+
return self.students.count()
|
| 17 |
+
|
| 18 |
+
class Meta:
|
| 19 |
+
ordering = ['grade_level', 'section']
|
| 20 |
+
verbose_name = 'Class'
|
| 21 |
+
verbose_name_plural = 'Classes'
|
classes/urls.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.urls import path
|
| 2 |
+
from . import views
|
| 3 |
+
|
| 4 |
+
urlpatterns = [
|
| 5 |
+
path('', views.class_list, name='class_list'),
|
| 6 |
+
path('<int:pk>/', views.class_detail, name='class_detail'),
|
| 7 |
+
path('add/', views.class_add, name='class_add'),
|
| 8 |
+
]
|
classes/views.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.shortcuts import render, get_object_or_404, redirect
|
| 2 |
+
from django.contrib.auth.decorators import login_required
|
| 3 |
+
from django.contrib import messages
|
| 4 |
+
from .models import SchoolClass
|
| 5 |
+
from django import forms
|
| 6 |
+
|
| 7 |
+
class ClassForm(forms.ModelForm):
|
| 8 |
+
class Meta:
|
| 9 |
+
model = SchoolClass
|
| 10 |
+
fields = ['name', 'grade_level', 'section', 'room_number', 'academic_year']
|
| 11 |
+
|
| 12 |
+
@login_required
|
| 13 |
+
def class_list(request):
|
| 14 |
+
classes = SchoolClass.objects.all()
|
| 15 |
+
return render(request, 'classes/list.html', {'classes': classes})
|
| 16 |
+
|
| 17 |
+
@login_required
|
| 18 |
+
def class_detail(request, pk):
|
| 19 |
+
school_class = get_object_or_404(SchoolClass, pk=pk)
|
| 20 |
+
students = school_class.students.all()
|
| 21 |
+
return render(request, 'classes/detail.html', {'school_class': school_class, 'students': students})
|
| 22 |
+
|
| 23 |
+
@login_required
|
| 24 |
+
def class_add(request):
|
| 25 |
+
form = ClassForm(request.POST or None)
|
| 26 |
+
if form.is_valid():
|
| 27 |
+
form.save()
|
| 28 |
+
messages.success(request, 'Class created!')
|
| 29 |
+
return redirect('class_list')
|
| 30 |
+
return render(request, 'classes/form.html', {'form': form, 'title': 'Add Class'})
|
core/__init__.py
ADDED
|
File without changes
|
core/__pycache__/__init__.cpython-314.pyc
ADDED
|
Binary file (178 Bytes). View file
|
|
|
core/__pycache__/apps.cpython-314.pyc
ADDED
|
Binary file (535 Bytes). View file
|
|
|
core/admin.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
from django.contrib import admin
|
core/apps.py
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.apps import AppConfig
|
| 2 |
+
|
| 3 |
+
class CoreConfig(AppConfig):
|
| 4 |
+
default_auto_field = 'django.db.models.BigAutoField'
|
| 5 |
+
name = 'core'
|
core/migrations/__init__.py
ADDED
|
File without changes
|
core/models.py
ADDED
|
File without changes
|
core/urls.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.urls import path
|
| 2 |
+
from . import views
|
| 3 |
+
from django.contrib.auth import views as auth_views
|
| 4 |
+
|
| 5 |
+
urlpatterns = [
|
| 6 |
+
path('', views.dashboard, name='dashboard'),
|
| 7 |
+
path('login/', auth_views.LoginView.as_view(template_name='login.html'), name='login'),
|
| 8 |
+
path('logout/', auth_views.LogoutView.as_view(), name='logout'),
|
| 9 |
+
]
|
core/views.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.shortcuts import render
|
| 2 |
+
from django.contrib.auth.decorators import login_required
|
| 3 |
+
from students.models import Student
|
| 4 |
+
from teachers.models import Teacher
|
| 5 |
+
from classes.models import SchoolClass
|
| 6 |
+
from notices.models import Notice
|
| 7 |
+
from attendance.models import Attendance
|
| 8 |
+
from grades.models import Grade
|
| 9 |
+
from django.utils import timezone
|
| 10 |
+
|
| 11 |
+
@login_required
|
| 12 |
+
def dashboard(request):
|
| 13 |
+
today = timezone.now().date()
|
| 14 |
+
context = {
|
| 15 |
+
'total_students': Student.objects.count(),
|
| 16 |
+
'total_teachers': Teacher.objects.count(),
|
| 17 |
+
'total_classes': SchoolClass.objects.count(),
|
| 18 |
+
'recent_notices': Notice.objects.order_by('-created_at')[:5],
|
| 19 |
+
'today_attendance': Attendance.objects.filter(date=today).count(),
|
| 20 |
+
'recent_grades': Grade.objects.order_by('-created_at')[:5],
|
| 21 |
+
}
|
| 22 |
+
return render(request, 'dashboard.html', context)
|
docker-compose.yml
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: '3.9'
|
| 2 |
+
|
| 3 |
+
services:
|
| 4 |
+
web:
|
| 5 |
+
build: .
|
| 6 |
+
command: python manage.py runserver 0.0.0.0:8000
|
| 7 |
+
volumes:
|
| 8 |
+
- .:/app
|
| 9 |
+
ports:
|
| 10 |
+
- "8000:8000"
|
| 11 |
+
env_file:
|
| 12 |
+
- .env.example
|
| 13 |
+
restart: unless-stopped
|
grades/__init__.py
ADDED
|
File without changes
|
grades/__pycache__/__init__.cpython-314.pyc
ADDED
|
Binary file (180 Bytes). View file
|
|
|
grades/__pycache__/apps.cpython-314.pyc
ADDED
|
Binary file (541 Bytes). View file
|
|
|
grades/admin.py
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.contrib import admin
|
| 2 |
+
from .models import Grade
|
| 3 |
+
|
| 4 |
+
@admin.register(Grade)
|
| 5 |
+
class GradeAdmin(admin.ModelAdmin):
|
| 6 |
+
list_display = ['student', 'subject', 'term', 'score', 'academic_year']
|
| 7 |
+
list_filter = ['term', 'academic_year', 'subject']
|
grades/apps.py
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.apps import AppConfig
|
| 2 |
+
|
| 3 |
+
class GradesConfig(AppConfig):
|
| 4 |
+
default_auto_field = 'django.db.models.BigAutoField'
|
| 5 |
+
name = 'grades'
|
grades/forms.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django import forms
|
| 2 |
+
from .models import Grade
|
| 3 |
+
|
| 4 |
+
class GradeForm(forms.ModelForm):
|
| 5 |
+
class Meta:
|
| 6 |
+
model = Grade
|
| 7 |
+
fields = ['student', 'subject', 'term', 'score', 'max_score', 'academic_year', 'remarks']
|
| 8 |
+
widgets = {
|
| 9 |
+
'remarks': forms.Textarea(attrs={'rows': 2}),
|
| 10 |
+
}
|
grades/migrations/0001_initial.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.db import migrations, models
|
| 2 |
+
import django.db.models.deletion
|
| 3 |
+
|
| 4 |
+
class Migration(migrations.Migration):
|
| 5 |
+
initial = True
|
| 6 |
+
dependencies = [
|
| 7 |
+
('students', '0001_initial'),
|
| 8 |
+
('teachers', '0001_initial'),
|
| 9 |
+
]
|
| 10 |
+
operations = [
|
| 11 |
+
migrations.CreateModel(
|
| 12 |
+
name='Grade',
|
| 13 |
+
fields=[
|
| 14 |
+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
| 15 |
+
('term', models.CharField(choices=[('1', 'Term 1'), ('2', 'Term 2'), ('3', 'Term 3'), ('final', 'Final')], default='1', max_length=10)),
|
| 16 |
+
('score', models.DecimalField(decimal_places=2, max_digits=5)),
|
| 17 |
+
('max_score', models.DecimalField(decimal_places=2, default=100, max_digits=5)),
|
| 18 |
+
('academic_year', models.CharField(default='2024-2025', max_length=20)),
|
| 19 |
+
('remarks', models.TextField(blank=True)),
|
| 20 |
+
('created_at', models.DateTimeField(auto_now_add=True)),
|
| 21 |
+
('student', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='grades', to='students.student')),
|
| 22 |
+
('subject', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='grades', to='teachers.subject')),
|
| 23 |
+
],
|
| 24 |
+
options={'ordering': ['-created_at']},
|
| 25 |
+
),
|
| 26 |
+
]
|
grades/migrations/__init__.py
ADDED
|
File without changes
|
grades/models.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.db import models
|
| 2 |
+
from students.models import Student
|
| 3 |
+
from teachers.models import Subject
|
| 4 |
+
|
| 5 |
+
class Grade(models.Model):
|
| 6 |
+
TERM_CHOICES = [('1', 'Term 1'), ('2', 'Term 2'), ('3', 'Term 3'), ('final', 'Final')]
|
| 7 |
+
|
| 8 |
+
student = models.ForeignKey(Student, on_delete=models.CASCADE, related_name='grades')
|
| 9 |
+
subject = models.ForeignKey(Subject, on_delete=models.CASCADE, related_name='grades')
|
| 10 |
+
term = models.CharField(max_length=10, choices=TERM_CHOICES, default='1')
|
| 11 |
+
score = models.DecimalField(max_digits=5, decimal_places=2)
|
| 12 |
+
max_score = models.DecimalField(max_digits=5, decimal_places=2, default=100)
|
| 13 |
+
academic_year = models.CharField(max_length=20, default='2024-2025')
|
| 14 |
+
remarks = models.TextField(blank=True)
|
| 15 |
+
created_at = models.DateTimeField(auto_now_add=True)
|
| 16 |
+
|
| 17 |
+
@property
|
| 18 |
+
def percentage(self):
|
| 19 |
+
if self.max_score > 0:
|
| 20 |
+
return round((self.score / self.max_score) * 100, 1)
|
| 21 |
+
return 0
|
| 22 |
+
|
| 23 |
+
@property
|
| 24 |
+
def letter_grade(self):
|
| 25 |
+
p = self.percentage
|
| 26 |
+
if p >= 90: return 'A+'
|
| 27 |
+
if p >= 80: return 'A'
|
| 28 |
+
if p >= 70: return 'B'
|
| 29 |
+
if p >= 60: return 'C'
|
| 30 |
+
if p >= 50: return 'D'
|
| 31 |
+
return 'F'
|
| 32 |
+
|
| 33 |
+
def __str__(self):
|
| 34 |
+
return f"{self.student} - {self.subject} - {self.term}"
|
| 35 |
+
|
| 36 |
+
class Meta:
|
| 37 |
+
ordering = ['-created_at']
|
grades/urls.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.urls import path
|
| 2 |
+
from . import views
|
| 3 |
+
|
| 4 |
+
urlpatterns = [
|
| 5 |
+
path('', views.grade_list, name='grade_list'),
|
| 6 |
+
path('add/', views.grade_add, name='grade_add'),
|
| 7 |
+
path('student/<int:student_id>/', views.student_grades, name='student_grades'),
|
| 8 |
+
]
|
grades/views.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.shortcuts import render, redirect, get_object_or_404
|
| 2 |
+
from django.contrib.auth.decorators import login_required
|
| 3 |
+
from django.contrib import messages
|
| 4 |
+
from .models import Grade
|
| 5 |
+
from .forms import GradeForm
|
| 6 |
+
from students.models import Student
|
| 7 |
+
|
| 8 |
+
@login_required
|
| 9 |
+
def grade_list(request):
|
| 10 |
+
grades = Grade.objects.select_related('student', 'subject').order_by('-created_at')[:50]
|
| 11 |
+
return render(request, 'grades/list.html', {'grades': grades})
|
| 12 |
+
|
| 13 |
+
@login_required
|
| 14 |
+
def grade_add(request):
|
| 15 |
+
form = GradeForm(request.POST or None)
|
| 16 |
+
if form.is_valid():
|
| 17 |
+
form.save()
|
| 18 |
+
messages.success(request, 'Grade recorded!')
|
| 19 |
+
return redirect('grade_list')
|
| 20 |
+
return render(request, 'grades/form.html', {'form': form, 'title': 'Add Grade'})
|
| 21 |
+
|
| 22 |
+
@login_required
|
| 23 |
+
def student_grades(request, student_id):
|
| 24 |
+
student = get_object_or_404(Student, pk=student_id)
|
| 25 |
+
grades = Grade.objects.filter(student=student).select_related('subject')
|
| 26 |
+
return render(request, 'grades/student_grades.html', {'student': student, 'grades': grades})
|
manage.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python
|
| 2 |
+
import os
|
| 3 |
+
import sys
|
| 4 |
+
|
| 5 |
+
def main():
|
| 6 |
+
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'school_system.settings')
|
| 7 |
+
try:
|
| 8 |
+
from django.core.management import execute_from_command_line
|
| 9 |
+
except ImportError as exc:
|
| 10 |
+
raise ImportError("Couldn't import Django.") from exc
|
| 11 |
+
execute_from_command_line(sys.argv)
|
| 12 |
+
|
| 13 |
+
if __name__ == '__main__':
|
| 14 |
+
main()
|
notices/__init__.py
ADDED
|
File without changes
|
notices/__pycache__/__init__.cpython-314.pyc
ADDED
|
Binary file (181 Bytes). View file
|
|
|
notices/admin.py
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.contrib import admin
|
| 2 |
+
from .models import Notice
|
| 3 |
+
|
| 4 |
+
@admin.register(Notice)
|
| 5 |
+
class NoticeAdmin(admin.ModelAdmin):
|
| 6 |
+
list_display = ['title', 'priority', 'audience', 'is_active', 'created_at']
|
| 7 |
+
list_filter = ['priority', 'audience', 'is_active']
|
notices/apps.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.apps import AppConfig
|
| 2 |
+
|
| 3 |
+
class NoticesConfig(AppConfig):
|
| 4 |
+
default_auto_field = 'django.db.models.Bi
|
| 5 |
+
|
| 6 |
+
Continuing exactly where the output was cut off — finishing the notices app and all remaining files including templates, static CSS/JS, Docker config, and the full Starting Instructions output.
|
| 7 |
+
|
| 8 |
+
<write path="school_system/notices/apps.py">
|
| 9 |
+
from django.apps import AppConfig
|
| 10 |
+
|
| 11 |
+
class NoticesConfig(AppConfig):
|
| 12 |
+
default_auto_field = 'django.db.models.BigAutoField'
|
| 13 |
+
name = 'notices'
|
notices/forms.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django import forms
|
| 2 |
+
from .models import Notice
|
| 3 |
+
|
| 4 |
+
class NoticeForm(forms.ModelForm):
|
| 5 |
+
class Meta:
|
| 6 |
+
model = Notice
|
| 7 |
+
fields = ['title', 'content', 'priority', 'audience', 'is_active']
|
| 8 |
+
widgets = {
|
| 9 |
+
'content': forms.Textarea(attrs={'rows': 5}),
|
| 10 |
+
}
|