From 38c42d20becac0c15aeca1b13e14e57ba2f182e1 Mon Sep 17 00:00:00 2001 From: janmax <j.michal@stud.uni-goettingen.de> Date: Fri, 16 Feb 2018 17:46:00 +0100 Subject: [PATCH] Added an export endpoint that produces a CSV file --- core/models.py | 4 +- core/tests/test_export.py | 86 +++++++++++++++++++++++++++++++++++++++ core/views/__init__.py | 1 + core/views/export.py | 39 ++++++++++++++++++ grady/urls.py | 6 ++- util/format_index.py | 2 +- 6 files changed, 133 insertions(+), 5 deletions(-) create mode 100644 core/tests/test_export.py create mode 100644 core/views/export.py diff --git a/core/models.py b/core/models.py index 80c3a42f..7e680a27 100644 --- a/core/models.py +++ b/core/models.py @@ -244,7 +244,7 @@ class StudentInfo(models.Model): }) @classmethod - def get_overall_score_annotated_submission_list(cls) -> QuerySet: + def get_annotated_score_submission_list(cls) -> QuerySet: """Can be used to quickly annotate a user with the necessary information on the overall score of a student and if he does not need any more correction. @@ -267,7 +267,7 @@ class StudentInfo(models.Model): default=Value(0), output_field=BooleanField() ) - ) + ).order_by('user__username') def disable(self): """The student won't be able to login in anymore, but his current diff --git a/core/tests/test_export.py b/core/tests/test_export.py new file mode 100644 index 00000000..182db206 --- /dev/null +++ b/core/tests/test_export.py @@ -0,0 +1,86 @@ +from rest_framework import status +from django.test import TestCase, Client + +from util.factories import make_test_data + + +class ExportCSVTest(TestCase): + + @classmethod + def setUpTestData(cls): + cls.data = make_test_data(data_dict={ + 'submission_types': [ + { + 'name': '01. Sort', + 'full_score': 35, + 'description': 'Very complicated', + 'solution': 'Trivial!' + }, + { + 'name': '02. Shuffle', + 'full_score': 35, + 'description': 'Very complicated', + 'solution': 'Trivial!' + } + ], + 'students': [ + {'username': 'student01'}, + {'username': 'student02'} + ], + 'reviewers': [ + {'username': 'reviewer'} + ], + 'submissions': [ + { + 'text': 'function blabl\n' + ' on multi lines\n' + ' for blabla in bla:\n' + ' lorem ipsum und so\n', + 'type': '01. Sort', + 'user': 'student01', + 'feedback': { + 'score': 5, + 'is_final': True, + 'feedback_lines': { + '1': [{ + 'text': 'This is very bad!', + 'of_tutor': 'reviewer' + }], + } + + } + }, + { + 'text': 'not much', + 'type': '02. Shuffle', + 'user': 'student01' + }, + { + 'text': 'function blabl\n' + ' asasxasx\n' + ' lorem ipsum und so\n', + 'type': '01. Sort', + 'user': 'student02' + }, + { + 'text': 'not much to see here', + 'type': '02. Shuffle', + 'user': 'student02' + } + ]} + ) + + def setUp(self): + client = Client() + client.force_login(user=self.data['reviewers'][0]) + + self.response = client.get('/export/csv/') + + def test_can_access(self): + self.assertEqual(status.HTTP_200_OK, self.response.status_code) + + def test_data_is_correct(self): + head, student1, student2, _ = self.response.content.split(b'\r\n') + self.assertIn(b'Matrikel,Username,Name,Sum,01. Sort,02. Shuffle', head) + self.assertIn(b'student01,,5,5,0', student1) + self.assertIn(b'student02,,0,0,0', student2) diff --git a/core/views/__init__.py b/core/views/__init__.py index 990e8c5c..beac6e7a 100644 --- a/core/views/__init__.py +++ b/core/views/__init__.py @@ -1,3 +1,4 @@ from .feedback import FeedbackApiView # noqa from .subscription import SubscriptionApiViewSet, AssignmentApiViewSet # noqa from .common_views import * # noqa +from .export import StudentCSVExport # noqa diff --git a/core/views/export.py b/core/views/export.py new file mode 100644 index 00000000..e08fa7d8 --- /dev/null +++ b/core/views/export.py @@ -0,0 +1,39 @@ +import csv + +from django.contrib.auth.mixins import PermissionRequiredMixin +from django.http import HttpResponse +from django.views.generic import View + +from core.models import StudentInfo, SubmissionType +from core.permissions import IsReviewer + + +class StudentCSVExport(PermissionRequiredMixin, View): + + def has_permission(self): + return IsReviewer().has_permission(self.request, self) + + def get(self, request, format=None): + # Create the HttpResponse object with the appropriate CSV header. + response = HttpResponse(content_type='text/csv') + response['Content-Disposition'] = 'attachment; filename="results.csv"' + + writer = csv.writer(response) + writer.writerow(['Matrikel', + 'Username', + 'Name', + 'Sum', + *SubmissionType.objects + .order_by('name') + .values_list('name', flat=True)]) + + for student in StudentInfo.get_annotated_score_submission_list(): + writer.writerow([ + student.matrikel_no, + student.user.username, + student.user.fullname, + student.overall_score, + *student.score_per_submission().values() + ]) + + return response diff --git a/grady/urls.py b/grady/urls.py index 8e651c92..ae77b57f 100644 --- a/grady/urls.py +++ b/grady/urls.py @@ -3,6 +3,8 @@ from django.urls import include, path from django.views.generic.base import TemplateView from rest_framework_jwt.views import obtain_jwt_token, refresh_jwt_token +from core import views + urlpatterns = [ path('admin/', admin.site.urls), path('api/', include('core.urls')), @@ -10,6 +12,6 @@ urlpatterns = [ path('api/refresh-token/', refresh_jwt_token), path('api-auth/', include('rest_framework.urls', namespace='rest_framework')), - path('', TemplateView.as_view(template_name='index.html')) - + path('', TemplateView.as_view(template_name='index.html')), + path('export/csv/', views.StudentCSVExport.as_view(), name='export-csv'), ] diff --git a/util/format_index.py b/util/format_index.py index c3f1f7d6..bf2ffa8f 100644 --- a/util/format_index.py +++ b/util/format_index.py @@ -1,5 +1,5 @@ -import sys import fileinput +import sys file = 'core/templates/index.html' -- GitLab