Skip to content
Snippets Groups Projects
Commit 8b481c21 authored by Jan Maximilian Michal's avatar Jan Maximilian Michal
Browse files

Merge branch '11-fix-the-stuff-from-the-review-the-henrik-brosenne' into 'master'

Resolve "Fix the stuff from the review the Henrik Brosenne"

Closes #11

See merge request !1
parents 68d16da9 70590ca7
No related branches found
No related tags found
1 merge request!1Resolve "Fix the stuff from the review the Henrik Brosenne"
......@@ -50,7 +50,6 @@ from django.db.models import Q
SLUG_LENGTH = 16
def random_slug():
return ''.join(sample(ascii_lowercase, SLUG_LENGTH))
......@@ -125,7 +124,7 @@ class Submission(models.Model):
)
@classmethod
def assign_tutor(cls, tutor, type_slug=None) -> bool:
def assign_tutor(cls, tutor, slug=None) -> bool:
"""Assigns a tutor to a submission
A submission is not assigned to the specified tutor in the case
......@@ -144,18 +143,27 @@ class Submission(models.Model):
if unfinished:
return False
ready = cls.objects.filter(feedback__isnull=True)
candidates = cls.objects.filter(
(
Q(feedback__isnull=True)
| Q(feedback__origin=Feedback.DID_NOT_COMPILE)
| Q(feedback__origin=Feedback.COULD_NOT_LINK)
)
& ~Q(feedback__of_tutor=tutor)
)
# we want a submission of a specific type
if slug:
candidates = candidates.filter(type__slug=slug)
# we do not want this tutor to correct the same submission twice
if type_slug:
ready = ready.filter(type__slug=type_slug)
ready = ready.exclude(feedback__of_tutor=tutor)
if not ready:
# we couldn't find any submission to correct
if not candidates:
return False
submission = ready[0]
feedback = Feedback()
submission = candidates[0]
feedback = submission.feedback if hasattr(submission, 'feedback') else Feedback()
feedback.origin = Feedback.MANUAL
feedback.status = Feedback.EDITABLE
feedback.of_tutor = tutor
feedback.of_submission = submission
feedback.save()
......
......@@ -52,3 +52,9 @@ table.dataTable {
.col-sm-12 {
padding: 0 !important; // this is ugly as --fucking-- hell
}
.table td.fit,
.table th.fit {
white-space: nowrap;
width: 1%;
}
......@@ -11,7 +11,10 @@
<div class="col my-2 nopadding-right">
<div class="card mb-2">
<h4 class="card-header">{{feedback.of_submission.type.name}}</h4>
<div class="card-header form-inline pb-1">
<h4 class="mr-2">{{feedback.of_submission.type.name}}</h4>
<a href="{% url 'download_submissions' feedback.of_submission.slug %}" class="btn btn-sm btn-outline-secondary mb-1">Download</a>
</div>
<div class="card-block">
<div id="student_text" class="editor editor-code"></div>
</div>
......@@ -98,11 +101,12 @@
{# Beware! compares status and origin #}
{% if feedback.status == feedback.NEEDS_REVIEW or feedback.status == feedback.EDITABLE %}
<button type="submit" form="form1" class="btn btn-success mr-1 mb-1" name="update" value="Next">Next</button>
<button type="submit" form="form1" class="btn btn-success mr-1 mb-1" name="update" value="Next">Next</button>
<button type="submit" form="form1" class="btn btn-success mr-1 mb-1" name="update" value="Save">Save</button>
{% endif %}
{% if feedback.origin != feedback.MANUAL %}
<a href="{% url 'FeedbackDelete' feedback.slug %}" class="btn btn-outline-danger mr-1 mb-1" name="delete" value="Delete">Delete auto feedback</a>
<a href="{% url 'FeedbackDelete' feedback.slug %}" class="btn btn-outline-danger mr-1 mb-1" name="delete" value="Delete">Delete auto feedback</a>
{% endif %}
{% if feedback.status == feedback.ACCEPTED %}
......
{# This is where all the messages pop up #}
{% if messages %}
{% if messages or form.errors %}
<div class="row">
<div class="col my-2">
{% for message in messages %}
......
......@@ -27,6 +27,7 @@
</tbody>
</table>
<div class="card-footer text-muted">
<a role="button" class="btn btn-outline-primary" href="{% url 'submission_list' %}">See all submissions</a>
<a role="button" class="btn btn-outline-primary" href="{% url 'export' %}">Download CSV data</a>
</div>
</div>
......@@ -108,13 +109,14 @@
<script>
$(document).ready(function() {
$('[id^=list-id-]').DataTable({
"paging": false,
"info": false,
"searching": false,
"order": [[ 0, 'desc' ], [ 2, 'desc' ]],
"columnDefs": [
{ "orderable": false, "targets": -1 },
]
"paging": false,
"info": false,
"searching": false,
"stateSave": true,
"order": [[ 0, 'desc' ], [ 2, 'desc' ]],
"columnDefs": [
{ "orderable": false, "targets": -1 },
]
});
});
</script>
......
{% extends 'base.html' %}
{% load staticfiles %}
{% block nav_title %} But they've tried so hard... {% endblock nav_title %}
{% block body_block %}
<div class="row">
<div class="col m-2">
<div class="card">
<h5 class="card-header">All student submissions</h5>
<div class="card-block">
<table id="list-id-submission_list" class="table nomargin">
<thead>
<tr>
<th></th>
<th>Task</th>
<th>Student</th>
<th>Score</th>
<th>Tutor</th>
<th></th>
</tr>
</thead>
<tbody>
{% for submission in submission_list %}
<tr>
<td class="align-middle fit"> <a href="{% url 'SubmissionView' submission.slug %}" class="btn btn-outline-primary mb-1" name="edit" value="View">View submission</a></td>
<td class="align-middle"> {{ submission.type }} </td>
<td class="align-middle"> {{ submission.student }} </td>
<td class="align-middle">
{% if submission.feedback %}
<code> {{ submission.feedback.score }} / {{ submission.type.full_score }} </code>
{% else %}
<code> no feedback </code>
{% endif %}
</td>
<td class="align-middle">
{% if submission.feedback %}
{{submission.feedback.of_tutor}}
{% endif %}
</td>
<td class="align-middle fit">
{% if submission.feedback %}
{% if submission.feedback.origin == submission.feedback.WAS_EMPTY %}
<button type="button" class="btn btn-secondary" disabled>was empty</button>
{% else %}
<a href="{% url 'FeedbackEdit' submission.feedback.slug %}" class="btn btn-outline-primary" name="edit" value="View">Edit Feedback</a>
<a href="{% url 'FeedbackDelete' submission.feedback.slug %}" class="btn btn-outline-danger" name="delete" value="Delete">Delete Feedback</a>
{% endif %}
{% else %}
<a href="{% url 'create_feedback_for_submission' submission.slug %}" class="btn btn-outline-success"> Create Feedback </a>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
{% endblock body_block %}
{% block script_block %}
<script>
$(document).ready(function() {
$('[id^=list-id-]').DataTable({
"paging": false,
"info": false,
"searching": false,
"stateSave": true,
"order": [[ 2, 'desc' ]],
"columnDefs": [
{ "orderable": false, "targets": [0, -1] },
]
});
});
</script>
{% endblock script_block %}
......@@ -5,7 +5,7 @@
{% block body_block %}
{% with submission.feedback_list.all|first as feedback %}
{% with submission.feedback as feedback %}
<div class="row justify-content-center">
<div class="col-3 my-4">
<div class="card">
......@@ -16,9 +16,22 @@
<ul class="list-group list-group-flush">
<li class="list-group-item"><strong class="mr-2">Submission Type: </strong> {{ submission.type }} </li>
<li class="list-group-item"><strong class="mr-2">Student: </strong> {{ submission.student }}</li>
{% if feedback and is_reviewer %}
<li class="list-group-item">
<strong class="mr-2">Status: </strong> {% include "core/feedback_badge.html" %}
<span class="badge badge-warning ml-2">Only visible to reviewer</span>
</li>
<li class="list-group-item">
<strong class="mr-2">Tutor: </strong> {{ feedback.of_tutor }}
<span class="badge badge-warning ml-2">Only visible to reviewer</span>
</li>
{% endif %}
<li class="list-group-item"><strong class="mr-2">Score: </strong>
{% if feedback.final %}
{% if feedback and feedback.status == feedback.ACCEPTED %}
<code> {{ feedback.score }} / {{submission.type.full_score}} </code>
{% elif feedback and is_reviewer %}
<code> {{ feedback.score }} / {{submission.type.full_score}} </code>
<span class="badge badge-warning ml-2">Only visible to reviewer</span>
{% else %}
<span class="badge badge-danger">No Feedback</span>
{% endif %}
......@@ -26,7 +39,18 @@
</ul>
</div>
<div class="card-footer">
{% if is_reviewer %}
<a href="{% url 'create_feedback_for_submission' submission.slug %}" class="btn btn-success">
{% if feedback %}
Edit Feedback
{% else %}
Create Feedback
{% endif %}
</a>
<a href="{% url 'submission_list' %}" class="btn btn-outline-success">Back</a>
{% else %}
<a href="{% url 'start' %}" class="btn btn-success">Back</a>
{% endif %}
</div>
</div>
</div>
......@@ -40,11 +64,17 @@
</div>
</div>
{% if feedback.final %}
{% if feedback %}
{% if feedback.status == feedback.ACCEPTED or is_reviewer %}
<div class="col-4 my-4">
<div class="card">
<div class="card-block">
<div class="card-header">Our feedback</div>
<div class="card-header">Our feedback
{% if is_reviewer %}
<span class="badge badge-warning ml-2">Only visible to reviewer</span>
{% endif %}
</div>
<div class="editor-code" id="textarea_feedback">{{ feedback.text }}</div>
</div>
</div>
......@@ -56,6 +86,7 @@
})
</script>
{% endif %}
{% endif %}
</div>
{% endwith %}
{% endblock body_block %}
......
......@@ -109,6 +109,7 @@
"paging": false,
"info": false,
"searching": false,
"stateSave": true,
"order": [[ 0, 'desc' ], [ 3, 'desc' ]],
"columnDefs": [
{ "orderable": false, "targets": -1 },
......
......@@ -15,7 +15,10 @@ 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'^submission/list/$', views.get_submission_list, name='submission_list'),
url(r'^submission/view/(?P<slug>\w+)/$', views.SubmissionView.as_view(), name='SubmissionView'),
url(r'^submission/create-feedback-for/(?P<slug>\w+)/$', views.create_feedback_for_submission, name='create_feedback_for_submission'),
url(r'^submission/download/(?P<slug>\w+)/$', views.download_submissions, name='download_submissions'),
url(r'^csv/$', views.export_csv, name='export')
]
......
......@@ -12,7 +12,7 @@ def export_csv(request):
response['Content-Disposition'] = 'attachment; filename="grady_results.csv"'
writer = csv.writer(response)
writer.writerow(['Matrikel', 'Name', 'Summe'] +
writer.writerow(['Matrikel', 'Username', 'Name', 'Sum'] +
[s.name for s in SubmissionType.objects.all().order_by('name')])
for student in Student.objects.all():
......@@ -22,6 +22,7 @@ def export_csv(request):
score_list = [0] * SubmissionType.objects.count()
writer.writerow([
student.matrikel_no,
student.user.username,
student.name,
sum(score_list),
*score_list
......
from random import choice
from django.contrib import messages
from django.http import Http404, HttpResponseRedirect
from django.http import Http404, HttpResponseRedirect, HttpResponse
from django.urls import reverse
from django.utils.decorators import method_decorator
from django.views.generic.edit import UpdateView
......@@ -56,6 +56,8 @@ class FeedbackEdit(UpdateView):
elif instance.status == Feedback.OPEN:
instance.reassign_to_tutor(self.request.user)
return instance
elif not (instance.of_tutor == self.request.user or in_groups(self.request.user, ('Reviewers', ))):
messages.error(self.request, "Get your hands of somebody else's feedback!")
raise Http404
def form_valid(self, form):
......@@ -72,16 +74,38 @@ class FeedbackEdit(UpdateView):
# ugly needs patch
if 'Next' in self.request.POST['update']:
if in_groups(self.request.user, ('Reviewers',)):
needs_review = Feedback.objects.filter(status=Feedback.NEEDS_REVIEW)
needs_review = needs_review[0] if needs_review else None
needs_review = Feedback.objects.filter(
status=Feedback.NEEDS_REVIEW,
of_submission__type=form.instance.of_submission.type
)
if needs_review:
return HttpResponseRedirect(reverse('FeedbackEdit', args=(needs_review.slug,)))
else:
return HttpResponseRedirect(self.get_success_url())
return HttpResponseRedirect(reverse('CreateFeedbackForType', args=(form.instance.of_submission.type.slug,)))
return HttpResponseRedirect(reverse('FeedbackEdit', args=(needs_review[0].slug,)))
else: # in_groups(self.request.user, ('Tutor',)):
return HttpResponseRedirect(reverse('CreateFeedbackForType', args=(form.instance.of_submission.type.slug,)))
elif 'Save' in self.request.POST['update']:
return HttpResponseRedirect(reverse('FeedbackEdit', args=(form.instance.slug,)))
return HttpResponseRedirect(self.get_success_url())
def get_context_data(self, **kwargs):
context = super(FeedbackEdit, self).get_context_data(**kwargs)
context['grady_says'] = choice(grady_says)
return context
@group_required('Reviewers', 'Tutors')
def download_submissions(request, slug):
submission = Submission.objects.get(slug=slug)
if not (submission.feedback.of_tutor == request.user or in_groups(request.user, ('Reviewers', ))):
messages.error(request, "Get your hands of somebody else's feedback!")
return HttpResponseRedirect(reverse('start'))
response = HttpResponse(content_type='text/plain')
response['Content-Disposition'] = 'attachment; filename="%s_%s.c"' % (submission.type.name, submission.slug)
response.write(submission.text)
return response
from django.utils.decorators import method_decorator
from django.views.generic import DetailView
from django.shortcuts import render
from django.urls import reverse
from django.http import HttpResponseRedirect
from core.custom_annotations import group_required
from core.models import Submission
from core.custom_annotations import group_required, in_groups
from core.models import Submission, Feedback
class SubmissionView(DetailView):
......@@ -16,7 +19,35 @@ class SubmissionView(DetailView):
def get_object(self):
obj = Submission.objects.get(slug=self.kwargs['slug'])
if obj.final_feedback is not None:
if in_groups(self.request.user, ('Students', )) and hasattr(obj, 'feedback') and obj.feedback.status == Feedback.ACCEPTED:
obj.seen = True
obj.save()
return obj
def get_context_data(self, **kwargs):
context = super(SubmissionView, self).get_context_data(**kwargs)
context['is_reviewer'] = in_groups(self.request.user, ('Reviewers', ))
return context
@group_required('Reviewers')
def create_feedback_for_submission(request, slug):
submission = Submission.objects.get(slug=slug)
submission.feedback = Feedback()
submission.feedback.of_reviewer = request.user
submission.feedback.of_tutor = request.user
submission.feedback.of_submission = submission
submission.feedback.save()
submission.save()
return HttpResponseRedirect(reverse('FeedbackEdit', args=(submission.feedback.slug,)))
@group_required('Reviewers')
def get_submission_list(request):
context = {
'submission_list': Submission.objects.all(),
}
return render(request, 'core/student_submission_list.html', context)
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.db.models import Count, Q
from django.db.models import Count, Q, Case, When, IntegerField, Value
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.urls import reverse
......@@ -36,10 +36,17 @@ def get_annotated_feedback_count():
Returns:
annotated queryset
"""
return SubmissionType.objects.annotate( # to display only manual
feedback_count=Count('submissions__feedback')).annotate(
submission_count=Count('submissions')
).all().order_by('name')
return SubmissionType.objects\
.annotate( # to display only manual
feedback_count=Count(
Case(
When(Q(submissions__feedback__isnull=False) & Q(submissions__feedback__status=Feedback.ACCEPTED),
then=Value(1)), output_field=IntegerField(),
)
)
).annotate(
submission_count=Count('submissions')
).all().order_by('name')
@group_required('Tutors')
def tutor_view(request):
......@@ -64,8 +71,8 @@ def student_view(request):
def reviewer_view(request):
context = {
'submission_type_list': get_annotated_feedback_count(),
'tutor_list': User.objects.annotate(Count('corrected_submissions')).filter(groups__name='Tutors').order_by('-corrected_submissions'),
'submission_list': Submission.objects.all(),
'tutor_list': User.objects.annotate(Count('corrected_submissions')).filter(groups__name='Tutors')
.order_by('-corrected_submissions__count'),
'feedback_list_manual': Feedback.objects.filter(origin=Feedback.MANUAL),
'feedback_list_empty': Feedback.objects.filter(origin=Feedback.WAS_EMPTY),
'feedback_list_did_not_compile': Feedback.objects.filter(origin=Feedback.DID_NOT_COMPILE),
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment