diff --git a/.gitignore b/.gitignore index 841c5691c58f2971086417327d3e80c249e3e7f0..0d40d6564229494f63c4986d03c836fd3a5eccae 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ tests/report/ db.sqlite3 env-grady/ .DS_Store +reinit.sh diff --git a/core/admin.py b/core/admin.py index 346f48ea40adea8c3e211d2fc733479a72dd08da..56decf2b309241a0ef4d8831d58e91cfd504bcfc 100644 --- a/core/admin.py +++ b/core/admin.py @@ -1,7 +1,6 @@ from django.contrib import admin -from .models import (Feedback, Student, Submission, SubmissionStatus, - SubmissionType) +from .models import (Feedback, Student, Submission, SubmissionType) # Register your models here. @@ -9,4 +8,3 @@ admin.site.register(SubmissionType) admin.site.register(Feedback) admin.site.register(Student) admin.site.register(Submission) -admin.site.register(SubmissionStatus) diff --git a/core/forms.py b/core/forms.py index 033bfabd3233af1a9b5d211970dc8fb21504a383..16f063195a77e8c737c3e729f785f1fd825d780e 100644 --- a/core/forms.py +++ b/core/forms.py @@ -1,13 +1,14 @@ -from django.forms import (CharField, HiddenInput, IntegerField, ModelForm, - NumberInput, Textarea, ValidationError) +from django.forms import ( + CharField, + HiddenInput, + ModelForm, + Textarea, + ValidationError +) from core.models import Feedback -#<textarea cols="40" id="id_text" name="text" rows="10" required=""></textarea> -#<input id="id_score" min="0" name="score" type="number" value="0" required=""> -#form-control - class FeedbackForm(ModelForm): text = CharField( widget=Textarea( diff --git a/core/grady_speak.py b/core/grady_speak.py new file mode 100644 index 0000000000000000000000000000000000000000..9d9a4d6ac78361c81d3cf9e93239987cd223cc39 --- /dev/null +++ b/core/grady_speak.py @@ -0,0 +1,23 @@ +grady_says = [ + "Now let's see if we can improve this with a little water, sir.", + "Won't keep you a moment, sir.", + "Grady, sir. Delbert Grady.", + "Yes, sir.", + "That's right, sir.", + "Why no, sir. I don't believe so.", + "Ah ha, it's coming off now, sir.", + "Why no, sir. I don't believe so.", + "Yes, sir. I have a wife and two daughters, sir.", + "Oh, they're somewhere around. I'm not quite sure at the moment, sir.", + "That's strange, sir. I don't haveany recollection of that at all.", + "I'm sorry to differ with you, sir, but you are the caretaker.", + "You have always been the caretaker, I should know, sir.", + "I've always been here.", + "Indeed, he is, Mr. Torrance. Avery willful boy. ", + "A rather naughty boy, if I may be so bold, sir.", + "Perhaps they need a good talking to, if you don't mind my saying so. Perhaps a bit more.", + "My girls, sir, they didn't care for the Overlook at first.", + "One of them actually stole a packet of matches and tried to burn it down.", + "But I corrected them, sir.", + "And when my wife tried to prevent me from doing my duty... so I corrected her.", +] diff --git a/core/migrations/0033_auto_20170321_2128.py b/core/migrations/0033_auto_20170321_2128.py new file mode 100644 index 0000000000000000000000000000000000000000..7b593a580cedbb0c28639ad7b5260d5b17210cd1 --- /dev/null +++ b/core/migrations/0033_auto_20170321_2128.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-03-21 21:28 +from __future__ import unicode_literals + +import core.models +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0032_auto_20170315_1609'), + ] + + operations = [ + migrations.AddField( + model_name='submissiontype', + name='slug', + field=models.SlugField(default=core.models.random_slug, editable=False, unique=True), + ), + migrations.AlterField( + model_name='feedback', + name='slug', + field=models.SlugField(default=core.models.random_slug, editable=False, unique=True), + ), + migrations.AlterField( + model_name='submission', + name='slug', + field=models.SlugField(default=core.models.random_slug, editable=False, unique=True), + ), + ] diff --git a/core/migrations/0034_auto_20170322_1304.py b/core/migrations/0034_auto_20170322_1304.py new file mode 100644 index 0000000000000000000000000000000000000000..181986053bf9d7380272184c80fabf6f61a28beb --- /dev/null +++ b/core/migrations/0034_auto_20170322_1304.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-03-22 13:04 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0033_auto_20170321_2128'), + ] + + operations = [ + migrations.RemoveField( + model_name='submission', + name='status', + ), + migrations.DeleteModel( + name='SubmissionStatus', + ), + ] diff --git a/core/models.py b/core/models.py index 108bafa8713455de04d775d7f270dd6a35c885b7..89f782d19b89880780225bf1c2901eba3a7490f6 100644 --- a/core/models.py +++ b/core/models.py @@ -2,9 +2,10 @@ from random import sample from string import ascii_lowercase from django.contrib.auth.models import User +from django.db.models import Count from django.db import models -MAX_FEEDBACK_PER_SUBMISSION = 3 +MAX_FEEDBACK_PER_SUBMISSION = 1 SLUG_LENGTH = 16 @@ -16,6 +17,7 @@ class SubmissionType(models.Model): # Fields name = models.CharField(max_length=50) + slug = models.SlugField(editable=False, unique=True, default=random_slug) full_score = models.PositiveIntegerField(default=0) task_description = models.TextField() possible_solution = models.TextField() @@ -50,17 +52,13 @@ class Submission(models.Model): # Fields slug = models.SlugField(editable=False, unique=True, default=random_slug) + + # This indicates that the student has seen his feedback seen = models.BooleanField(default=False) type = models.ForeignKey( SubmissionType, related_name='submissions' ) - status = models.OneToOneField( - 'SubmissionStatus', - null=True, editable=False, - related_name='submission', - on_delete=models.CASCADE - ) text = models.TextField() pre_corrections = models.TextField(blank=True) final_feedback = models.OneToOneField('Feedback', null=True, blank=True) @@ -70,10 +68,6 @@ class Submission(models.Model): verbose_name = "Submission" verbose_name_plural = "Submission Set" - def save(self, *args, **kwargs): - self.status = SubmissionStatus.objects.create() - super(Submission, self).save(*args, **kwargs) - def __str__(self): return "Submission of type '{}' from Student '{}'".format( self.type, @@ -87,7 +81,7 @@ class Submission(models.Model): return self.feedback_counter() == MAX_FEEDBACK_PER_SUBMISSION @classmethod - def assign_tutor(cls, tutor): + def assign_tutor(cls, tutor, type_slug=None): """Assigns a tutor to a submission A submission is not assigned to the specified tutor in the case @@ -103,49 +97,24 @@ class Submission(models.Model): if unfinished: return - ready = cls.objects.filter(status__status=SubmissionStatus.READY) + + ready = cls.objects.annotate(Count('feedback_list')).filter( + feedback_list__count__lt=MAX_FEEDBACK_PER_SUBMISSION + ) + + # we do not want this tutor to correct the same submission twice + if type_slug: + ready = cls.objects.filter(type__slug=type_slug) ready = ready.exclude(feedback_list__of_tutor=tutor) if not ready: return submission = ready[0] - feedback = Feedback() feedback.of_tutor = tutor feedback.of_submission = submission feedback.save() - # set the status to in progress - if submission.feedback_counter() == MAX_FEEDBACK_PER_SUBMISSION: - submission.status.status = SubmissionStatus.LIMIT_REACHED - submission.status.save() - - -class SubmissionStatus(models.Model): - - READY, LIMIT_REACHED = range(2) - STATUS = ( - (READY, 'READY'), - (LIMIT_REACHED, 'LIMIT_REACHED'), - ) - - # Fields - status = models.PositiveIntegerField( - choices=STATUS, - default=READY, - ) - - class Meta: - verbose_name = "Submission Status" - verbose_name_plural = "SubmissionStatus Set" - - def __str__(self): - return "Status is {} - Count is {}".format( - self.status, - self.submission.feedback_counter() - ) - - class Feedback(models.Model): # Fields text = models.TextField() @@ -217,6 +186,7 @@ class Feedback(models.Model): Arguments: user {[type]} -- [description] """ + self.empty = False self.final = True self.of_reviewer = user self.of_submission.final_feedback = self @@ -234,6 +204,3 @@ class Feedback(models.Model): self.of_reviewer = None self.of_submission.final_feedback = None self.save() - - def unassign_tutor(self): - self.of_submission.status.status = SubmissionStatus.READY diff --git a/core/templates/base.html b/core/templates/base.html index 8f2f198ffe3cef06e983e6e6acee725fa173a6d0..c2d42f58368fb3540326ccc6f009180a1375259d 100644 --- a/core/templates/base.html +++ b/core/templates/base.html @@ -37,7 +37,7 @@ {# Navbar contaning: Brand - Title - Page Title <---> (Username - Logout || Login form) #} <nav class="navbar navbar-toggleable-md navbar-light bg-faded"> - <a class="navbar-brand" href="{% url 'index' %}"> +<a class="navbar-brand" href="{% url 'start' %}"> <img src="{% static 'res/brand.png' %}" width="30" height="30" class="d-inline-block align-top" alt=""> Grady </a> diff --git a/core/templates/core/feedback_card.html b/core/templates/core/feedback_card.html index e9468aa00b61d42d6e33b789f094af0562fbe220..5002989f9138e956b15dc1d1c8a2abe33eedd0b4 100644 --- a/core/templates/core/feedback_card.html +++ b/core/templates/core/feedback_card.html @@ -1,11 +1,7 @@ <div class="card my-1"> - <div class="card-header" id="heading{{unique}}"> - <h5 class="mb-0"> - <a data-toggle="collapse" href="#collapse{{unique}}"> - {{ header }} - </a> - </h5> - </div> + <a data-toggle="collapse" href="#collapse{{unique}}"> + <h5 class="card-header">{{ header }}</h5> + </a> <div id="collapse{{unique}}" class="collapse hide" role="tabpanel"> <div class="card-block m-2"> {{ content }} diff --git a/core/templates/core/feedback_form.html b/core/templates/core/feedback_form.html index 45c066bfa6f9689fcc55ed5365fbd99f0e0e29b8..d6c89e74241d9075ea030c56f51ff8292d303dcd 100644 --- a/core/templates/core/feedback_form.html +++ b/core/templates/core/feedback_form.html @@ -1,8 +1,8 @@ {% extends "base.html" %} -{% block nav_title %} Editing Feedback {% endblock nav_title %} +{% block nav_title %} {{ grady_says }} {% endblock nav_title %} -{% block title %} Editing Feedback {% endblock %} +{% block title %} Editing feedback {% endblock %} {% load staticfiles %} @@ -11,11 +11,9 @@ <div class="col my-2"> <div class="card mb-2"> - <div class="card-header" id="SubmissionCard"> - <h4>Student Submission</h4> - </div> + <h4 class="card-header">Student Submission</h4> <div class="card-block"> - <div id="student_text" class="editor-code"> {{ feedback.of_submission.text }} </div> + <div id="student_text" class="editor-code">{{feedback.of_submission.text}}</div> </div> </div> @@ -32,11 +30,9 @@ <div class="col my-2"> <div class="card"> - <div class="card-header" role="tab" id="SubmissionCard"> - <h4>Please provide your feedback here:</h4> - </div> + <h4 class="card-header">Please provide your feedback</h4> <div class="card-block"> - <div class="editor-code" id="tutor_text"> {{ feedback.text }} </div> + <div class="editor-code" id="tutor_text">{{feedback.text}}</div> </div> </div> <form action="{% url 'FeedbackEdit' feedback.slug %}" method="post" id="form1"> @@ -57,6 +53,7 @@ <div class="col-6"> {% if not feedback.final %} <button type="submit" form="form1" class="btn btn-success" name="update" value="Submit">Submit</button> + <button type="submit" form="form1" class="btn btn-success" name="update" value="Next">Next</button> <a href="{% url 'FeedbackDelete' feedback.slug %}" class="btn btn-danger" name="delete" value="Delete">Delete</a> {% else %} <button class="btn btn-success mx-1" value="Submit" disabled>Submit</button> diff --git a/core/templates/core/submission_view.html b/core/templates/core/submission_view.html index 6eee96878bcdd8a0facde74c4adf1fd08f0a5080..bbc91f1e23755081f4fb1167e51ec3022c51ebf3 100644 --- a/core/templates/core/submission_view.html +++ b/core/templates/core/submission_view.html @@ -17,7 +17,7 @@ <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> <li class="list-group-item"><strong class="mr-2">Score: </strong> - {% if submission.feedback.final %} + {% if feedback.final %} <code> {{ feedback.score }} / {{submission.type.full_score}} </code> {% else %} <span class="badge badge-danger">No Feedback</span> @@ -25,33 +25,32 @@ </li> </ul> </div> + <div class="card-footer"> + <a href="{% url 'start' %}" class="btn btn-success">Back</a> + </div> </div> </div> <div class="col-4 my-4"> <div class="card"> <div class="card-block"> - <div class="card-header"> - Your submission - </div> - <div class="editor-code" id="textarea_submission">{{ feedback.of_submission.text }}</div> + <div class="card-header">Your submission</div> + <div class="editor-code" id="textarea_submission">{{submission.text}}</div> </div> </div> </div> - {% if submission.feedback.final %} + {% if feedback.final %} <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</div> <div class="editor-code" id="textarea_feedback">{{ feedback.text }}</div> </div> </div> </div> <script> - var feedback_editor = ace.edit("textarea_feedback"); + var feedback_editor = ace.edit("textarea_feedback"); feedback_editor.setOptions({ readOnly: true, }) diff --git a/core/templates/core/tutor_startpage.html b/core/templates/core/tutor_startpage.html index acd08953ff7d5400878c59870940a482ad4bd36b..1fc1b8431b798da0db7a4c84ed38e81c25b02257 100644 --- a/core/templates/core/tutor_startpage.html +++ b/core/templates/core/tutor_startpage.html @@ -7,7 +7,29 @@ {% block body_block %} <div class="row justify-content-center my-2"> - <div class="col-6"> +<div class="col-3"> + <div class="card"> + <h4 class="card-header">Command Center</h4> + <table class="table"> + <thead> + <th>Name</th> + <th></th> + </thead> + <tbody> + {% for submission_type in submission_type_list %} + <tr> + <td class="align-middle">{{ submission_type }}</td> + <td class="align-middle"><a role="button" class="btn btn-secondary" href="{% url 'CreateFeedbackForType' submission_type.slug %}">Get</a></td> + </tr> + {% endfor %} + </tbody> + </table> + <div class="card-footer text-muted"> + <a role="button" class="btn btn-outline-danger" href="{% url 'CreateFeedback' %}">Just give me anything</a> + </div> + </div> + </div> + <div class="col-5"> <div class="row my-2 page-header"> {% if feedback_list|length == 0 %} <h2>You havn't provided any feedback yet. Sad. Get to work!</h2> @@ -31,7 +53,7 @@ {% for feedback in feedback_list %} <tr> <td> {{ feedback.of_submission.type }} </td> - <td> {{ feedback.score}} </td> + <td> <code> {{ feedback.score }} / {{feedback.of_submission.type.full_score}} </code> </td> <td> {% if feedback.final %} <a class="btn btn-secondary" href="{% url 'FeedbackEdit' feedback.slug %}"> View </a> @@ -46,9 +68,6 @@ </table> {% endif %} </div> - <div class="row"> - <a role="button" class="btn btn-danger btn-xl" href="{% url 'FeedbackCreate' %}">Give me work</a> - </div> </div> </div> diff --git a/core/urls.py b/core/urls.py index 2f5b255813cd30c0cdc6059a4878f984ae4bea22..f218795b8c1ab7343665b6252d89896360fa7f3c 100644 --- a/core/urls.py +++ b/core/urls.py @@ -10,11 +10,13 @@ urlpatterns = [ url(r'^start/$', views.user_home, name='start'), url(r'^finished/$', views.finished, name='end'), + url(r'^feedback/create/$', views.create_feedback, name='CreateFeedback'), + url(r'^feedback/create/(?P<type_slug>\w+)/$', views.create_feedback, name='CreateFeedbackForType'), + 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'^feedback/markfinal/(?P<feedback_slug>\w+)/$', views.markfinal_feedback, name='FeedbackMarkFinal'), url(r'^feedback/markfinal/(?P<feedback_slug>\w+)/undo/$', views.markunfinal_feedback, name='FeedbackMarkNotFinal'), - url(r'^feedback/create/$', views.create_feedback, name='FeedbackCreate'), url(r'^submission/view/(?P<slug>\w+)/$', views.SubmissionView.as_view(), name='SubmissionView'), ] diff --git a/core/views.py b/core/views.py index acf81ca3cf5d0d6860173c1c359bc96ab5150b1e..2a7c1c53174a9578cbf7bc4e2e5130ff780ef084 100644 --- a/core/views.py +++ b/core/views.py @@ -1,6 +1,7 @@ from django.contrib.auth import authenticate, login, logout from django.contrib.auth.decorators import login_required, user_passes_test from django.contrib.auth.models import User +from django.contrib import messages from django.http import Http404, HttpResponse, HttpResponseRedirect from django.shortcuts import render from django.urls import reverse @@ -10,7 +11,9 @@ from django.views.generic.edit import UpdateView from core.forms import FeedbackForm from core.models import Feedback, Submission, SubmissionType +from core.grady_speak import grady_says +from random import choice # Create your views here. @@ -78,6 +81,7 @@ def user_home(request): @group_required('Tutors') def tutor_view(request): context = { + 'submission_type_list': SubmissionType.objects.all(), 'feedback_list': Feedback.objects.filter(of_tutor=request.user), } return render(request, 'core/tutor_startpage.html', context) @@ -102,13 +106,14 @@ def reviewer_view(request): return render(request, 'core/reviewer_startpage.html', context) -@group_required('Tutors', 'Reviewers') -def create_feedback(request): - Submission.assign_tutor(request.user) +@group_required('Tutors') +def create_feedback(request, type_slug=None): + # assign does nothing if tutor has unfinished feedback + Submission.assign_tutor(request.user, type_slug) feedback = Feedback.tutor_unfinished_feedback(request.user) if not feedback: - return HttpResponseRedirect(reverse('end')) - return HttpResponseRedirect('/feedback/edit/%s/' % feedback.slug) + return HttpResponseRedirect(reverse('start')) + return HttpResponseRedirect(reverse('FeedbackEdit', args=(feedback.slug,))) @group_required('Tutors', 'Reviewers') @@ -117,7 +122,6 @@ def delete_feedback(request, feedback_slug): instance = Feedback.objects.get(slug=feedback_slug) if instance.of_tutor != request.user and not in_groups(request.user, ('Reviewers', )): raise Http404 - instance.unassign_tutor() instance.delete() return HttpResponseRedirect(reverse('start')) @@ -166,8 +170,15 @@ class FeedbackEdit(UpdateView): if form.is_valid(): form.instance.empty = False form.save() + if 'Next' in self.request.POST['update']: + return HttpResponseRedirect(reverse('CreateFeedbackForType', args=(form.instance.of_submission.type.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 + class SubmissionView(DetailView): @@ -179,4 +190,8 @@ class SubmissionView(DetailView): return super(SubmissionView, self).dispatch(*args, **kwargs) def get_object(self): - return Submission.objects.get(slug=self.kwargs['slug']) + obj = Submission.objects.get(slug=self.kwargs['slug']) + if obj.final_feedback: + obj.seen = True + obj.save() + return obj