diff --git a/src/core/context_processors.py b/src/core/context_processors.py new file mode 100644 index 0000000..fc630b0 --- /dev/null +++ b/src/core/context_processors.py @@ -0,0 +1,8 @@ +from .models import SchoolYear +from django.shortcuts import get_object_or_404 + + +def current_year(request): + return { + 'current_year': SchoolYear.objects.latest('year') + } diff --git a/src/core/forms.py b/src/core/forms.py index 7a137b5..431c045 100644 --- a/src/core/forms.py +++ b/src/core/forms.py @@ -35,3 +35,17 @@ class StudentForm(forms.ModelForm): 'student_id': 'Student ID', 'dob': 'DOB', } + + +class ComponentCreateForm(forms.ModelForm): + class Meta: + model = Component + fields = ( + 'name', + 'category', + 'due_date', + 'grade_total', + ) + widgets = { + 'due_date': forms.DateInput(attrs={'type': 'date'}), + } diff --git a/src/core/models.py b/src/core/models.py index 5a54a2c..c23034e 100644 --- a/src/core/models.py +++ b/src/core/models.py @@ -5,6 +5,9 @@ from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.fields import ( GenericForeignKey, GenericRelation ) +from django.db.models import ( + Exists, OuterRef, Prefetch, Subquery, Count, Sum, Avg, F, Q, Value +) class SchoolYear(models.Model): @@ -89,7 +92,7 @@ class Student(models.Model): def get_absolute_url(self): return reverse('core:student-detail', kwargs={ 'year': self.school_year.year, - 'student_pk': self.pk + 'pk': self.pk }) class Meta: @@ -114,7 +117,10 @@ class Subject(models.Model): return self.name def get_absolute_url(self): - return reverse('subject-detail', kwargs={'pk': self.pk}) + return reverse('core:subject-detail', kwargs={ + 'year': self.school_year.year, + 'pk': self.pk + }) class Meta: ordering = ['name'] @@ -180,7 +186,11 @@ class Component(models.Model): return self.name def get_absolute_url(self): - return reverse('component-detail', kwargs={'pk': self.pk}) + return reverse('core:component-detail', kwargs={ + 'year': self.school_year.year, + 'pk': self.subject.pk, + 'component_pk': self.pk + }) class Meta: ordering = ['due_date'] @@ -203,7 +213,11 @@ class Score(models.Model): return f'{self.student} scored: {self.value} / {self.component.grade_total}' def get_absolute_url(self): - return reverse('score-detail', kwargs={'pk': self.pk}) + return reverse('core:component-detail', kwargs={ + 'year': self.component.subject.school_year.year, + 'pk': self.component.subject.pk, + 'component_pk': self.component.pk + }) class Meta: ordering = ['student'] diff --git a/src/core/templates/core/component_confirm_delete.html b/src/core/templates/core/component_confirm_delete.html new file mode 100644 index 0000000..e654aca --- /dev/null +++ b/src/core/templates/core/component_confirm_delete.html @@ -0,0 +1,13 @@ +{% extends 'base.html' %} + +{% block content %} +

Delete Component

+
+ {% csrf_token %} +

Are you sure you want to delete "{{ component }}"?

+ {{ form.as_p }} +

+ or cancel +

+
+{% endblock %} diff --git a/src/core/templates/core/component_create_form.html b/src/core/templates/core/component_create_form.html new file mode 100644 index 0000000..2d64eef --- /dev/null +++ b/src/core/templates/core/component_create_form.html @@ -0,0 +1,28 @@ +{% extends 'base.html' %} + +{% block head_title %}New component | {% endblock head_title %} + +{% block breadcrumbs %} + +{% endblock breadcrumbs %} + +{% block content %} +
+

+ New Component

+
+ {% csrf_token %} + {{ form.as_p }} +

+ or cancel +

