Delete old apps and refactor code

This commit is contained in:
Nathan Chapman 2022-07-28 15:09:38 -06:00
parent b1bbb2b1c2
commit 4ed3e1fd9d
120 changed files with 32437 additions and 3286 deletions

View File

@ -25,3 +25,9 @@ class User(AbstractUser):
choices=TIMEZONE_CHOICES,
default=MOUNTAIN
)
def __str__(self):
if self.first_name and self.last_name:
return f'{self.first_name} {self.last_name}'
else:
return self.username

View File

@ -1,72 +1,23 @@
{% extends 'base.html' %}
{% load static %}
{% block head_title %}Profile |{% endblock %}
{% block content %}
<article class="panel">
<header>
<h1 class="greeting"><em>Welcome {{user.first_name}} {{user.last_name }}</em>
<br>
Here's what's going on today
</h1>
<article class="detail">
<header class="detail__header">
<div>
<h1>{{ user }}</h1>
<p><small>Joined: <time datetime="{{ user.date_joined|date:'Y-m-d' }}">{{ user.date_joined|date:'M d Y' }}</time></p>
</div>
<a href="{% url 'account-update' user.pk %}" class="action-button">Edit</a>
</header>
<section>
<h3 class="domain__heading">Birthdays</h3>
<ul>
{% for student in birthdays %}
<li><strong><a href="{% url 'student-detail' student.pk %}">{{student}}</a></strong> is turning {{student.age|add:1}} on {{student.dob|date:"M j"}}</li>
{% empty %}
<p>No Birthdays this next week.</p>
{% endfor %}
</ul>
</section>
<section>
<h3 class="domain__heading">Today's Assignments</h3>
<ul>
{% for component in components %}
<li>
{{component.subject}}, <a href="{% url 'component-detail' component.subject.pk component.pk %}">{{component}}</a>
</li>
{% empty %}
<p>Nothing for today.</p>
{% endfor %}
</ul>
</section>
<section>
<h3 class="domain__heading">Today's Attendance</h3>
{% for day in attendance %}
<p><strong><a href="{% url 'day-update' day.pk %}">{{day.date}}</a></strong></p>
<table>
<thead>
<tr>
<td>Student</td>
<td colspan="2">Status</td>
</tr>
</thead>
<tbody>
{% for entry in day.entry_set.all %}
<tr>
<td>{{entry.student}}</td>
<td>{{entry.get_status_display}}</td>
<td><a href="{% url 'entry-update' entry.pk %}">Update</a></td>
</tr>
{% endfor %}
</tbody>
</table>
{% empty %}
<p class="greeting">No attendance taken yet: <a href="{% url 'day-create' %}" class="action-button">Take attendance</a></p>
{% endfor %}
</section>
<section>
<h3 class="domain__heading">Assignments to be graded</h3>
<ul>
{% for component in ungraded_components %}
<li>
{{component.subject}}, <a href="{% url 'component-detail' component.subject.pk component.pk %}">{{component}}</a>
</li>
{% empty %}
<p>Everything is graded to far.</p>
{% endfor %}
</ul>
<section class="detail__info">
<dl class="detail__datalist">
<dt>Username</dt>
<dd>{{ user.username }}</dd>
<dt>Email address</dt>
<dd>{{ user.email }}</dd>
</dl>
</section>
</article>
{% endblock %}

View File

@ -1,23 +0,0 @@
{% extends 'base.html' %}
{% block content %}
<section class="panel">
<h1>Users</h1>
<table>
<thead>
<th>Username</th>
<th>Name</th>
</thead>
<tbody>
{% for user in user_list %}
<tr>
<td>{{ user.username }}</td>
<td><a href="{% url 'account-detail' user.id %}">{{user.first_name}} {{user.last_name}}</a></td>
</tr>
{% empty %}
<tr><td>No users yet.</td></tr>
{% endfor %}
</tbody>
</table>
</section>
{% endblock %}

View File

@ -1,72 +0,0 @@
{% extends 'base.html' %}
{% load static %}
{% block content %}
<article class="panel">
<header>
<h1 class="greeting"><em>Welcome {{profile.user.first_name}} {{profile.user.last_name }}</em>
<br>
Here's what's going on today
</h1>
</header>
<section>
<h3 class="domain__heading">Birthdays</h3>
<ul>
{% for student in birthdays %}
<li><strong><a href="{% url 'student-detail' student.pk %}">{{student}}</a></strong> is turning {{student.age|add:1}} on {{student.dob|date:"M j"}}</li>
{% empty %}
<p>No Birthdays this next week.</p>
{% endfor %}
</ul>
</section>
<section>
<h3 class="domain__heading">Today's Assignments</h3>
<ul>
{% for component in components %}
<li>
{{component.subject}}, <a href="{% url 'component-detail' component.subject.pk component.pk %}">{{component}}</a>
</li>
{% empty %}
<p>Nothing for today.</p>
{% endfor %}
</ul>
</section>
<section>
<h3 class="domain__heading">Today's Attendance</h3>
{% for day in attendance %}
<p><strong><a href="{% url 'day-update' day.pk %}">{{day.date}}</a></strong></p>
<table>
<thead>
<tr>
<td>Student</td>
<td colspan="2">Status</td>
</tr>
</thead>
<tbody>
{% for entry in day.entry_set.all %}
<tr>
<td>{{entry.student}}</td>
<td>{{entry.get_status_display}}</td>
<td><a href="{% url 'entry-update' entry.pk %}">Update</a></td>
</tr>
{% endfor %}
</tbody>
</table>
{% empty %}
<p class="greeting">No attendance taken yet: <a href="{% url 'day-create' %}" class="action-button">Take attendance</a></p>
{% endfor %}
</section>
<section>
<h3 class="domain__heading">Assignments to be graded</h3>
<ul>
{% for component in ungraded_components %}
<li>
{{component.subject}}, <a href="{% url 'component-detail' component.subject.pk component.pk %}">{{component}}</a>
</li>
{% empty %}
<p>Everything is graded to far.</p>
{% endfor %}
</ul>
</section>
</article>
{% endblock %}

View File

@ -2,10 +2,21 @@ from django.urls import path, include
from . import views
urlpatterns = [
path('', views.AccountListView.as_view(), name='account-list'),
path('<int:pk>/', include([
path('', views.AccountDetailView.as_view(), name='account-detail'),
path('update/', views.AccountUpdateView.as_view(), name='account-update'),
path('delete/', views.AccountDeleteView.as_view(), name='account-delete'),
path(
'',
views.AccountDetailView.as_view(),
name='account-detail'
),
path(
'update/',
views.AccountUpdateView.as_view(),
name='account-update'
),
path(
'delete/',
views.AccountDeleteView.as_view(),
name='account-delete'
),
])),
]

View File

@ -12,48 +12,14 @@ from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.forms import PasswordChangeForm
from students.models import Student
from gradebook.models import Component
from attendance.models import Day, Entry
from .models import User
from .forms import AccountUpdateForm
class AccountListView(LoginRequiredMixin, ListView):
model = User
template_name = 'accounts/account_list.html'
class AccountDetailView(LoginRequiredMixin, DetailView):
model = User
template_name = 'accounts/account_detail.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
today = timezone.localtime(timezone.now()).date()
enddate = today + dt.timedelta(days=7)
context['birthdays'] = Student.objects.filter(
dob__month=today.month,
dob__day__range=[today.day, enddate.day]
).order_by('dob')
context['components'] = Component.objects.filter(
due_date=today
).select_related('subject')
context['attendance'] = Day.objects.filter(
date=today
).prefetch_related('entry_set', 'entry_set__student')
context['ungraded_components'] = Component.objects.filter(
due_date__lte=today,
finished_grading=False
).select_related('subject')
return context
class AccountUpdateView(LoginRequiredMixin, UpdateView):
model = User

View File

@ -1,6 +0,0 @@
from django.contrib import admin
from .models import Day, Entry
admin.site.register(Day)
admin.site.register(Entry)

View File

@ -1,6 +0,0 @@
from django.apps import AppConfig
class AttendanceConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'attendance'

View File

@ -1,24 +0,0 @@
from django import forms
from django.utils import timezone
from .models import Day, Entry
from students.models import Student
class DayForm(forms.ModelForm):
class Meta:
model = Day
fields = ('date',)
widgets = {
'date': forms.DateInput(attrs = {
'type': 'date',
}),
}
class EntryForm(forms.ModelForm):
class Meta:
model = Entry
fields = ('day', 'student', 'status')
widgets = {
'student': forms.HiddenInput()
}

View File

@ -1,39 +0,0 @@
# Generated by Django 3.2.7 on 2021-09-01 15:26
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('students', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='Day',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('date', models.DateField()),
],
options={
'ordering': ('-date',),
},
),
migrations.CreateModel(
name='Entry',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('status', models.CharField(choices=[('P', 'Present'), ('T', 'Tardy'), ('A', 'Absent')], default='P', max_length=1)),
('day', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='attendance.day')),
('student', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='students.student')),
],
options={
'verbose_name_plural': 'entries',
'ordering': ('student',),
},
),
]

View File

@ -1,33 +0,0 @@
from django.db import models
from students.models import Student
class Day(models.Model):
class Meta:
ordering = ('-date',)
date = models.DateField()
def __str__(self):
return f"{self.date}"
class Entry(models.Model):
class Meta:
verbose_name_plural = 'entries'
ordering = ('student',)
STATUS_CHOICES = [
('P', 'Present'),
('T', 'Tardy'),
('A', 'Absent'),
]
day = models.ForeignKey(Day, on_delete=models.CASCADE)
student = models.ForeignKey(Student, on_delete=models.CASCADE)
status = models.CharField(
max_length=1,
choices=STATUS_CHOICES,
default='P'
)
def __str__(self):
return f"{self.day} | {self.student} | {self.status}"

View File

@ -1,13 +0,0 @@
{% extends "base.html" %}
{% block content %}
<article class="panel">
<h1>Delete {{day}}</h1>
<form method="post" action="{% url 'day-delete' day.pk %}">
{% csrf_token %}
<p>
<input class="action-button action-delete" type="submit" value="Confirm Delete {{day}}"> or <a href="{% url 'day-detail' day.pk %}">cancel</a>
</p>
</form>
</article>
{% endblock %}

View File

