diff --git a/core/migrations/0012_auto_20170711_1104.py b/core/migrations/0012_auto_20170711_1104.py new file mode 100644 index 0000000000000000000000000000000000000000..ca27ba4ce3c4c3b614a893878768bc9b02c9765e --- /dev/null +++ b/core/migrations/0012_auto_20170711_1104.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-07-11 11:04 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0011_auto_20170710_1610'), + ] + + operations = [ + migrations.CreateModel( + name='ExamType', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('module_reference', models.CharField(max_length=50)), + ('total_score', models.PositiveIntegerField()), + ('pass_score', models.PositiveIntegerField()), + ('pass_only', models.BooleanField(default=False)), + ], + options={ + 'verbose_name': 'ExamType', + 'verbose_name_plural': 'ExamTypes', + }, + ), + migrations.AddField( + model_name='student', + name='exam', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='students', to='core.ExamType'), + ), + ] diff --git a/core/models.py b/core/models.py index 22480b510d9baceeb41247459168acdf5e782d04..30543fdab970f90cdc998bf33abf9a3eb1061652 100644 --- a/core/models.py +++ b/core/models.py @@ -47,7 +47,8 @@ from collections import OrderedDict from django.contrib.auth.models import User from django.db import models -from django.db.models import Q +from django.db.models import Q, Sum, Value as V +from django.db.models.functions import Coalesce SLUG_LENGTH = 16 @@ -67,9 +68,9 @@ class ExamType(models.Model): verbose_name_plural = "ExamTypes" def __str__(self): - pass + return self.module_reference - module_reference = models.CharField(max_length=50) + module_reference = models.CharField(max_length=50, unique=True) total_score = models.PositiveIntegerField() pass_score = models.PositiveIntegerField() pass_only = models.BooleanField(default=False) @@ -95,7 +96,7 @@ class SubmissionType(models.Model): class Student(models.Model): # Fields has_logged_in = models.BooleanField(default=False) - exam = models.ForeignKey('ExamType', related_name='students') + exam = models.ForeignKey('ExamType', related_name='students', null=True) name = models.CharField(max_length=50, default="__no_name__") matrikel_no = models.CharField( unique=True, max_length=8, default=random_matrikel_no) @@ -115,7 +116,13 @@ class Student(models.Model): t.name : 0 for t in SubmissionType.objects.all() }) - def overall_score(self): + @classmethod + def get_overall_score_annotated_submission_list(cls): + return cls.objects.annotate( + overall_score=Coalesce(Sum('submissions__feedback__score'), V(0)), + ) + + def overall_score(self): # TODO purge return sum(self.score_per_submission().values()) def has_passed_exam(self): diff --git a/core/templates/core/r/reviewer_base.html b/core/templates/core/r/reviewer_base.html index 6ab586329f900be8c86ad062ac30ebdf0775ad8f..f2e5e46f9d01d24e2b65e4a28986f7f3d60012f4 100644 --- a/core/templates/core/r/reviewer_base.html +++ b/core/templates/core/r/reviewer_base.html @@ -5,6 +5,7 @@ {% block navbar %} <a class="nav-item nav-link" href="{% url 'start' %}">Feedback</a> <a class="nav-item nav-link" href="{% url 'submission_list' %}">Submissions</a> +<a class="nav-item nav-link" href="{% url 'student_list' %}">Students</a> <div class="nav-item dropdown"> <a class="nav-link dropdown-toggle" href="http://example.com" id="navbarDropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> Export @@ -17,7 +18,7 @@ {% block body_block %} <div class="row my-3"> - <div class="col-4"> + <div class="col-3"> {% block sidebar %} {# This just gives an overview about what has been done already #} {% include "core/r/progress_card.html" %} @@ -27,7 +28,7 @@ {% endblock sidebar %} </div> - <div class="col-8"> + <div class="col-9"> {% block main %} {% endblock main %} diff --git a/core/templates/core/r/single_submission.html b/core/templates/core/r/single_submission.html index 09d388b3849725e3530c8e7dce92124626a0c654..3e283e9e3a0d796ecb0be94cf4f1477a1898431a 100644 --- a/core/templates/core/r/single_submission.html +++ b/core/templates/core/r/single_submission.html @@ -39,7 +39,7 @@ Create Feedback {% endif %} </a> - <a href="{% url 'submission_list' %}" class="btn btn-outline-success">Back</a> + <button class="btn btn-outline-success" onclick="window.history.go(-1); return false;">Back</button> </div> </div> </div> diff --git a/core/templates/core/r/student_list.html b/core/templates/core/r/student_list.html new file mode 100644 index 0000000000000000000000000000000000000000..ebaea5e390613bf61ad386710e6a91ac0dbcc7fc --- /dev/null +++ b/core/templates/core/r/student_list.html @@ -0,0 +1,57 @@ +{% extends 'core/r/reviewer_base.html' %} + +{% load staticfiles %} + +{% block main %} + +<div class="card"> + <h5 class="card-header">Student Overview</h5> + <div class="card-block"> + <table id="list-id-submission_list" class="table nomargin"> + <thead> + <tr> + <th>Name</th> + <th>Username</th> + <th>Module</th> + {% for submission_type in submission_type_list %} + <th>{{submission_type.name}}</th> + {% endfor %} + <th>Total score</th> + </tr> + </thead> + <tbody> + {% for student in student_list %} + <tr> + <td class="fit">{{student.name}}</td> + <td>{{student.user.username}}</td> + <td>{{student.exam}}</td> + {% for sub in student.submissions.all %} + <td>{% if sub.feedback %} + <a href="{% url 'SubmissionViewReviewer' sub.slug %}" role="button" class="btn-link btn-sm"><code>{{sub.feedback.score}} / {{sub.type.full_score}}</code></a> + {% else %} + <a href="{% url 'SubmissionViewReviewer' sub.slug %}" role="button" class="btn btn-outline-primary btn-sm">View</a> + {% endif %} </td> + {% endfor %} + <td><code>{{student.overall_score}}</code></td> + </tr> + {% endfor %} + </tbody> + </table> + </div> +</div> + +{% endblock main %} + +{% block script_block %} +<script> + $(document).ready(function() { + $('[id^=list-id-]').DataTable({ + "paging": false, + "info": false, + "searching": false, + "stateSave": true, + "order": [[ 0, 'desc' ]], + }); + }); +</script> +{% endblock script_block %} diff --git a/core/urls.py b/core/urls.py index 42c5917be07fa9905c8f360a0ffe5ec3b7ff47ab..4c36c25c43a46df9a68316a5114d4b5b40ff89e4 100644 --- a/core/urls.py +++ b/core/urls.py @@ -14,6 +14,7 @@ urlpatterns = [ url(r'^feedback/edit/(?P<feedback_slug>\w+)/$', views.FeedbackEdit.as_view(), name='FeedbackEdit'), url(r'^feedback/delete/(?P<feedback_slug>\w+)/$', views.delete_feedback, name='FeedbackDelete'), + url(r'^r/studentlist/$', views.StudentListView.as_view(), name='student_list'), url(r'^r/submission/list/$', views.get_submission_list, name='submission_list'), url(r'^r/submission/view/(?P<slug>\w+)/$', views.SubmissionViewReviewer.as_view(), name='SubmissionViewReviewer'), url(r'^r/submission/create-feedback-for/(?P<slug>\w+)/$', views.create_feedback_for_submission, name='create_feedback_for_submission'), diff --git a/core/views/submission.py b/core/views/submission.py index 2b93d90c3d2d209ee3f44f86d4073e47f69a5d91..c0d3c6bbf969cdbaa89558ec7e35d084327d37f4 100644 --- a/core/views/submission.py +++ b/core/views/submission.py @@ -1,9 +1,11 @@ from django.utils.decorators import method_decorator -from django.views.generic import DetailView +from django.views.generic import DetailView, ListView from django.shortcuts import render +from django.db.models import Sum + from core.custom_annotations import group_required, in_groups -from core.models import Submission, Feedback +from core.models import Submission, Feedback, Student from .user_startpages import get_annotated_feedback_count, get_annotated_tutor_list @@ -46,3 +48,30 @@ def get_submission_list(request): 'tutor_list': get_annotated_tutor_list(), } return render(request, 'core/r/student_submission_list.html', context) + + +class StudentListView(ListView): + + model = Student + template_name = 'core/r/student_list.html' + context_object_name = 'student_list' + + @method_decorator(group_required('Reviewers',)) + def dispatch(self, *args, **kwargs): + return super().dispatch(*args, **kwargs) + + def get_queryset(self): + ret = self.model.get_overall_score_annotated_submission_list() + print(ret) + return ret + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + + context = { + **context, + 'submission_type_list': get_annotated_feedback_count(), + 'tutor_list': get_annotated_tutor_list(), + } + + return context