+
+
+{% endblock %} diff --git a/src/core/templates/core/component_detail.html b/src/core/templates/core/component_detail.html new file mode 100644 index 0000000..3ca5d54 --- /dev/null +++ b/src/core/templates/core/component_detail.html @@ -0,0 +1,88 @@ +{% extends "base.html" %} +{% load helpers %} + +{% block head_title %}{{ component.name }} | {% endblock head_title %} + +{% block breadcrumbs %} + +{% endblock breadcrumbs %} + +{% block content %} +
+
+
+

{{component.name}}   -   {{ component.get_category_display }}{% if component.finished_grading %}   —   ✓ Graded{% endif %}

+

Added:
+ Last updated:

+
+ Edit +
+ {% if component.tags.count > 0 %} +
+ + {% for tag in component.tags.all %} + {{tag.name}} + {% endfor %} + +
+ {% endif %} +
+
+
Due Date
+
{{component.due_date}}
+
Category
+
{{component.get_category_display}}
+
Grade Total
+
{{component.grade_total}}
+
+
+
+
+
+

Scores

+ Enter Scores → +
+
+ + + + + + + + + {% for score in component.score_set.all %} + + + + + + {% endfor %} + + + + + +
StudentScore
{{score.student.student_id}} — {{score.student}}{{score.value}} / {{component.grade_total}}Edit score
Avg. Score{{component.grade_avg_pre|floatformat:2}} / {{component.grade_total}}
+
+ {% if scoreless %} +
+ +

Scoreless

+ +
+ {% endif %} +
+{% endblock %} diff --git a/src/core/templates/core/component_form.html b/src/core/templates/core/component_form.html new file mode 100644 index 0000000..ea2259c --- /dev/null +++ b/src/core/templates/core/component_form.html @@ -0,0 +1,12 @@ +{% extends 'base.html' %} + +{% block content %} +

Update Component

+
+ {% csrf_token %} + {{ form.as_p }} +

+ or cancel +

+
+{% endblock %} diff --git a/src/core/templates/core/component_list.html b/src/core/templates/core/component_list.html new file mode 100644 index 0000000..003f836 --- /dev/null +++ b/src/core/templates/core/component_list.html @@ -0,0 +1,16 @@ +{% extends 'base.html' %} + +{% block content %} +

Components

+ + + {% for component in component_list %} + +
{{ component }}
+ + {% empty %} + No components yet. + {% endfor %} + +
+{% endblock %} diff --git a/src/core/templates/core/component_manager.html b/src/core/templates/core/component_manager.html new file mode 100644 index 0000000..c950284 --- /dev/null +++ b/src/core/templates/core/component_manager.html @@ -0,0 +1,56 @@ +{% extends "base.html" %} +{% load helpers %} + +{% block head_title %}Enter Scores {{ component.name }} | {% endblock head_title %} + +{% block breadcrumbs %} + +{% endblock breadcrumbs %} + +{% block content %} +
+

{{component}}

+
+
+ {% csrf_token %} +

Enter Scores

+ + + + + + {% if formset.errors %} + + {% endif %} + + + + {% for student in student_list %} + + + + + {% endfor %} + +
StudentScoreErrors
{{student.full_name}} + +   /   {{component.grade_total}} +
+ {{form.as_p}} +

+ or cancel +

+
+
+
+{% endblock %} diff --git a/src/core/templates/core/schoolyear_detail.html b/src/core/templates/core/schoolyear_detail.html index 0a02ad2..4cd4719 100644 --- a/src/core/templates/core/schoolyear_detail.html +++ b/src/core/templates/core/schoolyear_detail.html @@ -13,7 +13,7 @@ Edit {% endif %} -
+

Students

Tool Description

@@ -24,11 +24,11 @@

Tool Description

- +

Subjects

Tool Description

-
+ {% endblock %} diff --git a/src/core/templates/core/schoolyear_list.html b/src/core/templates/core/schoolyear_list.html index ca8583c..ac49187 100644 --- a/src/core/templates/core/schoolyear_list.html +++ b/src/core/templates/core/schoolyear_list.html @@ -14,32 +14,17 @@ - - - - - - - - - - - - {% for schoolyear in schoolyear_list %} - - - - - - - - {% empty %} - - - - {% endfor %} - -
CreatedYearStudentsComponentsLast Updated
No schoolyears yet.
+
+ {% for schoolyear in schoolyear_list %} + +