@ -1,34 +0,0 @@
{% extends "base.html" %}
{% block content %}
<article class="panel">
<form action="{% url 'day-create' %}" method="POST">
{% csrf_token %}
<header>
<h1>Take Attendance</h1>
{{form.as_p}}
</header>
<table>
<thead>
<tr>
<td>Student</td>
<td>Present</td>
<td>Tardy</td>
<td>Absent</td>
</tr>
</thead>
<tbody>
{% for student in student_list %}
<tr>
<td>{{student.full_name}}</td>
<td><input type="radio" name="students_{{student.pk}}" value="P" checked></td>
<td><input type="radio" name="students_{{student.pk}}" value="T"></td>
<td><input type="radio" name="students_{{student.pk}}" value="A"></td>
</tr>
{% endfor %}
</tbody>
</table>
<input class="action-button" type="submit" value="Save attendance"> or <a href="{% url 'day-list' %}">cancel</a>
</form>
</article>
{% endblock %}

View File

@ -1,34 +0,0 @@
{% extends "base.html" %}
{% block content %}
<article class="panel">
<form action="{% url 'day-update' day.pk %}" method="POST">
{% csrf_token %}
<header>
<h1>Take Attendance</h1>
{{form.as_p}}
</header>
<table>
<thead>
<tr>
<td>Student</td>
<td>Present</td>
<td>Tardy</td>
<td>Absent</td>
</tr>
</thead>
<tbody>
{% for entry in day.entry_set.all %}
<tr>
<td>{{entry.student.full_name}}</td>
<td><input type="radio" name="students_{{entry.student.pk}}" value="P" {% if entry.status == "P" %}checked{% endif %}></td>
<td><input type="radio" name="students_{{entry.student.pk}}" value="T" {% if entry.status == "T" %}checked{% endif %}></td>
<td><input type="radio" name="students_{{entry.student.pk}}" value="A" {% if entry.status == "A" %}checked{% endif %}></td>
</tr>
{% endfor %}
</tbody>
</table>
<input class="action-button" type="submit" value="Save changes"> or <a href="{% url 'day-list' %}">cancel</a>
</form>
</article>
{% endblock %}

View File

@ -1,51 +0,0 @@
{% extends "base.html" %}
{% block content %}
<article class="panel">
<h1>Attendance</h1>
<p>
<a href="{% url 'day-create' %}" class="action-button">Take attendance</a>
</p>
<section>
{% for day in day_list %}
<h4><a href="{% url 'day-update' day.pk %}">{{day.date}}</a></h4>
<table>
<thead>
<tr>
<td>Student</td>
<td colspan="2">Status</td>
</tr>
</thead>
<tbody>
{% for entry in day.entry_set.all %}
<tr>
<td>{{entry.student}}</td>
<td>{{entry.get_status_display}}</td>
<td><a href="{% url 'entry-update' entry.pk %}">Update</a></td>
</tr>
{% endfor %}
</tbody>
</table>
{% empty %}
<p>No attendance taken yet.</p>
{% endfor %}
</section>
<section class="pagination">
<span class="step-links">
{% if page_obj.has_previous %}
<a class="minor-action-button" href="?page=1">&laquo; first</a>
<a class="minor-action-button" href="?page={{ page_obj.previous_page_number }}">&lsaquo; previous</a>
{% endif %}
<span class="current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
</span>
{% if page_obj.has_next %}
<a class="minor-action-button" href="?page={{ page_obj.next_page_number }}">next &rsaquo;</a>
<a class="minor-action-button" href="?page={{ page_obj.paginator.num_pages }}">last &raquo;</a>
{% endif %}
</span>
</section>
</article>
{% endblock %}

View File

@ -1,13 +0,0 @@
{% extends "base.html" %}
{% block content %}
<article class="panel">
<h1>Delete {{entry}}</h1>
<form method="post" action="{% url 'entry-delete' entry.pk %}">
{% csrf_token %}
<p>
<input class="action-button action-delete" type="submit" value="Confirm Delete {{entry}}"> or <a href="{% url 'day-detail' entry.day.pk %}">cancel</a>
</p>
</form>
</article>
{% endblock %}

View File

@ -1,20 +0,0 @@
{% extends "base.html" %}
{% block content %}
<article class="panel">
<div class="generic__header">
<h1>Update Entry</h1>
<a class="action-button action-delete" href="{% url 'entry-delete' entry.pk %}">Delete</a>
</div>
<p>For <strong>{{student}}</strong></p>
<section>
<form action="{% url 'entry-update' entry.pk %}" method="POST">
{% csrf_token %}
{{form.as_p}}
<p>
<input class="action-button" type="submit" value="Save changes"> or <a href="{% url 'student-detail' entry.student.pk %}">cancel</a>
</p>
</form>
</section>
</article>
{% endblock %}

View File

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

View File

@ -1,20 +0,0 @@
from django.urls import path, include
from . import views
urlpatterns = [
path('days/', views.DayListView.as_view(), name='day-list'),
path('days/new/', views.DayCreateView.as_view(), name='day-create'),
path('days/<int:pk>/', include([
path('', views.DayDetailView.as_view(), name='day-detail'),
path('update/', views.DayUpdateView.as_view(), name='day-update'),
path('delete/', views.DayDeleteView.as_view(), name='day-delete'),
])),
path('entries/', views.EntryListView.as_view(), name='entry-list'),
path('entries/new/', views.EntryCreateView.as_view(), name='entry-create'),
path('entries/<int:pk>/', include([
path('', views.EntryDetailView.as_view(), name='entry-detail'),
path('update/', views.EntryUpdateView.as_view(), name='entry-update'),
path('delete/', views.EntryDeleteView.as_view(), name='entry-delete'),
])),
]

View File

@ -1,98 +0,0 @@
from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.urls import reverse_lazy, reverse
from django.views.generic.base import TemplateView
from django.views.generic.edit import FormView, CreateView, UpdateView, DeleteView
from django.views.generic.detail import DetailView
from django.views.generic.list import ListView
from django.contrib.auth.mixins import LoginRequiredMixin
from django.utils import timezone
from django.db.models import Prefetch, Subquery, Count, Sum, F, Q, Value
from django.db.models.functions import Length, Upper
from students.models import Student
from .models import Day, Entry
from .forms import DayForm, EntryForm
# Days
class DayListView(LoginRequiredMixin, ListView):
model = Day
paginate_by = 7
def get_queryset(self):
object_list = Day.objects.all().prefetch_related('entry_set', 'entry_set__student')
return object_list
class DayCreateView(LoginRequiredMixin, CreateView):
model = Day
template_name_suffix = '_create_form'
form_class = DayForm
success_url = reverse_lazy('profile-detail')
def get_initial(self):
today = timezone.localtime(timezone.now()).date()
initial = {
'date': today,
}
return initial
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['student_list'] = Student.objects.all()
return context
def form_valid(self, form):
form.save()
for key, value in self.request.POST.items():
if 'student' in key:
s = key.split('_')[1]
Entry.objects.create(
day=form.instance,
student=Student.objects.get(pk=s),
status=value,
)
return super().form_valid(form)
class DayDetailView(LoginRequiredMixin, DetailView):
model = Day
class DayUpdateView(LoginRequiredMixin, UpdateView):
model = Day
form_class = DayForm
success_url = reverse_lazy('day-list')
def form_valid(self, form):
form.save()
for key, value in self.request.POST.items():
if 'student' in key:
s = key.split('_')[1]
Entry.objects.filter(day=self.object, student=s).update(status=value)
return super().form_valid(form)
class DayDeleteView(LoginRequiredMixin, DeleteView):
model = Day
success_url = reverse_lazy('day-list')
# Entries
class EntryListView(LoginRequiredMixin, ListView):
model = Entry
class EntryCreateView(LoginRequiredMixin, CreateView):
model = Entry
template_name_suffix = '_create_form'
fields = ('__all__')
class EntryDetailView(LoginRequiredMixin, DetailView):
model = Entry
class EntryUpdateView(LoginRequiredMixin, UpdateView):
model = Entry
form_class = EntryForm
success_url = reverse_lazy('day-list')
class EntryDeleteView(LoginRequiredMixin, DeleteView):
model = Entry
success_url = reverse_lazy('day-list')

View File

@ -28,6 +28,7 @@ class StudentForm(forms.ModelForm):
'last_name',
'address',
'dob',
'allergies',
'tags'
]
labels = {

View File

@ -1,4 +1,4 @@
# Generated by Django 4.0.5 on 2022-06-29 18:50
# Generated by Django 4.0.5 on 2022-07-28 20:46
from django.db import migrations, models
import django.db.models.deletion
@ -9,16 +9,41 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
]
operations = [
migrations.CreateModel(
name='Component',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50)),
('category', models.CharField(choices=[('QUIZ', 'Quiz'), ('ASSIGNMENT', 'Assignment'), ('TEST', 'Test')], default='ASSIGNMENT', max_length=255)),
('due_date', models.DateField()),
('grade_total', models.PositiveIntegerField()),
('finished_grading', models.BooleanField(default=False)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
],
options={
'ordering': ['due_date'],
},
),
migrations.CreateModel(
name='SchoolYear',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('start_date', models.DateField()),
('end_date', models.DateField()),
('year', models.PositiveIntegerField(unique=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
],
),
migrations.CreateModel(
name='StudentTag',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
],
),
migrations.CreateModel(
@ -26,6 +51,8 @@ class Migration(migrations.Migration):
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
],
options={
'ordering': ['name'],
@ -37,21 +64,14 @@ class Migration(migrations.Migration):
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=250)),
('description', models.CharField(blank=True, max_length=250)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('school_year', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='core.schoolyear')),
],
options={
'ordering': ['name'],
},
),
migrations.CreateModel(
name='StudentTag',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('tag', models.SlugField()),
('object_id', models.PositiveIntegerField()),
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')),
],
),
migrations.CreateModel(
name='Student',
fields=[
@ -61,44 +81,58 @@ class Migration(migrations.Migration):
('last_name', models.CharField(max_length=50)),
('address', models.TextField(blank=True)),
('dob', models.DateField()),
('allergies', models.CharField(blank=True, max_length=255)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('school_year', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='core.schoolyear')),
('tags', models.ManyToManyField(blank=True, to='core.studenttag')),
],
options={
'ordering': ['student_id', 'first_name'],
},
),
migrations.CreateModel(
name='Score',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('value', models.PositiveIntegerField()),
('component', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.component')),
('student', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.student')),
],
options={
'ordering': ['student'],
},
),
migrations.CreateModel(
name='SchoolDay',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('date', models.DateField()),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('school_year', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='core.schoolyear')),
],
options={
'ordering': ['-date'],
},
),
migrations.CreateModel(
name='Component',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50)),
('category', models.CharField(choices=[('QUIZ', 'Quiz'), ('ASSIGNMENT', 'Assignment'), ('TEST', 'Test')], default='ASSIGNMENT', max_length=255)),
('due_date', models.DateField()),
('grade_total', models.PositiveIntegerField()),
('finished_grading', models.BooleanField(default=False)),
('subject', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.subject')),
('tags', models.ManyToManyField(blank=True, to='core.tag')),
],
options={
'ordering': ['due_date'],
},
migrations.AddField(
model_name='component',
name='subject',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.subject'),
),
migrations.AddField(
model_name='component',
name='tags',
field=models.ManyToManyField(blank=True, to='core.tag'),
),
migrations.CreateModel(
name='AttendanceEntry',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('status', models.CharField(choices=[('P', 'Present'), ('T', 'Tardy'), ('A', 'Absent')], default='P', max_length=1)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('school_day', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.schoolday')),
('student', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.student')),
],
@ -107,8 +141,4 @@ class Migration(migrations.Migration):
'ordering': ['student'],
},
),
migrations.AddIndex(
model_name='studenttag',
index=models.Index(fields=['content_type', 'object_id'], name='core_studen_content_a7305d_idx'),
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 4.0.5 on 2022-06-29 20:35
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='schoolyear',
name='start_date',
field=models.DateField(unique_for_year=True),
),
]

