import datetime as dt from django.conf import settings from django.utils import timezone from django.db import models from django.shortcuts import render, reverse, redirect, get_object_or_404 from django.urls import reverse, reverse_lazy from django.views.generic.base import TemplateView from django.views.generic.detail import DetailView from django.views.generic.list import ListView from django.views.generic.dates import YearArchiveView from django.views.generic.edit import ( FormView, CreateView, UpdateView, DeleteView, FormMixin ) from django.contrib import messages from django.contrib.auth.mixins import ( LoginRequiredMixin, PermissionRequiredMixin ) from django.contrib.messages.views import SuccessMessageMixin from django.contrib.contenttypes.models import ContentType from django.db.models import ( Exists, OuterRef, Prefetch, Subquery, Count, Sum, Avg, F, Q, Value ) from .models import ( SchoolYear, StudentTag, Student, Subject, Tag, Component, Score, SchoolDay, AttendanceEntry, ) from .forms import ( SchoolYearCreateForm, ComponentCreateForm, SchoolDayForm, AttendanceEntryForm ) class TodayView(LoginRequiredMixin, TemplateView): template_name = 'core/today.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'] = SchoolDay.objects.filter( date=today ).prefetch_related('attendanceentry_set', 'attendanceentry_set__student') context['ungraded_components'] = Component.objects.filter( due_date__lte=today, finished_grading=False ).select_related('subject') return context class SchoolYearListView(LoginRequiredMixin, ListView): model = SchoolYear paginate_by = 10 def get_queryset(self): queryset = SchoolYear.objects.annotate( models.Count('student', distinct=True), models.Count('subject', distinct=True) ).order_by('-year') return queryset class SchoolYearCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView): model = SchoolYear success_message = 'SchoolYear created.' form_class = SchoolYearCreateForm template_name_suffix = '_create_form' class SchoolYearDetailView(LoginRequiredMixin, DetailView): model = SchoolYear slug_url_kwarg = 'year' slug_field = 'year' class SchoolYearUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView): model = SchoolYear slug_url_kwarg = 'year' slug_field = 'year' success_message = 'SchoolYear saved.' fields = '__all__' class StudentListView(LoginRequiredMixin, ListView): model = Student paginate_by = 50 def get_queryset(self): queryset = Student.objects.filter( school_year__year=self.kwargs['year'] ) return queryset def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['school_year'] = get_object_or_404( SchoolYear, year=self.kwargs['year'] ) return context 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', ] def form_valid(self, form): form.instance.school_year = get_object_or_404( SchoolYear, year=self.kwargs['year'] ) return super().form_valid(form) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['school_year'] = get_object_or_404(SchoolYear, year=self.kwargs['year']) return context class StudentDetailView(LoginRequiredMixin, DetailView): model = Student 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['score_list'] = Score.objects.select_related( 'student' ).prefetch_related( 'component' ).select_related( 'component__subject' ).filter( student=self.object ).order_by( 'component__subject', '-component__due_date' ) context['subject_list'] = Subject.objects.filter( component__score__student=self.object ).annotate( grade=Sum(F('component__score__value')), grade_total=Sum('component__grade_total') ) context['entry_list'] = AttendanceEntry.objects.select_related( 'school_day' ).filter( student=self.object ).order_by('status', '-school_day__date').select_related('student') return context class StudentUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView): model = Student success_message = 'Student saved.' fields = '__all__' class StudentDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteView): model = Student success_message = 'Student deleted.' success_url = reverse_lazy('student-list') class StudentTagListView(LoginRequiredMixin, ListView): model = StudentTag def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['school_year'] = get_object_or_404( SchoolYear, year=self.kwargs['year'] ) return context class StudentTagCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView): model = StudentTag success_message = 'Tag created.' template_name_suffix = '_create_form' fields = '__all__' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['school_year'] = get_object_or_404( SchoolYear, year=self.kwargs['year'] ) return context def get_success_url(self): return reverse('core:stag-list', kwargs={ 'year': self.kwargs['year'] }) class StudentTagDetailView(LoginRequiredMixin, DetailView): model = StudentTag pk_url_kwarg = 'stag_pk' context_object_name = 'tag' def get_object(self): queryset = StudentTag.objects.filter( pk=self.kwargs.get(self.pk_url_kwarg) ).prefetch_related( Prefetch( 'student_set', queryset=Student.objects.filter( school_year__year=self.kwargs['year'] ) ) ) obj = queryset.get() return obj def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['school_year'] = get_object_or_404( SchoolYear, year=self.kwargs['year'] ) return context class StudentTagUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView): model = StudentTag pk_url_kwarg = 'stag_pk' context_object_name = 'tag' success_message = 'Tag saved.' fields = '__all__' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['school_year'] = get_object_or_404( SchoolYear, year=self.kwargs['year'] ) return context def get_success_url(self): return reverse('core:stag-list', kwargs={ 'year': self.kwargs['year'] }) class StudentTagDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteView): model = StudentTag pk_url_kwarg = 'stag_pk' context_object_name = 'tag' success_message = 'Tag deleted.' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['school_year'] = get_object_or_404( SchoolYear, year=self.kwargs['year'] ) return context def get_success_url(self): return reverse('core:stag-list', kwargs={ 'year': self.kwargs['year'] }) class SubjectListView(LoginRequiredMixin, ListView): model = Subject paginate_by = 50 def get_queryset(self): queryset = Subject.objects.filter( school_year__year=self.kwargs['year'] ).annotate( models.Count('component') ).order_by('name') return queryset def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['school_year'] = get_object_or_404( SchoolYear, year=self.kwargs['year'] ) return context class SubjectCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView): model = Subject success_message = 'Subject created.' template_name_suffix = '_create_form' fields = [ 'name', 'description' ] def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['school_year'] = get_object_or_404( SchoolYear, year=self.kwargs['year'] ) return context def form_valid(self, form): form.instance.school_year = get_object_or_404( SchoolYear, year=self.kwargs['year'] ) return super().form_valid(form) 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, SuccessMessageMixin, UpdateView): model = Subject success_message = 'Subject saved.' fields = '__all__' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['school_year'] = get_object_or_404( SchoolYear, year=self.kwargs['year'] ) return context class SubjectDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteView): model = Subject success_message = 'Subject deleted.' success_url = reverse_lazy('subject-list') def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['school_year'] = get_object_or_404( SchoolYear, year=self.kwargs['year'] ) return context class ComponentListView(LoginRequiredMixin, ListView): model = Component class ComponentCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView): model = Component success_message = 'Component created.' template_name_suffix = '_create_form' form_class = ComponentCreateForm 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['subject'] = get_object_or_404( Subject, pk=self.kwargs['pk'] ) return context def form_valid(self, form): form.instance.school_year = get_object_or_404( SchoolYear, year=self.kwargs['year'] ) form.instance.subject = get_object_or_404( Subject, pk=self.kwargs['pk'] ) return super().form_valid(form) class ComponentDetailView(LoginRequiredMixin, DetailView): model = Component pk_url_kwarg = 'component_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.filter( school_year__year=self.kwargs['year'] ).exclude( score__in=cscores ) return context class ComponentUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView): model = Component pk_url_kwarg = 'component_pk' success_message = 'Component saved.' fields = '__all__' def get_success_url(self): return reverse('core:component-detail', kwargs={ 'year': self.object.subject.school_year.year, 'pk': self.object.subject.pk, 'component_pk': self.object.pk }) class ComponentDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteView): model = Component pk_url_kwarg = 'component_pk' success_message = 'Component deleted.' success_url = reverse_lazy('core:component-list') class ComponentManagerView(LoginRequiredMixin, UpdateView): model = Component pk_url_kwarg = 'component_pk' template_name_suffix = '_manager' fields = ['finished_grading'] 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.filter( school_year__year=self.kwargs['year'] ).annotate( cscore=Subquery(cscores.values('value')), cscore_pk=Subquery(cscores.values('pk')) ) return context def get_success_url(self): return reverse('core:component-detail', kwargs={ 'year': self.object.subject.school_year.year, 'pk': self.object.subject.pk, 'component_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 ScoreListView(LoginRequiredMixin, ListView): model = Score class ScoreCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView): model = Score success_message = 'Score created.' template_name_suffix = '_create_form' fields = '__all__' class ScoreDetailView(LoginRequiredMixin, DetailView): model = Score class ScoreUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView): model = Score success_message = 'Score saved.' fields = [ 'component', 'value' ] def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['school_year'] = get_object_or_404( SchoolYear, year=self.kwargs['year'] ) return context class ScoreDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteView): model = Score success_message = 'Score deleted.' def get_success_url(self): return reverse('core:schoolyear-detail', kwargs={ 'year': self.kwargs['year'] }) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['school_year'] = get_object_or_404( SchoolYear, year=self.kwargs['year'] ) return context class SchoolDayListView(LoginRequiredMixin, ListView): model = SchoolDay paginate_by = 7 def get_queryset(self): queryset = SchoolDay.objects.filter( school_year__year=self.kwargs['year'] ) return queryset def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['school_year'] = get_object_or_404( SchoolYear, year=self.kwargs['year'] ) return context class SchoolDayCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView): model = SchoolDay success_message = 'SchoolDay created.' template_name_suffix = '_create_form' form_class = SchoolDayForm 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.filter( school_year__year=self.kwargs['year'] ) context['school_year'] = get_object_or_404( SchoolYear, year=self.kwargs['year'] ) return context def form_valid(self, form): form.instance.school_year = get_object_or_404( SchoolYear, year=self.kwargs['year'] ) form.save() for key, value in self.request.POST.items(): if 'student' in key: s = key.split('_')[1] AttendanceEntry.objects.create( school_day=form.instance, student=Student.objects.get(pk=s), status=value, ) return super().form_valid(form) class SchoolDayDetailView(LoginRequiredMixin, DetailView): model = SchoolDay class SchoolDayUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView): model = SchoolDay success_message = 'SchoolDay saved.' form_class = SchoolDayForm def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['school_year'] = get_object_or_404( SchoolYear, year=self.kwargs['year'] ) 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] AttendanceEntry.objects.filter( school_day=self.object, student=Student.objects.get(pk=s) ).update(status=value) return super().form_valid(form) class SchoolDayDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteView): model = SchoolDay success_message = 'SchoolDay deleted.' def get_success_url(self): return reverse('core:schoolday-list', kwargs={ 'year': self.kwargs['year'] }) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['school_year'] = get_object_or_404( SchoolYear, year=self.kwargs['year'] ) return context class AttendanceEntryListView(LoginRequiredMixin, ListView): model = AttendanceEntry class AttendanceEntryCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView): model = AttendanceEntry success_message = 'Entry created.' template_name_suffix = '_create_form' fields = '__all__' class AttendanceEntryDetailView(LoginRequiredMixin, DetailView): model = AttendanceEntry pk_url_kwarg = 'entry_pk' context_object_name = 'entry' class AttendanceEntryUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView): model = AttendanceEntry pk_url_kwarg = 'entry_pk' context_object_name = 'entry' success_message = 'Entry saved.' form_class = AttendanceEntryForm def get_success_url(self): return reverse('core:schoolday-detail', kwargs={ 'year': self.kwargs['year'], 'pk': self.kwargs['pk'] }) class AttendanceEntryDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteView): model = AttendanceEntry pk_url_kwarg = 'entry_pk' context_object_name = 'entry' success_message = 'Entry deleted.' def get_success_url(self): return reverse('core:schoolday-detail', kwargs={ 'year': self.kwargs['year'], 'pk': self.kwargs['pk'] })