{{ schoolyear.year }}

+
Students: ()   Subjects: ()
+ +
+ {% empty %} +

No school years yet

+ {% endfor %} +
{% include 'core/partials/pagination.html' %} diff --git a/src/core/templates/core/score_confirm_delete.html b/src/core/templates/core/score_confirm_delete.html new file mode 100644 index 0000000..9fa1792 --- /dev/null +++ b/src/core/templates/core/score_confirm_delete.html @@ -0,0 +1,31 @@ +{% extends "base.html" %} + +{% block head_title %}Delete Score {{ score.pk }} | {% endblock head_title %} + +{% block breadcrumbs %} + +{% endblock breadcrumbs %} + +{% block content %} +
+

Delete Score

+
+ {% csrf_token %} +

Are you sure you want to delete "{{ score }}"?

+ {{ form.as_p }} +

+ or cancel +

+
+
+{% endblock %} diff --git a/src/core/templates/core/score_create_form.html b/src/core/templates/core/score_create_form.html new file mode 100644 index 0000000..1366c99 --- /dev/null +++ b/src/core/templates/core/score_create_form.html @@ -0,0 +1,12 @@ +{% extends 'base.html' %} + +{% block content %} +

+ New Score

+
+ {% csrf_token %} + {{ form.as_p }} +

+ or cancel +

+
+{% endblock %} diff --git a/src/core/templates/core/score_detail.html b/src/core/templates/core/score_detail.html new file mode 100644 index 0000000..7dcadeb --- /dev/null +++ b/src/core/templates/core/score_detail.html @@ -0,0 +1,10 @@ +{% extends 'base.html' %} + +{% block content %} +

Score

+

+ Edit + Delete +

+

{{ score }}

+{% endblock %} diff --git a/src/core/templates/core/score_form.html b/src/core/templates/core/score_form.html new file mode 100644 index 0000000..026eba1 --- /dev/null +++ b/src/core/templates/core/score_form.html @@ -0,0 +1,40 @@ +{% extends "base.html" %} + +{% block head_title %}Edit score {{ score.pk }} | {% endblock head_title %} + +{% block breadcrumbs %} + +{% endblock breadcrumbs %} + +{% block content %} +
+
+
+

Update Score

+

{{score.component.subject}}— {{score.component.get_category_display}}: {{score.component}}

+
+

Delete score

+
+
+
+ {% csrf_token %} + +

{{score.student}}

+ {{form.as_p}} +

+ or cancel +

+
+
+
+{% endblock %} diff --git a/src/core/templates/core/score_list.html b/src/core/templates/core/score_list.html new file mode 100644 index 0000000..f9a81d5 --- /dev/null +++ b/src/core/templates/core/score_list.html @@ -0,0 +1,16 @@ +{% extends 'base.html' %} + +{% block content %} +

Scores

+ + + {% for score in score_list %} + +
{{ score }}
+ + {% empty %} + No scores yet. + {% endfor %} + +
+{% endblock %} diff --git a/src/core/templates/core/student_detail.html b/src/core/templates/core/student_detail.html index 906a91f..64ae008 100644 --- a/src/core/templates/core/student_detail.html +++ b/src/core/templates/core/student_detail.html @@ -23,8 +23,8 @@ Edit -
-
+
+
{% if student.allergies %}
Allergies
{{ student.allergies }}
@@ -51,7 +51,7 @@ {% for subject in subject_list %} - {{subject}} +
{{subject}}
{{subject.grade|grade_as_percentage:subject.grade_total}}% {% empty %} @@ -65,35 +65,38 @@

Components

-
+
{% regroup score_list by component.subject as score_list %} {% for subject in score_list %} -

{{subject.grouper}}