View File

@ -1,27 +0,0 @@
# Generated by Django 4.0.5 on 2022-06-29 20:47
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0002_alter_schoolyear_start_date'),
]
operations = [
migrations.RemoveField(
model_name='schoolyear',
name='end_date',
),
migrations.RemoveField(
model_name='schoolyear',
name='start_date',
),
migrations.AddField(
model_name='schoolyear',
name='year',
field=models.PositiveIntegerField(default=2021),
preserve_default=False,
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 4.0.5 on 2022-06-29 20:48
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0003_remove_schoolyear_end_date_and_more'),
]
operations = [
migrations.AlterField(
model_name='schoolyear',
name='year',
field=models.PositiveIntegerField(unique=True),
),
]

View File

@ -1,104 +0,0 @@
# Generated by Django 4.0.5 on 2022-06-29 21:27
import datetime
from django.db import migrations, models
from django.utils.timezone import utc
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('core', '0004_alter_schoolyear_year'),
]
operations = [
migrations.AddField(
model_name='attendanceentry',
name='created_at',
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
preserve_default=False,
),
migrations.AddField(
model_name='attendanceentry',
name='updated_at',
field=models.DateTimeField(auto_now=True),
),
migrations.AddField(
model_name='component',
name='created_at',
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
preserve_default=False,
),
migrations.AddField(
model_name='component',
name='updated_at',
field=models.DateTimeField(auto_now=True),
),
migrations.AddField(
model_name='schoolday',
name='created_at',
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
preserve_default=False,
),
migrations.AddField(
model_name='schoolday',
name='updated_at',
field=models.DateTimeField(auto_now=True),
),
migrations.AddField(
model_name='schoolyear',
name='created_at',
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
preserve_default=False,
),
migrations.AddField(
model_name='schoolyear',
name='updated_at',
field=models.DateTimeField(auto_now=True),
),
migrations.AddField(
model_name='student',
name='created_at',
field=models.DateTimeField(auto_now_add=True, default=datetime.datetime(2022, 6, 29, 21, 26, 47, 547231, tzinfo=utc)),
preserve_default=False,
),
migrations.AddField(
model_name='student',
name='updated_at',
field=models.DateTimeField(auto_now=True),
),
migrations.AddField(
model_name='studenttag',
name='created_at',
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
preserve_default=False,
),
migrations.AddField(
model_name='studenttag',
name='updated_at',
field=models.DateTimeField(auto_now=True),
),
migrations.AddField(
model_name='subject',
name='created_at',
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
preserve_default=False,
),
migrations.AddField(
model_name='subject',
name='updated_at',
field=models.DateTimeField(auto_now=True),
),
migrations.AddField(
model_name='tag',
name='created_at',
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
preserve_default=False,
),
migrations.AddField(
model_name='tag',
name='updated_at',
field=models.DateTimeField(auto_now=True),
),
]

View File

@ -1,26 +0,0 @@
# Generated by Django 4.0.5 on 2022-06-30 20:03
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('core', '0005_attendanceentry_created_at_and_more'),
]
operations = [
migrations.CreateModel(
name='Score',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('value', models.PositiveIntegerField()),
('component', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.component')),
('student', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.student')),
],
options={
'ordering': ['student'],
},
),
]

View File

@ -1,30 +0,0 @@
# Generated by Django 4.0.5 on 2022-07-27 19:55
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0006_score'),
]
operations = [
migrations.RemoveIndex(
model_name='studenttag',
name='core_studen_content_a7305d_idx',
),
migrations.RemoveField(
model_name='studenttag',
name='content_type',
),
migrations.RemoveField(
model_name='studenttag',
name='object_id',
),
migrations.AddField(
model_name='student',
name='tags',
field=models.ManyToManyField(blank=True, to='core.studenttag'),
),
]

View File

@ -1,23 +0,0 @@
# Generated by Django 4.0.5 on 2022-07-27 19:57
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0007_remove_studenttag_core_studen_content_a7305d_idx_and_more'),
]
operations = [
migrations.RemoveField(
model_name='studenttag',
name='tag',
),
migrations.AddField(
model_name='studenttag',
name='name',
field=models.CharField(default='Allergy: Seasonal/Hay', max_length=255),
preserve_default=False,
),
]

View File

@ -61,6 +61,7 @@ class Student(models.Model):
last_name = models.CharField(max_length=50)
address = models.TextField(blank=True)
dob = models.DateField()
allergies = models.CharField(max_length=255, blank=True)
tags = models.ManyToManyField(StudentTag, blank=True)

View File

@ -1,13 +1,31 @@
{% extends 'base.html' %}
{% block head_title %}Edit Attendance | {% endblock head_title %}
{% block breadcrumbs %}
<div class="breadcrumbs">
<menu>
<li><strong><a href="{% url 'core:schoolyear-detail' entry.school_day.school_year.year %}">{{ entry.school_day.school_year.year }}</a></strong></li>
<span></span>
<li><a href="{% url 'core:schoolday-list' entry.school_day.school_year.year %}">Attendance</a></li>
<span></span>
<li><a href="{% url 'core:schoolday-detail' entry.school_day.school_year.year entry.school_day.pk %}">{{ entry.school_day }}</a></li>
</menu>
</div>
{% endblock breadcrumbs %}
{% block content %}
<h1>Delete Attendanceentry</h1>
<form method="POST" action="{% url 'core:attendanceentry-delete' attendanceentry.pk %}">
{% csrf_token %}
<p>Are you sure you want to delete "{{ attendanceentry }}"?</p>
{{ form.as_p }}
<p>
<input type="submit" value="Delete"> or <a href="{% url 'core:attendanceentry-detail' attendanceentry.pk %}">cancel</a>
</p>
</form>
<article class="form">
<header class="form__header">
<h1>Delete Attendanceentry</h1>
</header>
<form method="POST" action="{% url 'core:entry-delete' entry.school_day.school_year.year entry.school_day.pk entry.pk %}">
{% csrf_token %}
<p>Are you sure you want to delete "{{ attendanceentry }}"?</p>
{{ form.as_p }}
<p>
<input type="submit" value="Delete"> or <a href="{% url 'core:schoolday-detail' entry.school_day.school_year.year entry.school_day.pk %}">cancel</a>
</p>
</form>
</article>
{% endblock %}

View File

@ -1,6 +1,6 @@
{% extends 'base.html' %}
{% block head_title %}Attendance | {% endblock head_title %}
{% block head_title %}Edit Attendance | {% endblock head_title %}
{% block breadcrumbs %}
<div class="breadcrumbs">

View File

@ -0,0 +1,9 @@
{% extends 'base.html' %}
{% block head_title %}Grades & Attendance Management Software |{% endblock %}
{% block content %}
<article>
<h1>Grades and Attendance Management Software</h1>
</article>
{% endblock %}

View File

@ -30,7 +30,7 @@
<tbody>
{% for student in student_list %}
<tr>
<td><h5>{{student.full_name}}</h5></td>
<td><h5>{{ student.student_id }} &ensp; / &ensp; {{ student.full_name }}</h5></td>
<td><input type="radio" name="students_{{student.pk}}" value="P" checked></td>
<td><input type="radio" name="students_{{student.pk}}" value="T"></td>
<td><input type="radio" name="students_{{student.pk}}" value="A"></td>

View File

@ -37,7 +37,7 @@
<tbody>
{% for entry in schoolday.attendanceentry_set.all %}
<tr>
<td><h5>{{entry.student.full_name}}</h5></td>
<td><h5>{{ entry.student.student_id }} &ensp; / &ensp; {{ entry.student.full_name }}</h5></td>
<td><input type="radio" name="students_{{ entry.student.pk }}" value="P" {% if entry.status == "P" %}checked{% endif %}></td>
<td><input type="radio" name="students_{{ entry.student.pk }}" value="T" {% if entry.status == "T" %}checked{% endif %}></td>
<td><input type="radio" name="students_{{ entry.student.pk }}" value="A" {% if entry.status == "A" %}checked{% endif %}></td>

View File

@ -1,12 +1,14 @@
{% extends 'base.html' %}
{% block content %}
<h1>+ New Schoolyear</h1>
<form method="POST" action="{% url 'core:schoolyear-create' %}">
{% csrf_token %}
{{ form.as_p }}
<p>
<input type="submit" value="Create"> or <a href="{% url 'core:schoolyear-list' %}">cancel</a>
</p>
</form>
<article class="form">
<h1>+ New School year</h1>
<form method="POST" action="{% url 'core:schoolyear-create' %}">
{% csrf_token %}
{{ form.as_p }}
<p>
<input type="submit" value="Create"> or <a href="{% url 'core:schoolyear-list' %}">cancel</a>
</p>
</form>
</article>
{% endblock %}

View File

@ -8,9 +8,7 @@
<div>
<h1>{{ schoolyear }}</h1>
</div>
{% if perms.core.can_change_schoolyear %}
<a href="{% url 'core:schoolyear-update' schoolyear.year %}" class="action-button">Edit</a>
{% endif %}
</header>
<section class="tools">
<a href="{% url 'core:student-list' schoolyear.year %}" class="tool">

View File

@ -8,9 +8,7 @@
<header class="list__header">
<div class="list__title">
<h1>School Years</h1>
{% if perms.core.can_add_schoolyear %}
<a href="{% url 'core:schoolyear-create' %}" class="action-button">+ New Year</a>
{% endif %}
</div>
</header>

View File

@ -1,7 +1,7 @@
{% extends 'base.html' %}
{% load helpers %}
{% block head_title %}Student {{ student.student_id }} | {% endblock head_title %}
{% block head_title %}{{ student.student_id }} / {{ student.full_name }} | {% endblock head_title %}
{% block breadcrumbs %}
<div class="breadcrumbs">
@ -29,6 +29,8 @@
<dd>{{ student.dob }}</dd>
<dt>Age</dt>
<dd>{{ student.age }}</dd>
<dt>Allergies</dt>
<dd>{% if student.allergies %}<span class="tag tag__warning">{{ student.allergies }}</span>{% endif %}</dd>
{% if student.address %}
<dt>Address</dt>
<dd>
@ -57,7 +59,7 @@
<tbody>
{% for subject in subject_list %}
<tr>
<td class="grade"><h5>{{ subject }}</h5></td>
<td class="grade"><h5>{{ subject }}</h5> {{ subject.description }}</td>
<td class="grade">{{ subject.grade|grade_as_percentage:subject.grade_total }}%</td>
</tr>
{% empty %}

View File

@ -25,6 +25,9 @@
<tr>
<th><a href="?order_by=record_num&sort=asc">Student No. &varr;</a></th>
<th>Name</th>
<th>Age</th>
<th>Birthday</th>
<th>Allergies</th>
<th>Tags</th>
</tr>
</thead>
@ -33,6 +36,9 @@
<tr class="has-link" onclick="document.location='{% url 'core:student-detail' school_year.year student.pk %}'">
<td>{{ student.student_id }}</td>
<td><h5>{{ student }}</h5></td>
<td>{{ student.age }}</td>
<td>{{ student.dob }}</td>
<td>{% if student.allergies %}<span class="tag tag__warning">{{ student.allergies }}</span>{% endif %}</td>
<td>
{% for tag in student.tags.all %}
<a href="{% url 'core:stag-detail' school_year.year tag.pk %}" class="tag tag__info">{{ tag }}</a>
@ -41,7 +47,7 @@
</tr>
{% empty %}
<tr>
<td colspan="3">No students yet.</td>
<td colspan="6">No students yet.</td>
</tr>
{% endfor %}
</tbody>

View File

@ -1,8 +1,22 @@
{% extends 'base.html' %}
{% block head_title %}New Subject | {% endblock head_title %}
{% block breadcrumbs %}
<div class="breadcrumbs">
<menu>
<li><strong><a href="{% url 'core:schoolyear-detail' school_year.year %}">{{ school_year.year }}</a></strong></li>
<span></span>
<li><a href="{% url 'core:subject-list' school_year.year %}">Subjects</a></li>
</menu>
</div>
{% endblock breadcrumbs %}
{% block content %}
<article class="form">
<h1>+ New Subject</h1>
<header class="form__header">
<h1>+ New Subject</h1>
</header>
<form method="POST" action="{% url 'core:subject-create' school_year.year %}">
{% csrf_token %}
{{ form.as_p }}

View File

@ -1,7 +1,7 @@
{% extends 'base.html' %}
{% load helpers %}
{% block head_title %}Subject {{ subject.subject_id }} | {% endblock head_title %}
{% block head_title %}{{ subject }} | {% endblock head_title %}
{% block breadcrumbs %}
<div class="breadcrumbs">

View File

@ -18,7 +18,7 @@
<h1>Subjects</h1>
<a href="{% url 'core:subject-create' school_year.year %}" class="action-button">+ New Subject</a>
</div>
<a href="">Subject Tags &rarr;</a>
<a href="">Tags &rarr;</a>
</header>
<table class="list__table">
<thead>

View File

@ -1,6 +1,8 @@
{% extends 'base.html' %}
{% load static %}
{% block head_title %}Today |{% endblock %}
{% block content %}
<article class="panel">
<header>
@ -50,7 +52,7 @@
</tbody>
</table>
{% empty %}
<p class="greeting">No attendance taken yet: <a href="{% url 'core:day-create' %}" class="action-button">Take attendance</a></p>
<p class="greeting">No attendance taken yet: <a href="{% url 'core:schoolday-create' current_year.year %}" class="action-button">Take attendance &rarr;</a></p>
{% endfor %}
</section>
<section>

View File

@ -2,7 +2,7 @@ from django.urls import path, include
from . import views
urlpatterns = [
path('today/', views.TodayView.as_view(), name='today'),
path('', views.HomeView.as_view(), name='home'),
# SchoolYears
path('years/', include([
@ -27,6 +27,7 @@ urlpatterns = [
views.SchoolYearUpdateView.as_view(),
name='schoolyear-update'
),
path('today/', views.TodayView.as_view(), name='today'),
# Students
path('students/', include([

View File

@ -34,12 +34,29 @@ from .models import (
)
from .forms import (
StudentForm,
SchoolYearCreateForm,
ComponentCreateForm,
SchoolDayForm,
AttendanceEntryForm
)
# SEARCH
# # Look up Q objects for combining different fields in a single query
# from django.db.models import Q
# people = Person.objects.filter(Q(first_name__contains=query) | Q(last_name__contains=query)
# restaurants = Restaurant.objects.filter(restaurant_name__contains=query)
# pizzas = Pizza.objects.filter(pizza_name__contains=query)
# Then combine the results, if you want
# from itertools import chain
# results = chain(people, restaurants, pizzas)
class HomeView(TemplateView):
template_name = 'core/home.html'
class TodayView(LoginRequiredMixin, TemplateView):
template_name = 'core/today.html'
@ -50,22 +67,27 @@ class TodayView(LoginRequiredMixin, TemplateView):
enddate = today + dt.timedelta(days=7)
context['birthdays'] = Student.objects.filter(
dob__month=today.month,
dob__day__range=[today.day, enddate.day]
).order_by('dob')
school_year__year=self.kwargs['year'],
dob__month=today.month,
dob__day__range=[today.day, enddate.day]
).order_by('dob')
context['components'] = Component.objects.filter(
due_date=today
).select_related('subject')
).select_related('subject')
context['attendance'] = SchoolDay.objects.filter(
date=today
).prefetch_related('attendanceentry_set', 'attendanceentry_set__student')
).prefetch_related(
'attendanceentry_set',
'attendanceentry_set__student'
)
context['ungraded_components'] = Component.objects.filter(
subject__school_year__year=self.kwargs['year'],
due_date__lte=today,
finished_grading=False
).select_related('subject')
).select_related('subject')
return context
@ -82,7 +104,9 @@ class SchoolYearListView(LoginRequiredMixin, ListView):
return queryset
class SchoolYearCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
class SchoolYearCreateView(
LoginRequiredMixin, SuccessMessageMixin, CreateView
):
model = SchoolYear
success_message = 'SchoolYear created.'
form_class = SchoolYearCreateForm
@ -95,7 +119,9 @@ class SchoolYearDetailView(LoginRequiredMixin, DetailView):
slug_field = 'year'
class SchoolYearUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
class SchoolYearUpdateView(
LoginRequiredMixin, SuccessMessageMixin, UpdateView
):
model = SchoolYear
slug_url_kwarg = 'year'
slug_field = 'year'
@ -125,13 +151,7 @@ class StudentCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
model = Student
success_message = 'Student created.'
template_name_suffix = '_create_form'
fields = [
'student_id',
'first_name',
'last_name',
'address',
'dob',
]
form_class = StudentForm
def form_valid(self, form):
form.instance.school_year = get_object_or_404(
@ -141,7 +161,9 @@ class StudentCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['school_year'] = get_object_or_404(SchoolYear, year=self.kwargs['year'])
context['school_year'] = get_object_or_404(
SchoolYear, year=self.kwargs['year']
)
return context
@ -207,7 +229,9 @@ class StudentTagListView(LoginRequiredMixin, ListView):
return context
class StudentTagCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
class StudentTagCreateView(
LoginRequiredMixin, SuccessMessageMixin, CreateView
):
model = StudentTag
success_message = 'Tag created.'
template_name_suffix = '_create_form'
@ -231,7 +255,7 @@ class StudentTagDetailView(LoginRequiredMixin, DetailView):
pk_url_kwarg = 'stag_pk'
context_object_name = 'tag'
def get_object(self):
def get_queryset(self):
queryset = StudentTag.objects.filter(
pk=self.kwargs.get(self.pk_url_kwarg)
).prefetch_related(
@ -242,8 +266,7 @@ class StudentTagDetailView(LoginRequiredMixin, DetailView):
)
)
)
obj = queryset.get()
return obj
return queryset
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
@ -253,7 +276,9 @@ class StudentTagDetailView(LoginRequiredMixin, DetailView):
return context
class StudentTagUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
class StudentTagUpdateView(
LoginRequiredMixin, SuccessMessageMixin, UpdateView
):
model = StudentTag
pk_url_kwarg = 'stag_pk'
context_object_name = 'tag'
@ -273,7 +298,9 @@ class StudentTagUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
})
class StudentTagDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteView):
class StudentTagDeleteView(
LoginRequiredMixin, SuccessMessageMixin, DeleteView
):
model = StudentTag
pk_url_kwarg = 'stag_pk'
context_object_name = 'tag'
@ -338,9 +365,11 @@ class SubjectCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
class SubjectDetailView(LoginRequiredMixin, DetailView):
model = Subject
def get_object(self):
def get_queryset(self):
queryset = Subject.objects.filter(
pk=self.kwargs.get(self.pk_url_kwarg)
).select_related(
'school_year'
).prefetch_related(
Prefetch(
'component_set',
@ -351,8 +380,7 @@ class SubjectDetailView(LoginRequiredMixin, DetailView):
)
)
)
obj = queryset.get()
return obj
return queryset
class SubjectUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
@ -415,7 +443,7 @@ class ComponentDetailView(LoginRequiredMixin, DetailView):
model = Component
pk_url_kwarg = 'component_pk'
def get_object(self):
def get_queryset(self):
queryset = Component.objects.filter(
pk=self.kwargs.get(self.pk_url_kwarg)
).prefetch_related(
@ -427,8 +455,7 @@ class ComponentDetailView(LoginRequiredMixin, DetailView):
).annotate(
grade_avg_pre=Avg('score__value')
)
obj = queryset.get()
return obj
return queryset
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
@ -482,7 +509,7 @@ class ComponentManagerView(LoginRequiredMixin, UpdateView):
).annotate(
cscore=Subquery(cscores.values('value')),
cscore_pk=Subquery(cscores.values('pk'))
)
)
return context
def get_success_url(self):