- - - - - - - - - - - - - {% for score in subject.list %} - - - - - - - - - - {% endfor %} - -
Due DateComponentCategoryScoreTotalPercentage
{{score.component.due_date}}{{score.component}}{{score.component.get_category_display}}{{score.value}}{{score.component.grade_total}}{{score.grade_as_percentage}}%Change score
+
+

{{subject.grouper}}

+ + + + + + + + + + + + + {% for score in subject.list %} + + + + + + + + + + + {% endfor %} + +
Due DateComponentCategoryScoreTotalPercentage
{{score.component.due_date}}{{score.component}}{{score.component.get_category_display}}{{score.value}}{{score.component.grade_total}}{{score.grade_as_percentage}}%Change score
+
{% empty %}

No components graded yet.

{% endfor %} @@ -109,8 +112,8 @@ - - + + diff --git a/src/core/templates/core/student_form.html b/src/core/templates/core/student_form.html index 534a797..556dbdc 100644 --- a/src/core/templates/core/student_form.html +++ b/src/core/templates/core/student_form.html @@ -1,12 +1,29 @@ {% extends 'base.html' %} +{% load helpers %} + +{% block head_title %}Edit Student {{ student.student_id }} | {% endblock head_title %} + +{% block breadcrumbs %} + +{% endblock breadcrumbs %} {% block content %} -

Update Student

- - {% csrf_token %} - {{ form.as_p }} -

- or cancel -

- +
+

Update Student

+
+ {% csrf_token %} + {{ form.as_p }} +

+ or cancel +

+ +
{% endblock %} diff --git a/src/core/templates/core/subject_confirm_delete.html b/src/core/templates/core/subject_confirm_delete.html new file mode 100644 index 0000000..490ee4b --- /dev/null +++ b/src/core/templates/core/subject_confirm_delete.html @@ -0,0 +1,13 @@ +{% extends 'base.html' %} + +{% block content %} +

Delete Subject

+ + {% csrf_token %} +

Are you sure you want to delete "{{ subject }}"?

+ {{ form.as_p }} +

+ or cancel +

+ +{% endblock %} diff --git a/src/core/templates/core/subject_create_form.html b/src/core/templates/core/subject_create_form.html new file mode 100644 index 0000000..fa330d0 --- /dev/null +++ b/src/core/templates/core/subject_create_form.html @@ -0,0 +1,14 @@ +{% extends 'base.html' %} + +{% block content %} +
+

+ New Subject

+
+ {% csrf_token %} + {{ form.as_p }} +

+ or cancel +

+ +
+{% endblock %} diff --git a/src/core/templates/core/subject_detail.html b/src/core/templates/core/subject_detail.html new file mode 100644 index 0000000..ce396e1 --- /dev/null +++ b/src/core/templates/core/subject_detail.html @@ -0,0 +1,62 @@ +{% extends 'base.html' %} +{% load helpers %} + +{% block head_title %}Subject {{ subject.subject_id }} | {% endblock head_title %} + +{% block breadcrumbs %} + +{% endblock breadcrumbs %} + +{% block content %} +
+
+
+

{{ subject.name }}

+
{{ subject.description }}
+

Added:
+ Last updated:

+
+ Edit +
+
+
+
+

Components

+ + New component +
+
+
DateStatusDateStatus
+ + + + + + + + + + + {% for component in subject.component_set.all %} + + + + + + + + {% empty %} + + + + {% endfor %} + +
Due DateDescriptionCategoryGrade TotalAvg Score
No components yet.
+
+ +{% endblock %} diff --git a/src/core/templates/core/subject_form.html b/src/core/templates/core/subject_form.html new file mode 100644 index 0000000..0ce4497 --- /dev/null +++ b/src/core/templates/core/subject_form.html @@ -0,0 +1,12 @@ +{% extends 'base.html' %} + +{% block content %} +

Update Subject

+
+ {% csrf_token %} + {{ form.as_p }} +

+ or cancel +

+
+{% endblock %} diff --git a/src/core/templates/core/subject_list.html b/src/core/templates/core/subject_list.html new file mode 100644 index 0000000..173275b --- /dev/null +++ b/src/core/templates/core/subject_list.html @@ -0,0 +1,48 @@ +{% extends 'base.html' %} +{% load static %} + +{% block head_title %}Subjects | {% endblock head_title %} + +{% block breadcrumbs %} + +{% endblock breadcrumbs %} + +{% block content %} +
+
+
+

Subjects

+ + New Subject +
+ Subject Tags → +
+ + + + + + + + + {% for subject in subject_list %} + + + + + {% empty %} + + + + {% endfor %} + +
NameComponents
No subjects yet.
+ {% include 'core/partials/pagination.html' %} +
+{% endblock %} diff --git a/src/core/urls.py b/src/core/urls.py index 650e8fb..a4a811e 100644 --- a/src/core/urls.py +++ b/src/core/urls.py @@ -38,7 +38,7 @@ urlpatterns = [ views.StudentCreateView.as_view(), name='student-create' ), - path('/', include([ + path('/', include([ path( '', views.StudentDetailView.as_view(), @@ -56,6 +56,104 @@ urlpatterns = [ ), ])), ])), + + # Subjects + path('subjects/', include([ + path( + '', + views.SubjectListView.as_view(), + name='subject-list' + ), + path( + 'new/', + views.SubjectCreateView.as_view(), + name='subject-create' + ), + path('/', 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' + ), + + # Components + path('components/', include([ + path( + '', + views.ComponentListView.as_view(), + name='component-list' + ), + path( + 'new/', + views.ComponentCreateView.as_view(), + name='component-create' + ), + path('/', include([ + path( + '', + views.ComponentDetailView.as_view(), + name='component-detail' + ), + path( + 'update/', + views.ComponentUpdateView.as_view(), + name='component-update' + ), + path( + 'delete/', + views.ComponentDeleteView.as_view(), + name='component-delete' + ), + path( + 'manage/', + views.ComponentManagerView.as_view(), + name='component-manager' + ), + ])), + ])), + ])), + ])), + + # Scores + path('scores/', include([ + path( + '', + views.ScoreListView.as_view(), + name='score-list' + ), + path( + 'new/', + views.ScoreCreateView.as_view(), + name='score-create' + ), + path('/', 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' + ), + ])), + ])), ])), ])), ] diff --git a/src/core/views.py b/src/core/views.py index 352f73e..346e175 100644 --- a/src/core/views.py +++ b/src/core/views.py @@ -33,18 +33,14 @@ from .models import ( ) from .forms import ( - SchoolYearCreateForm + SchoolYearCreateForm, + ComponentCreateForm ) class SchoolYearListView(ListView): model = SchoolYear - - -class SchoolYearDetailView(DetailView): - model = SchoolYear - slug_url_kwarg = 'year' - slug_field = 'year' + paginate_by = 10 class SchoolYearCreateView(SuccessMessageMixin, CreateView): @@ -54,6 +50,12 @@ class SchoolYearCreateView(SuccessMessageMixin, CreateView): template_name_suffix = '_create_form' +class SchoolYearDetailView(DetailView): + model = SchoolYear + slug_url_kwarg = 'year' + slug_field = 'year' + + class SchoolYearUpdateView(SuccessMessageMixin, UpdateView): model = SchoolYear slug_url_kwarg = 'year' @@ -80,9 +82,32 @@ class StudentListView(ListView): return context +class StudentCreateView(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(DetailView): model = Student - pk_url_kwarg = 'student_pk' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) @@ -116,37 +141,261 @@ class StudentDetailView(DetailView): return context -class StudentCreateView(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 StudentUpdateView(SuccessMessageMixin, UpdateView): model = Student - pk_url_kwarg = 'student_pk' success_message = 'Student saved.' fields = '__all__' class StudentDeleteView(SuccessMessageMixin, DeleteView): model = Student - pk_url_kwarg = 'student_pk' success_message = 'Student deleted.' success_url = reverse_lazy('student-list') + + +class SubjectListView(ListView): + model = Subject + paginate_by = 50 + + def get_queryset(self): + queryset = Subject.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 SubjectCreateView(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(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(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(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(ListView): + model = Component + + +class ComponentCreateView(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(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.exclude( + score__in=cscores + ) + return context + + +class ComponentUpdateView(SuccessMessageMixin, UpdateView): + model = Component + pk_url_kwarg = 'component_pk' + success_message = 'Component saved.' + fields = '__all__' + + +class ComponentDeleteView(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.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(ListView): + model = Score + + +class ScoreCreateView(SuccessMessageMixin, CreateView): + model = Score + success_message = 'Score created.' + template_name_suffix = '_create_form' + fields = '__all__' + + +class ScoreDetailView(DetailView): + model = Score + + +class ScoreUpdateView(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(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 diff --git a/src/indici/settings.py b/src/indici/settings.py index 3b7ea10..358beee 100644 --- a/src/indici/settings.py +++ b/src/indici/settings.py @@ -65,6 +65,7 @@ TEMPLATES = [ 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', + 'core.context_processors.current_year', ], }, }, @@ -146,7 +147,7 @@ STATICFILES_FINDERS = ( DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' AUTH_USER_MODEL = 'accounts.User' -LOGIN_REDIRECT_URL = reverse_lazy('student-list') +LOGIN_REDIRECT_URL = reverse_lazy('core:schoolyear-list') # Decimal settings DEFAULT_DECIMAL_PLACES = 2 diff --git a/src/static/styles/main.css b/src/static/styles/main.css index c825ea9..aeb1c06 100644 --- a/src/static/styles/main.css +++ b/src/static/styles/main.css @@ -136,7 +136,7 @@ thead a { color: inherit; text-decoration: none; } -tr:nth-child(even) { +tbody tr:nth-child(even) { background-color: var(--color-light-gray); } th, td { @@ -159,6 +159,11 @@ tbody tr.has-link { cursor: pointer; } +td h5 { + margin-bottom: 0; + line-height: 1.5; +} + /* Forms ========================================================================== */ form { @@ -198,6 +203,7 @@ input[type=search]:focus { } button, +input[type=submit], .action-button { cursor: pointer; border: none; @@ -211,6 +217,10 @@ button, border-radius: 0.5rem; } +.action-delete { + background-color: var(--color-danger); +} + form progress { display: none; } @@ -367,6 +377,10 @@ article > header { margin-bottom: 1.5rem; } +article > section:not(:last-child) { + margin-bottom: 4rem; +} + /* List ========================================================================== */ @@ -409,6 +423,16 @@ article > header { /*justify-self: end;*/ } + +/* Form + ========================================================================== */ +.form__header { + display: flex; + align-items: baseline; + justify-content: space-between; +} + + /* Breadcrumbs ========================================================================== */ .breadcrumbs { @@ -462,8 +486,41 @@ article > header { background-color: var(--color-yellow); } -.tool *:last-child { +.tool > *:last-child { justify-self: end; align-self: end; font-size: 2rem; } + +/* SchoolYear + ========================================================================== */ +.schoolyears__list { + display: grid; + grid-template-columns: 1fr; + gap: 2rem; +} + +.schoolyear { + display: grid; + grid-template-rows: repeat(3, auto); + min-height: 4rem; + border: var(--default-border); + padding: 1rem; + border-radius: 0.5rem; + text-decoration: none; + color: var(--color-primary); +} + +.schoolyear:hover { + background-color: var(--color-yellow); +} + +.schoolyear > *:last-child { + justify-self: end; + align-self: end; + font-size: 2rem; +} + +.student__component { + margin-bottom: 2rem; +} diff --git a/src/templates/base.html b/src/templates/base.html index b967abf..99562cb 100644 --- a/src/templates/base.html +++ b/src/templates/base.html @@ -29,11 +29,11 @@ {% endif %}> +
  • Years
  • Today
  • -
  • Students
  • +
  • Students
  • Attendance
  • -
  • Curricula
  • -
  • Communications
  • +
  • Subjects