32184
src/db.json Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,12 +0,0 @@
from django.contrib import admin
from .models import (
Tag,
Subject,
Component,
Score,
)
admin.site.register(Tag)
admin.site.register(Subject)
admin.site.register(Component)
admin.site.register(Score)

View File

@ -1,6 +0,0 @@
from django.apps import AppConfig
class GradebookConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'gradebook'

View File

@ -1,43 +0,0 @@
from django import forms
from .models import Component, Score, Tag
from students.models import Student
class ComponentForm(forms.ModelForm):
class Meta:
model = Component
fields = (
'name',
'category',
'due_date',
'grade_total',
)
widgets = {
'due_date': forms.DateInput(attrs = {
'type': 'date'
}),
}
class ComponentUpdateForm(forms.ModelForm):
class Meta:
model = Component
fields = (
'subject',
'name',
'category',
'due_date',
'grade_total',
'tags',
)
widgets = {
'due_date': forms.DateInput(attrs = {
'type': 'date'
}),
}
class TagForm(forms.ModelForm):
class Meta:
model = Tag
fields = (
'name',
)

View File

@ -1,69 +0,0 @@
# Generated by Django 3.2.7 on 2021-09-01 15:26
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('students', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='Component',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50)),
('category', models.CharField(choices=[('QZ', 'Quiz'), ('AS', 'Assignment'), ('TS', 'Test')], default='AS', max_length=2)),
('due_date', models.DateField()),
('grade_total', models.PositiveIntegerField()),
],
options={
'ordering': ['due_date'],
},
),
migrations.CreateModel(
name='Subject',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=250)),
('description', models.CharField(blank=True, max_length=250)),
],
options={
'ordering': ['name'],
},
),
migrations.CreateModel(
name='Tag',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50)),
],
),
migrations.CreateModel(
name='Score',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('value', models.PositiveIntegerField()),
('component', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='gradebook.component')),
('student', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='students.student')),
],
options={
'ordering': ('student',),
},
),
migrations.AddField(
model_name='component',
name='subject',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='gradebook.subject'),
),
migrations.AddField(
model_name='component',
name='tags',
field=models.ManyToManyField(blank=True, to='gradebook.Tag'),
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 3.2.7 on 2021-09-16 23:21
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('gradebook', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='component',
name='finished_grading',
field=models.BooleanField(default=False),
),
]

View File

@ -1,100 +0,0 @@
from datetime import datetime, date
from django.db import models
from django.urls import reverse
from django.db.models import Count, Sum, Avg, F, Value
from django.db.models.functions import Length, Upper
from students.models import Student
class Tag(models.Model):
name = models.CharField(max_length=50)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('tag-detail', kwargs={'pk': self.pk})
class Subject(models.Model):
class Meta:
ordering = ['name']
name = models.CharField(max_length=250)
description = models.CharField(max_length=250, blank=True)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('subject-detail', kwargs={'pk': self.pk})
class Component(models.Model):
class Meta:
ordering = ['due_date']
CATEGORY_CHOICES = [
('QZ', 'Quiz'),
('AS', 'Assignment'),
('TS', 'Test'),
]
subject = models.ForeignKey(Subject, on_delete=models.CASCADE)
name = models.CharField(max_length=50)
category = models.CharField(
max_length = 2,
choices = CATEGORY_CHOICES,
default='AS',
)
due_date = models.DateField()
grade_total = models.PositiveIntegerField()
tags = models.ManyToManyField(Tag, blank=True)
finished_grading = models.BooleanField(default=False)
@property
def is_due(self):
if self.due_date < date.today():
return True
else:
return False
@property
def grade_avg(self):
avg = Score.objects.filter(component=self.pk).aggregate(
Avg('value')
)
if avg['value__avg'] is not None:
return round(avg['value__avg'], 2)
else:
return "No scores yet."
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('component-detail', kwargs={'pk': self.pk})
class Score(models.Model):
class Meta:
ordering = ('student',)
component = models.ForeignKey(Component, on_delete=models.CASCADE)
student = models.ForeignKey(Student, on_delete=models.CASCADE)
value = models.PositiveIntegerField()
@property
def grade(self):
return f"{self.value} / {self.component.grade_total}"
@property
def grade_as_percentage(self):
return round(self.value / self.component.grade_total * 100, 2)
def __str__(self):
return f"{self.student} scored: {self.value} / {self.component.grade_total}"
def get_absolute_url(self):
return reverse('score-detail', kwargs={'pk': self.pk})

View File

@ -1,13 +0,0 @@
{% extends "base.html" %}
{% block content %}
<article class="panel">
<h1>Delete {{component}}</h1>
<form method="post" action="{% url 'component-delete' component.subject.pk component.pk %}">
{% csrf_token %}
<p>
<input class="action-button action-delete" type="submit" value="Confirm Delete {{component}}"> or <a href="{% url 'component-detail' component.subject.pk component.pk %}">cancel</a>
</p>
</form>
</article>
{% endblock %}

View File

@ -1,17 +0,0 @@
{% extends "base.html" %}
{% block content %}
<article class="panel">
<h1>{{subject}}</h1>
<h2>Create Component</h2>
<section>
<form action="{% url 'component-create' subject.pk %}" method="POST">
{% csrf_token %}
{{form.as_p}}
<p>
<input class="action-button" type="submit" value="Create component"> or <a href="{% url 'subject-detail' subject.pk %}">cancel</a>
</p>
</form>
</section>
</article>
{% endblock %}

View File

@ -1,77 +0,0 @@
{% extends "base.html" %}
{% block content %}
<article class="panel">
<header class="generic__header">
<h1>{{component.name}}</h1>
<a href="{% url 'component-update' component.subject.pk component.pk %}" class="action-button">Edit</a>
</header>
{% if component.finished_grading %}
<section>
<p>
<span class="component__grade--graded">✓ Graded</span>
</p>
</section>
{% endif %}
{% if component.tags.count > 0 %}
<section>
<span>
{% for tag in component.tags.all %}
<a class="tag__item" href="{% url 'tag-detail' tag.pk %}">{{tag.name}}</a>
{% endfor %}
</span>
</section>
{% endif %}
<section>
<dl>
<dt>Due Date</dt>
<dd>{{component.due_date}}</dd>
<dt>Description</dt>
<dd>{{component.name}}</dd>
<dt>Category</dt>
<dd>{{component.get_category_display}}</dd>
<dt>Grade Total</dt>
<dd>{{component.grade_total}}</dd>
</dl>
</section>
<section>
<h3>Scores</h3>
<p>
<a class="action-button" href="{% url 'component-manager' component.subject.pk component.pk %}">Bulk Edit Scores</a>
</p>
<table>
<thead>
<tr>
<td>Student</td>
<td colspan="2">Score</td>
</tr>
</thead>
<tbody>
{% for score in component.score_set.all %}
<tr>
<td><a href="{% url 'student-detail' score.student.pk %}">{{score.student.student_id}} &mdash; {{score.student}}</a></td>
<td>{{score.value}}</td>
<td><a href="{% url 'score-update' score.pk %}">Edit score</a></td>
</tr>
{% endfor %}
<tr>
<td><strong>Avg Score</strong></td>
<td><strong>{{component.grade_avg_pre|floatformat:2}}</strong></td>
</tr>
</tbody>
</table>
{% if scoreless %}
<section>
<h3>Scoreless</h3>
<ul>
{% for student in scoreless %}
<li>{{student}}</li>
{% endfor %}
</ul>
</section>
{% endif %}
</section>
</article>
{% endblock %}

View File

@ -1,19 +0,0 @@
{% extends "base.html" %}
{% block content %}
<article class="panel">
<div class="generic__header">
<h1>Edit Component</h1>
<a class="action-button action-delete" href="{% url 'component-delete' component.subject.pk component.pk %}">Delete</a>
</div>
<section>
<form action="{% url 'component-update' component.subject.pk component.pk %}" method="POST">
{% csrf_token %}
{{form.as_p}}
<p>
<input class="action-button" type="submit" value="Save changes"> or <a href="{% url 'component-detail' component.subject.pk component.pk %}">cancel</a>
</p>
</form>
</section>
</article>
{% endblock %}

View File

@ -1,41 +0,0 @@
{% extends "base.html" %}
{% load gradebook_filters %}
{% block content %}
<article class="panel">
<h1>{{component}}, <em>{{component.subject}}</em></h1>
<section>
<form action="{% url 'component-manager' component.subject.pk component.pk %}" method="POST">
{% csrf_token %}
<h3>Enter Scores</h3>
<table>
<thead>
<tr>
<td>Out of:</td>
<td>{{component.grade_total}}</td>
</tr>
<tr>
<td>Student</td>
<td>Score</td>
{% if formset.errors %}
<td>Errors</td>
{% endif %}
</tr>
</thead>
<tbody>
{% for student in student_list %}
<tr>
<td>{{student.full_name}}</td>
<td><input type="number" name="student_{{student.pk}}" min="0" max="{{component.grade_total}}" value="{{student.cscore}}"></td>
</tr>
{% endfor %}
</tbody>
</table>
{{form.as_p}}
<p>
<input class="action-button" type="submit" value="Save changes"> or <a href="{% url 'component-detail' component.subject.pk component.pk %}">cancel</a>
</p>
</form>
</section>
</article>
{% endblock %}

View File

@ -1,13 +0,0 @@
{% extends "base.html" %}
{% block content %}
<article class="panel">
<h1>Delete {{score}}</h1>
<form method="post" action="{% url 'score-delete' score.pk %}">
{% csrf_token %}
<p>
<input class="action-button action-delete" type="submit" value="Confirm Delete {{score}}"> or <a href="{% url 'score-detail' score.pk %}">cancel</a>
</p>
</form>
</article>
{% endblock %}

View File

@ -1,16 +0,0 @@
{% extends "base.html" %}
{% block content %}
<article class="panel">
<h1>Create Score</h1>
<section>
<form action="{% url 'score-create' %}" method="POST">
{% csrf_token %}
{{form.as_p}}
<p>
<input class="action-button" type="submit" value="Create Score"> or <a href="{% url 'subject-list' %}">cancel</a>
</p>
</form>
</section>
</article>
{% endblock %}

View File

@ -1,19 +0,0 @@
{% extends "base.html" %}
{% block content %}
<article class="panel">
<h1>Update Score</h1>
<h2>{{score.component.subject}}&mdash; <em>{{score.component.get_category_display}}</em>: {{score.component}}</h2>
<section>
<form action="{% url 'score-update' score.pk %}" method="POST">
{% csrf_token %}
<input type="hidden" name="next" value="{{next}}">
<h3>{{score.student}}</h3>
{{form.as_p}}
<p>
<input class="action-button" type="submit" value="Save changes"> or <a href="{% url 'student-detail' score.student.pk %}">cancel</a>
</p>
</form>
</section>
</article>
{% endblock %}

View File

@ -1,83 +0,0 @@
{% extends "base.html" %}
{% block content %}
<article class="panel">
<h1>Search Results</h1>
{% if student_list.count > 0 %}
<section>
<h3>Students</h3>
<ul>
{% for student in student_list %}
<li class="student">
<a class="student__li" href="{% url 'student-detail' student.pk %}">{{student.student_id}} &mdash; {{student.full_name}}</a>
{% if student.sit %}
<span class="student__attribute">SIT: {{student.get_sit_display}}</span>
{% endif %}
{% if student.iep_behavioral %}
<span class="student__attribute">IEP behavioral</span>
{% endif %}
{% if student.iep_math %}
<span class="student__attribute">IEP math</span>
{% endif %}
{% if student.iep_ela %}
<span class="student__attribute">IEP ELA</span>
{% endif %}
{% if student.parent__count > 0 %}
<br>Parents:
{% for parent in student.parent_set.all %}
<a href="{% url 'parent-detail' parent.pk %}">{{parent.full_name}}</a>{% if not forloop.last %},{% endif %}
{% endfor %}
{% endif %}
</li>
{% endfor %}
</ul>
</section>
{% endif %}
{% if component_list.count > 0 %}
<section>
<h3>Components</h3>
<ul class="search__results">
<li class="search__result">
<strong>Subject</strong>
<strong>Component</strong>
<strong>Due Date</strong>
<strong>Tags</strong>
</li>
<hr>
{% for component in component_list %}
<li class="search__result">
{{component.subject}}
<a href="{% url 'component-detail' component.subject.pk component.pk %}">{{component}}</a>
<span>{{component.due_date}}</span>
<span>
{% for tag in component.tags.all %}
<a class="tag__item" href="{% url 'tag-detail' tag.pk %}">{{tag.name}}</a>
{% endfor %}
</span>
</li>
{% empty %}
<p>No components by that name were found.</p>
{% endfor %}
</ul>
</section>
{% endif %}
{% if message_list.count > 0 %}
<section>
<h3>Messages</h3>
<ul class="search__results">
{% for message in message_list %}
<li class="search__mresult">
<p>
<a href="{% url 'thread-detail' message.thread.pk %}">{{message.thread.subject}}</a><br>
{{message.content|truncatewords:25}}<br>
<a class="" href="{% url 'thread-detail' message.thread.pk %}">Read more &rarr;</a>
</p>
</li>
{% empty %}
<p>No components by that name were found.</p>
{% endfor %}
</ul>
</section>
{% endif %}
</article>
{% endblock %}

View File

@ -1,13 +0,0 @@
{% extends "base.html" %}
{% block content %}
<article class="panel">
<h1>Delete {{subject}} Subject</h1>
<form method="post" action="{% url 'subject-delete' subject.pk %}">
{% csrf_token %}
<p>
<input class="action-button action-delete" type="submit" value="Confirm Delete {{subject}}"> or <a href="{% url 'subject-list' %}">cancel</a>
</p>
</form>
</article>
{% endblock %}

View File

@ -1,16 +0,0 @@
{% extends "base.html" %}
{% block content %}
<article class="panel">
<h1>Create Subject</h1>
<section>
<form action="{% url 'subject-create' %}" method="POST">
{% csrf_token %}
{{form.as_p}}
<p>
<input class="action-button" type="submit" value="Create Subject"> or <a href="{% url 'subject-list' %}">cancel</a>
</p>
</form>
</section>
</article>
{% endblock %}

View File

@ -1,41 +0,0 @@
{% extends "base.html" %}
{% block content %}
<article class="panel">
<div class="generic__header">
<h1>{{subject.name}}</h1>
<a class="action-button" href="{% url 'subject-update' subject.pk %}">Update subject details</a>
</div>
{% if subject.description %}
<span>{{subject.description}}</span>
{% endif %}
<section>
<h3>Syllabus</h3>
<p>
<a href="{% url 'component-create' subject.pk %}" class="action-button">+ New component</a>
</p>
<table>
<thead>
<tr>
<td>Due Date</td>
<td>Description</td>
<td>Category</td>
<td>Grade Total</td>
<td>Avg Score</td>
</tr>
</thead>
<tbody>
{% for component in subject.component_set.all %}
<tr>
<td>{{component.due_date}}</td>
<td><a href="{% url 'component-detail' subject.pk component.pk %}">{{component.name}}</a></td>
<td>{{component.get_category_display}}</td>
<td>{{component.grade_total}}</td>
<td>{{component.grade_avg_pre|floatformat:2}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</section>
</article>
{% endblock %}

View File

@ -1,19 +0,0 @@
{% extends "base.html" %}
{% block content %}
<article class="panel">
<div class="generic__header">
<h1>Update Subject</h1>
<a class="action-button action-delete" href="{% url 'subject-delete' subject.pk %}">Delete subject</a>
</div>
<section>
<form action="{% url 'subject-update' subject.pk %}" method="POST">
{% csrf_token %}
{{form.as_p}}
<p>
<input class="action-button" type="submit" value="Save changes"> or <a href="{% url 'subject-detail' subject.pk %}">cancel</a>
</p>
</form>
</section>
</article>
{% endblock %}

View File

@ -1,35 +0,0 @@
{% extends "base.html" %}
{% block content %}
<article class="panel">
<header>
<h1>Curricula</h1>
</header>
<section>
<h3>Subjects</h3>
<p>
<a href="{% url 'subject-create' %}" class="action-button">+ New subject</a>
</p>
{% for subject in subject_list %}
<div class="subject__item">
<h4><a href="{% url 'subject-detail' subject.pk %}">{{subject.name}}</a></h4>
{% if subject.description %}
<p>{{subject.description}}</p>
{% endif %}
</div>
{% endfor %}
</section>
<section>
<h4>Tags</h4>
<p><a href="{% url 'tag-create' %}" class="action-button">+ New tag</a></p>
<p>
{% for tag in tag_list %}
<span class="tag">
<a class="tag__item" href="{% url 'tag-detail' tag.pk %}">{{tag.name}}</a>
<span class="tag__count">{{tag.component__count}}</span>
</span>
{% endfor %}
</p>
</section>
</article>
{% endblock %}

View File

@ -1,13 +0,0 @@
{% extends "base.html" %}
{% block content %}
<article class="panel">
<h1>Delete {{tag}}</h1>
<form method="post" action="{% url 'tag-delete' tag.pk %}">
{% csrf_token %}
<p>
<input class="action-button action-delete" type="submit" value="Confirm Delete {{tag}}"> or <a href="{% url 'tag-detail' tag.pk %}">cancel</a>
</p>
</form>
</article>
{% endblock %}

View File

@ -1,16 +0,0 @@
{% extends "base.html" %}
{% block content %}
<article class="panel">
<h1>Create Tag</h1>
<section>
<form action="{% url 'tag-create' %}" method="POST">
{% csrf_token %}
{{form.as_p}}
<p>
<input class="action-button" type="submit" value="Create Tag"> or <a href="{% url 'subject-list' %}">cancel</a>
</p>
</form>
</section>
</article>
{% endblock %}

View File

@ -1,35 +0,0 @@
{% extends "base.html" %}
{% block content %}
<article class="panel">
<div class="generic__header">
<h1>{{tag.name}}</h1>
<a class="action-button" href="{% url 'tag-update' tag.pk %}">Update tag details</a>
</div>
<section>
<h3>Components with this tag</h3>
<table>
<thead>
<tr>
<td>Due Date</td>
<td>Description</td>
<td>Category</td>
<td>Grade Total</td>
<td>Avg Score</td>
</tr>
</thead>
<tbody>
{% for component in tag.component_set.all %}
<tr>
<td>{{component.due_date}}</td>
<td><strong>{{component.subject}}</strong>: <a href="{% url 'component-detail' tag.pk component.pk %}">{{component.name}}</a></td>
<td>{{component.get_category_display}}</td>
<td>{{component.grade_total}}</td>
<td>{{component.grade_avg_pre|floatformat:2}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</section>
</article>
{% endblock %}

View File

@ -1,16 +0,0 @@
{% extends "base.html" %}
{% block content %}
<article class="panel">
<h1>Update Tag</h1>
<section>
<form action="{% url 'tag-update' tag.pk %}" method="POST">
{% csrf_token %}
{{form.as_p}}
<p>
<input class="action-button" type="submit" value="Save changes"> or <a href="{% url 'tag-detail' tag.pk %}">cancel</a>
</p>
</form>
</section>
</article>
{% endblock %}

View File

@ -1,20 +0,0 @@
{% extends "base.html" %}
{% block content %}
<article class="panel">
<h1>Tags</h1>
<p>
<a href="{% url 'tag-create' %}" class="action-button">+ New tag</a>
</p>
<section>
<ul>
{% for tag in tag_list %}
<li>
<a href="{% url 'tag-detail' tag.pk %}">{{tag.name}}</a>
<span class="tag__count">{{tag.component__count}}</span>
</li>
{% endfor %}
</ul>
</section>
</article>
{% endblock %}

View File

@ -1,14 +0,0 @@
from django import template
register = template.Library()
@register.filter(name='grade_as_percentage')
def grade_as_percentage(numerator, denominator):
return round(numerator / denominator * 100, 2)
@register.filter(name='keyvalue')
def keyvalue(dict, key):
try:
return dict[key-1]
except KeyError:
return ''

View File

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

View File

@ -1,40 +0,0 @@
from django.urls import path, include
from . import views
urlpatterns = [
path('search/', views.SearchResultsView.as_view(), name='search-results'),
path('subjects/', views.SubjectListView.as_view(), name='subject-list'),
path('subjects/new/', views.SubjectCreateView.as_view(), name='subject-create'),
path('subjects/<int:pk>/', include([
path('', views.SubjectDetailView.as_view(), name='subject-detail'),
path('update/', views.SubjectUpdateView.as_view(), name='subject-update'),
path('delete/', views.SubjectDeleteView.as_view(), name='subject-delete'),
path('components/', views.ComponentListView.as_view(), name='component-list'),
path('components/new/', views.ComponentCreateView.as_view(), name='component-create'),
path('components/<int:comp_pk>/', include([
path('', views.ComponentDetailView.as_view(), name='component-detail'),
path('update/', views.ComponentUpdateView.as_view(), name='component-update'),
path('manager/', views.ComponentManagerView.as_view(), name='component-manager'),
path('delete/', views.ComponentDeleteView.as_view(), name='component-delete'),
])),
])),
path('scores/', views.ScoreListView.as_view(), name='score-list'),
path('scores/new/', views.ScoreCreateView.as_view(), name='score-create'),
path('scores/<int:pk>/', include([
path('', views.ScoreDetailView.as_view(), name='score-detail'),
path('update/', views.ScoreUpdateView.as_view(), name='score-update'),
path('delete/', views.ScoreDeleteView.as_view(), name='score-delete'),
])),
path('tags/', views.TagListView.as_view(), name='tag-list'),
path('tags/new/', views.TagCreateView.as_view(), name='tag-create'),
path('tags/<int:pk>/', include([
path('', views.TagDetailView.as_view(), name='tag-detail'),
path('update/', views.TagUpdateView.as_view(), name='tag-update'),
path('delete/', views.TagDeleteView.as_view(), name='tag-delete'),
])),
]

View File

@ -1,297 +0,0 @@
from django.shortcuts import render
from django.urls import reverse_lazy, reverse
from django.views.generic.base import TemplateView
from django.views.generic.edit import FormView, CreateView, UpdateView, DeleteView
from django.views.generic.detail import DetailView
from django.views.generic.list import ListView
from django.contrib.auth.mixins import LoginRequiredMixin
from django.forms.models import inlineformset_factory
from django import forms
from django.db.models import ( Exists, OuterRef,
Prefetch, Subquery, Count, Sum, Avg, F, Q, Value)
from django.db.models.functions import Length, Upper
from students.models import Student, Thread, Message
from .models import (
Tag,
Subject,
Component,
Score,
)
from .forms import ComponentForm, ComponentUpdateForm
# UPLOAD CSV
# import pandas as pd
# csv_data = pd.read_csv('file.csv', sep=';')
# products = [
# Product(
# name = csv_data.ix[row]['Name'],
# description = csv_data.ix[row]['Description'],
# price = csv_data.ix[row]['price'],
# )
# for row in csv_data['ID']
# ]
# Product.objects.bulk_create(products)
# if form.is_valid():
# csv_file = form.cleaned_data['csv_file']
class SearchResultsView(ListView):
model = Component
template_name = 'gradebook/search_results.html'
def get_queryset(self):
query = self.request.GET.get('q')
object_list = Component.objects.filter(
Q(name__icontains=query) | Q(tags__name__icontains=query)
).prefetch_related(
Prefetch(
'score_set',
queryset=Score.objects.select_related('student')
),
'tags'
).annotate(
grade_avg_pre=Avg('score__value')
).order_by('subject')
return object_list
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
query = self.request.GET.get('q')
context['student_list'] = Student.objects.filter(
Q(first_name__icontains=query) | Q(last_name__icontains=query) | Q(student_id__icontains=query)
)
context['message_list'] = Message.objects.filter(
Q(content__icontains=query)
)
context['query'] = query
return context
# SUBJECTS
class SubjectListView(LoginRequiredMixin, ListView):
model = Subject
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['tag_list'] = Tag.objects.annotate(Count('component'))
return context
class SubjectCreateView(LoginRequiredMixin, CreateView):
model = Subject
template_name_suffix = '_create_form'
fields = ('__all__')
class SubjectDetailView(LoginRequiredMixin, DetailView):
model = Subject
def get_object(self):
queryset = Subject.objects.filter(
pk=self.kwargs.get(self.pk_url_kwarg)
).prefetch_related(
Prefetch('component_set', queryset=Component.objects.prefetch_related(
'score_set'
).annotate(
grade_avg_pre=Avg('score__value')
))
)
obj = queryset.get()
return obj
class SubjectUpdateView(LoginRequiredMixin, UpdateView):
model = Subject
fields = ('__all__')
def get_success_url(self):
pk = self.kwargs["pk"]
return reverse('subject-detail', kwargs={'pk': pk})
class SubjectDeleteView(LoginRequiredMixin, DeleteView):
model = Subject
success_url = reverse_lazy('subject-list')
# COMPONENTS
class ComponentListView(LoginRequiredMixin, ListView):
model = Component
pk_url_kwarg = 'comp_pk'
class ComponentCreateView(LoginRequiredMixin, CreateView):
model = Component
form_class = ComponentForm
template_name_suffix = '_create_form'
pk_url_kwarg = 'comp_pk'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['subject'] = Subject.objects.get(pk=self.kwargs['pk'])
return context
def form_valid(self, form):
form.instance.subject = Subject.objects.get(pk=self.kwargs['pk'])
return super().form_valid(form)
def get_success_url(self):
return reverse('component-detail', kwargs={'pk': self.kwargs['pk'], 'comp_pk': self.object.pk})
class ComponentDetailView(LoginRequiredMixin, DetailView):
model = Component
pk_url_kwarg = 'comp_pk'
def get_object(self):
queryset = Component.objects.filter(
pk=self.kwargs.get(self.pk_url_kwarg)
).prefetch_related(
Prefetch(
'score_set',
queryset=Score.objects.select_related('student')
),
'tags'
).annotate(
grade_avg_pre=Avg('score__value')
)
obj = queryset.get()
return obj
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
cscores = Score.objects.filter(
component=self.object,
student=OuterRef('pk')
)
context['scoreless'] = Student.objects.exclude(
score__in=cscores
)
return context
class ComponentUpdateView(LoginRequiredMixin, UpdateView):
model = Component
form_class = ComponentUpdateForm
pk_url_kwarg = 'comp_pk'
def get_success_url(self):
return reverse('subject-detail', kwargs={'pk': self.object.subject.pk})
class ComponentManagerView(LoginRequiredMixin, UpdateView):
model = Component
fields = ('finished_grading',)
pk_url_kwarg = 'comp_pk'
template_name_suffix = '_manager'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
cscores = Score.objects.filter(
component=self.object,
student=OuterRef('pk')
)
context['student_list'] = Student.objects.annotate(
cscore=Subquery(cscores.values('value')),
cscore_pk=Subquery(cscores.values('pk'))
)
return context
def get_success_url(self):
return reverse('component-detail', kwargs={'pk': self.object.subject.pk, 'comp_pk': self.object.pk})
def form_valid(self, form):
form.save()
for key, value in self.request.POST.items():
if 'student' in key and value:
s_pk = key.split('_')[1]
obj, created = Score.objects.update_or_create(
component=self.object,
student=Student.objects.get(pk=s_pk),
defaults={'value': value}
)
return super().form_valid(form)
class ComponentDeleteView(LoginRequiredMixin, DeleteView):
model = Component
pk_url_kwarg = 'comp_pk'
def get_success_url(self):
return reverse('subject-detail', kwargs={'pk': self.kwargs['pk']})
# SCORES
class ScoreListView(LoginRequiredMixin, ListView):
model = Score
class ScoreCreateView(LoginRequiredMixin, CreateView):
model = Score
template_name_suffix = '_create_form'
fields = ('__all__')
def get_success_url(self):
return reverse('component-detail', kwargs={'pk': self.object.component.pk})
class ScoreDetailView(LoginRequiredMixin, DetailView):
model = Score
class ScoreUpdateView(LoginRequiredMixin, UpdateView):
model = Score
fields = ['value']
def get_success_url(self):
return reverse('component-detail', kwargs={'pk': self.object.component.subject.pk, 'comp_pk': self.object.component.pk})
class ScoreDeleteView(LoginRequiredMixin, DeleteView):
model = Score
def get_success_url(self):
return reverse('component-detail', kwargs={'pk': self.object.component.subject.pk, 'comp_pk': self.object.component.pk})
# TAGS
class TagListView(LoginRequiredMixin, ListView):
model = Tag
def get_queryset(self):
object_list = Tag.objects.annotate(Count('component'))
return object_list
class TagCreateView(LoginRequiredMixin, CreateView):
model = Tag
template_name_suffix = '_create_form'
fields = ('__all__')
class TagDetailView(LoginRequiredMixin, DetailView):
model = Tag
def get_object(self):
queryset = Tag.objects.filter(
pk=self.kwargs.get(self.pk_url_kwarg)
).prefetch_related(
Prefetch('component_set', queryset=Component.objects.prefetch_related(
'score_set'
).annotate(
grade_avg_pre=Avg('score__value')
))
)
obj = queryset.get()
return obj
class TagUpdateView(LoginRequiredMixin, UpdateView):
model = Tag
fields = ('__all__')
def get_success_url(self):
pk = self.kwargs["pk"]
return reverse('tag-detail', kwargs={'pk': pk})
class TagDeleteView(LoginRequiredMixin, DeleteView):
model = Tag
success_url = reverse_lazy('tag-list')

View File

@ -34,9 +34,6 @@ INSTALLED_APPS = [
# Local
'accounts.apps.AccountsConfig',
'core.apps.CoreConfig',
'students.apps.StudentsConfig',
'gradebook.apps.GradebookConfig',
'attendance.apps.AttendanceConfig',
]
# Middlewares
@ -147,7 +144,7 @@ STATICFILES_FINDERS = (
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
AUTH_USER_MODEL = 'accounts.User'
LOGIN_REDIRECT_URL = reverse_lazy('core:schoolyear-list')
LOGIN_REDIRECT_URL = reverse_lazy('core:today')
# Decimal settings
DEFAULT_DECIMAL_PLACES = 2

View File

@ -7,9 +7,6 @@ urlpatterns = [
path('', include(('core.urls', 'core'), namespace='core')),
path('accounts/', include('accounts.urls'), name='accounts'),
path('accounts/', include('django.contrib.auth.urls')),
path('students/', include('students.urls'), name='students'),
path('gradebook/', include('gradebook.urls'), name='gradebook'),
path('attendance/', include('attendance.urls'), name='attendance'),
path('admin/', admin.site.urls),
path('__debug__/', include('debug_toolbar.urls')),
]

View File

@ -354,9 +354,12 @@ form input[type=checkbox] {
font-weight: bold;
}
.site__logo {
margin-bottom: 0;
}
.nav__account {
justify-self: end;
/*align-items: center;*/
}
/* ==========================================================================

View File

@ -1,8 +0,0 @@
from django.contrib import admin
from .models import (
Student,
Parent,
)
admin.site.register(Student)
admin.site.register(Parent)

View File

@ -1,6 +0,0 @@
from django.apps import AppConfig
class StudentsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'students'

View File

@ -1,77 +0,0 @@
from django import forms
from django.utils import timezone
from .models import Student, Parent, Thread, Message
class StudentForm(forms.ModelForm):
class Meta:
model = Student
fields = (
'student_id',
'first_name',
'last_name',
'address',
'dob',
'glasses',
'allergies',
'sit',
'iep_behavioral',
'iep_math',
'iep_ela',
)
labels = {
'student_id': 'Student ID',
'dob': 'DOB',
'sit': 'SIT',
'iep_behavioral': 'IEP Behavioral',
'iep_math': 'IEP Math',
'iep_ela': 'IEP ELA',
}
class ParentForm(forms.ModelForm):
class Meta:
model = Parent
fields = (
'students',
'first_name',
'last_name',
'relation',
'phone_number',
'email_address',
'notes',
)
class ThreadForm(forms.ModelForm):
class Meta:
model = Thread
fields = (
'parent',
'student',
'subject',
)
widgets = {
'subject': forms.TextInput(attrs = {
'placeholder': 'Reason for contact…'
}),
}
class MessageForm(forms.ModelForm):
class Meta:
model = Message
fields = (
'method_of_contact',
'date',
'time',
'content',
)
widgets = {
'content': forms.Textarea(attrs = {
'placeholder': 'Enter your message here'
}),
'date': forms.DateInput(attrs = {
'type': 'date',
}),
'time': forms.DateInput(attrs = {
'type': 'time',
}),
}

View File

@ -1,73 +0,0 @@
# Generated by Django 3.2.7 on 2021-09-01 15:26
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Parent',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('first_name', models.CharField(max_length=50)),
('last_name', models.CharField(max_length=50)),
('relation', models.CharField(choices=[('MO', 'Mother'), ('FA', 'Father'), ('GM', 'Grandmother'), ('GP', 'Grandfather'), ('SM', 'Stepmother'), ('SF', 'Stepfather'), ('OT', 'Other')], default='MO', max_length=2)),
('phone_number', models.IntegerField(blank=True, null=True)),
('email_address', models.EmailField(blank=True, max_length=254, null=True)),
('notes', models.TextField(blank=True)),
],
),
migrations.CreateModel(
name='Student',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('student_id', models.IntegerField()),
('first_name', models.CharField(max_length=50)),
('last_name', models.CharField(max_length=50)),
('address', models.TextField(blank=True)),
('dob', models.DateField()),
('sit', models.CharField(blank=True, choices=[('T2', 'Tier 2'), ('T3', 'Tier 3')], max_length=2)),
('iep_behavioral', models.BooleanField(default=False)),
('iep_math', models.BooleanField(default=False)),
('iep_ela', models.BooleanField(default=False)),
],
options={
'ordering': ['first_name'],
},
),
migrations.CreateModel(
name='Thread',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('subject', models.CharField(max_length=250)),
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='students.parent')),
('student', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='students.student')),
],
),
migrations.AddField(
model_name='parent',
name='students',
field=models.ManyToManyField(blank=True, to='students.Student'),
),
migrations.CreateModel(
name='Message',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('date', models.DateField(blank=True, null=True)),
('time', models.TimeField(blank=True, null=True)),
('content', models.TextField()),
('method_of_contact', models.CharField(blank=True, choices=[('PH', 'Phone'), ('EM', 'Email'), ('NO', 'Note'), ('IP', 'In person')], max_length=2)),
('thread', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='students.thread')),
],
options={
'ordering': ('-date',),
},
),
]

View File

@ -1,23 +0,0 @@
# Generated by Django 3.2.7 on 2021-09-12 00:03
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('students', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='student',
name='allergies',
field=models.CharField(blank=True, max_length=50),
),
migrations.AddField(
model_name='student',
name='glasses',
field=models.BooleanField(default=False),
),
]

View File

@ -1,17 +0,0 @@
# Generated by Django 3.2.7 on 2021-09-12 00:07
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('students', '0002_auto_20210912_0003'),
]
operations = [
migrations.AlterModelOptions(
name='student',
options={'ordering': ['student_id']},
),
]

View File

@ -1,23 +0,0 @@
# Generated by Django 3.2.7 on 2021-09-12 00:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('students', '0003_alter_student_options'),
]
operations = [
migrations.AlterField(
model_name='parent',
name='first_name',
field=models.CharField(blank=True, max_length=50),
),
migrations.AlterField(
model_name='parent',
name='last_name',
field=models.CharField(blank=True, max_length=50),
),
]

View File

@ -1,131 +0,0 @@
from datetime import datetime, date
from django.db import models
from django.urls import reverse
from django.db.models import Count, Sum, F, Value
from django.db.models.functions import Length, Upper
class Student(models.Model):
class Meta:
ordering = ['student_id']
student_id = models.IntegerField()
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
address = models.TextField(blank=True)
dob = models.DateField()
T2 = 'T2'
T3 = 'T3'
SIT_CHOICES = [
(T2, 'Tier 2'),
(T3, 'Tier 3'),
]
sit = models.CharField(
max_length=2,
choices=SIT_CHOICES,
blank=True,
)
glasses = models.BooleanField(default=False)
allergies = models.CharField(max_length=50, blank=True)
iep_behavioral = models.BooleanField(default=False)
iep_math = models.BooleanField(default=False)
iep_ela = models.BooleanField(default=False)
@property
def full_name(self):
return f"{self.first_name} {self.last_name}"
@property
def age(self):
today = date.today()
return today.year - self.dob.year - ((today.month, today.day) < (self.dob.month, self.dob.day))
def __str__(self):
return f"{self.first_name} {self.last_name}"
def get_absolute_url(self):
return reverse('student-detail', kwargs={'pk': self.pk})
class Parent(models.Model):
RELATION_CHOICES = [
('MO', 'Mother'),
('FA', 'Father'),
('GM', 'Grandmother'),
('GP', 'Grandfather'),
('SM', 'Stepmother'),
('SF', 'Stepfather'),
('OT', 'Other'),
]
students = models.ManyToManyField(Student, blank=True)
first_name = models.CharField(max_length=50, blank=True)
last_name = models.CharField(max_length=50, blank=True)
relation = models.CharField(
max_length=2,
choices=RELATION_CHOICES,
default='MO',
)
phone_number = models.IntegerField(blank=True, null=True)
email_address = models.EmailField(blank=True, null=True)
notes = models.TextField(blank=True)
@property
def full_name(self):
return f"{self.first_name} {self.last_name}"
def __str__(self):
return f"{self.first_name} {self.last_name}"
def get_absolute_url(self):
return reverse('parent-detail', kwargs={'pk': self.pk})
class Thread(models.Model):
parent = models.ForeignKey(Parent, on_delete=models.CASCADE, blank=True, null=True)
student = models.ForeignKey(Student, on_delete=models.CASCADE, blank=True, null=True)
subject = models.CharField(max_length=250)
def __str__(self):
return f"{self.parent} | {self.subject}"
def get_absolute_url(self):
return reverse('thread-detail', kwargs={'pk': self.pk})
class Message(models.Model):
class Meta:
ordering = ('-date',)
thread = models.ForeignKey(Thread, on_delete=models.CASCADE)
date = models.DateField(blank=True, null=True)
time = models.TimeField(blank=True, null=True)
content = models.TextField()
PHONE = 'PH'
EMAIL = 'EM'
NOTE = 'NO'
IN_PERSON = 'IP'
MOC_CHOICES = [
(PHONE, 'Phone'),
(EMAIL, 'Email'),
(NOTE, 'Note'),
(IN_PERSON, 'In person'),
]
method_of_contact = models.CharField(
max_length=2,
choices=MOC_CHOICES,
blank=True,
)
def __str__(self):
return f"{self.date} {self.thread.subject}"
def get_absolute_url(self):
return reverse('message-detail', kwargs={'pk': self.thread.pk, 'message_pk': self.pk})

View File

@ -1,13 +0,0 @@
{% extends "base.html" %}
{% block content %}
<article class="panel">
<h1>Delete {{message}}</h1>
<form method="post" action="{% url 'message-delete' message.thread.pk message.pk %}">
{% csrf_token %}
<p>
<input class="action-button action-delete" type="submit" value="Confirm Delete {{message}}"> or <a href="{% url 'thread-detail' message.thread.pk %}">cancel</a>
</p>
</form>
</article>
{% endblock %}

View File

@ -1,16 +0,0 @@
{% extends "base.html" %}
{% block content %}
<article class="panel">
<h1>{{thread.subject}}</h1>
<section>
<form action="{% url 'message-create' thread.pk %}" method="POST">
{% csrf_token %}
{{form.as_p}}
<p>
<input class="action-button" type="submit" value="Create Message"> or <a href="{% url 'thread-detail' thread.pk %}">cancel</a>
</p>
</form>
</section>
</article>
{% endblock %}

View File

@ -1,8 +0,0 @@
<div class="message">
<strong class="message__date">
{{message.date|date:"D, M j"}}<br>
{{message.time|time:"TIME_FORMAT"}} via {{message.get_method_of_contact_display}}
</strong>
<span><a href="{% url 'message-update' thread.pk message.pk %}">Edit</a></span>
<p>{{message.content|linebreaksbr}}</p>
</div>

View File

@ -1,19 +0,0 @@
{% extends "base.html" %}
{% block content %}
<article class="panel">
<div class="generic__header">
<h1>Update Message</h1>
<a class="action-button action-delete" href="{% url 'message-delete' message.thread.pk message.pk %}">Delete {{message}}</a>
</div>
<section>
<form action="{% url 'message-update' message.thread.pk message.pk %}" method="POST">
{% csrf_token %}
{{form.as_p}}
<p>
<input class="action-button" type="submit" value="Save changes"> or <a href="{% url 'thread-detail' message.thread.pk %}">cancel</a>
</p>
</form>
</section>
</article>
{% endblock %}

View File

@ -1,19 +0,0 @@
{% extends "base.html" %}
{% block content %}
<article class="panel">
<h1>Parents</h1>
<section>
<p>
<a class="action-button" href="{% url 'message-create' %}">+ New message</a>
</p>
<ul>
{% for message in message_list %}
<li class="message">
<a class="message__li" href="{% url 'message-detail' message.pk %}">{{message}}</a>
</li>
{% endfor %}
</ul>
</section>
</article>
{% endblock %}

View File

@ -1,13 +0,0 @@
{% extends "base.html" %}
{% block content %}
<article class="panel">
<h1>Delete {{parent}}</h1>
<form method="post" action="{% url 'parent-delete' parent.pk %}">
{% csrf_token %}
<p>
<input class="action-button action-delete" type="submit" value="Confirm Delete {{parent}}"> or <a href="{% url 'parent-detail' parent.pk %}">cancel</a>
</p>
</form>
</article>
{% endblock %}

Some files were not shown because too many files have changed in this diff Show More