diff --git a/core/fixtures/initial_data.json b/core/fixtures/initial_data.json deleted file mode 100644 index 87a57244ae962bb23f694b0623d2cc369b2a2eef..0000000000000000000000000000000000000000 --- a/core/fixtures/initial_data.json +++ /dev/null @@ -1,44 +0,0 @@ -[ - { - "fields": { - "correct_solution": "Das geht dich gar nichts an.", - "correction_guideline": "Mach das mal so und so.", - "full_score": 10, - "name": "Irgendwas Sortieren" - }, - "model": "core.submissiontype", - "pk": "123" - }, - { - "fields": { - "matrikel_no": 21999999, - "user": 17 - }, - "model": "core.student", - "pk": 1 - }, - { - "fields": { - "final_feedback": null, - "pre_corrections": "Was das System halt so kann.", - "student": 1, - "submission_text": "class Uhrzeit implements Comparable<Uhrzeit> {\r\n private int stunde; // Stundenangabe\r\n private int minute; // Minutenangabe\r\n public Uhrzeit(int stunde, int minute) { // nur sinnvolle Zeitangaben ..\r\n if ( 0 <= stunde && stunde <= 23 && // .. behandeln\r\n 0 <= minute && minute <= 59) {\r\n this.stunde = stunde; // Stundenangabe zuweisen\r\n this.minute = minute; // Minutenangabe zuweisen\r\n } else throw new RuntimeException(\"UngÞltige Zeitangabe!\");\r\n }\r\n\r\n public String toString() { // String-Darstellung in der Form:\r\n return stunde + \" Uhr und \" + minute + \" Minute(n)\"; // <Stundenangabe> Uhr und <Minutenangabe> Minuten\r\n }\r\n}", - "submission_type": "123" - }, - "model": "core.submission", - "pk": 1 - }, - { - "fields": { - "of_reviewer": [ - 16 - ], - "of_submission": 1, - "of_tutor": 15, - "score": 10, - "text": "You did a good job" - }, - "model": "core.feedback", - "pk": 1 - } -] diff --git a/core/fixtures/load_inital.json b/core/fixtures/load_inital.json deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/core/fixtures/testdata-core.json b/core/fixtures/testdata-core.json new file mode 100644 index 0000000000000000000000000000000000000000..ff4385a6ca4c846b14debc333abfeef9452b277d --- /dev/null +++ b/core/fixtures/testdata-core.json @@ -0,0 +1 @@ +[{"model": "core.submissiontype", "pk": 1, "fields": {"name": "Aufgabe 01", "slug": "brezmaphgocfuikw", "full_score": 10, "task_description": "description", "possible_solution": "solution", "correction_guideline": "guideline"}}, {"model": "core.submissiontype", "pk": 2, "fields": {"name": "Aufgabe 02", "slug": "zbjfwldsuhqgxvmn", "full_score": 20, "task_description": "description", "possible_solution": "solution", "correction_guideline": "guideline"}}, {"model": "core.student", "pk": 1, "fields": {"matrikel_no": "12345678", "has_logged_in": false, "name": "Student 01 Vorname und Nachname", "user": 4}}, {"model": "core.student", "pk": 2, "fields": {"matrikel_no": "87654321", "has_logged_in": false, "name": "Student 02 Vorname und Nachname", "user": 5}}, {"model": "core.submission", "pk": 1, "fields": {"slug": "qgleatcwzfxsdnjr", "seen": false, "type": 1, "text": "function generate(timeout){\r\n\r\n\t$('#menu_button_img').attr('src', 'style/menu_blink.gif'); \r\n\r\n\tif(timeout == 0)\t\t\t\t\t\t\t\t\r\n\t\t$('#config_form').attr('action', $('#config_form').attr('action') + '#title'); \t\t\t\t// show directly the question\r\n\telse\r\n\t\ttimeout = 0;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t// disable timeout\r\n\t\r\n\tsetTimeout(function(){ $('#config_form').submit(); }, timeout);\r\n\r\n}", "pre_corrections": "COMPILER", "student": 1}}, {"model": "core.submission", "pk": 2, "fields": {"slug": "mrthqgsloaydjfnc", "seen": false, "type": 2, "text": "function showTextEditor(){\r\n\r\n\t$('.ilc_question_Standard').hide('slow');\r\n\t$('.ilc_question_ml_Standard').hide('slow');\r\n\t$('.text_editor').show('slow');\r\n\t\r\n}\r\n\r\nfunction showConfig(){\r\n\r\n\t$('#config_wrapper').animate(\r\n\t\t{\r\n\t\t\tright: ($('#config_wrapper').css('right') == '0px' ? '-322px' : '0px')\r\n\t\t}, \r\n\t500);\r\n\r\n}", "pre_corrections": "LINKER ERROR", "student": 1}}, {"model": "core.submission", "pk": 3, "fields": {"slug": "hunkgevtcfdobyxw", "seen": false, "type": 2, "text": "$(document).keydown(function(evt){\r\n\r\n\tif(evt.which == 9){\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t// #9 = TAB\r\n\t\tgenerate(0);\r\n\t\tevt.preventDefault();\r\n\t}\r\n\t\r\n});", "pre_corrections": "ALL GOOD", "student": 2}}, {"model": "core.submission", "pk": 4, "fields": {"slug": "gurvbyzxjfmhdiep", "seen": false, "type": 1, "text": "function showTextEditor(){\r\n\r\n\t$('.ilc_question_Standard').hide('slow');\r\n\t$('.ilc_question_ml_Standard').hide('slow');\r\n\t$('.text_editor').show('slow');\r\n\t\r\n}\r\n\r\nfunction showConfig(){\r\n\r\n\t$('#config_wrapper').animate(\r\n\t\t{\r\n\t\t\tright: ($('#config_wrapper').css('right') == '0px' ? '-322px' : '0px')\r\n\t\t}, \r\n\t500);\r\n\r\n}", "pre_corrections": "QUACK", "student": 2}}] \ No newline at end of file diff --git a/core/fixtures/testdata-groups.json b/core/fixtures/testdata-groups.json new file mode 100644 index 0000000000000000000000000000000000000000..e0c5e95cad7a721f77dcb33e8aab4d2e61deac9d --- /dev/null +++ b/core/fixtures/testdata-groups.json @@ -0,0 +1 @@ +[{"model": "auth.group", "pk": 1, "fields": {"name": "Students", "permissions": []}}, {"model": "auth.group", "pk": 2, "fields": {"name": "Reviewers", "permissions": []}}, {"model": "auth.group", "pk": 3, "fields": {"name": "Tutors", "permissions": []}}] \ No newline at end of file diff --git a/core/fixtures/testdata-user.json b/core/fixtures/testdata-user.json new file mode 100644 index 0000000000000000000000000000000000000000..34cf27c2acc807ae2223d5c3dbe77373daa68d61 --- /dev/null +++ b/core/fixtures/testdata-user.json @@ -0,0 +1,98 @@ +[ + { + "fields": { + "date_joined": "2017-06-08T15:07:10.023Z", + "email": "", + "first_name": "", + "groups": [], + "is_active": true, + "is_staff": true, + "is_superuser": true, + "last_login": "2017-06-08T15:07:30.105Z", + "last_name": "", + "password": "pbkdf2_sha256$30000$hirLzfSDQ9f3$CNgyfYKEzxYHkhx3SE5gde3ZeTRsJRYmdr1AMlYIwtg=", + "user_permissions": [], + "username": "doncamillo" + }, + "model": "auth.user", + "pk": 1 + }, + { + "fields": { + "date_joined": "2017-06-08T15:27:53Z", + "email": "", + "first_name": "", + "groups": [ + 2 + ], + "is_active": true, + "is_staff": false, + "is_superuser": false, + "last_login": null, + "last_name": "", + "password": "pbkdf2_sha256$30000$GMuQITImWNbK$i1FftkDWk2fmjHyv7VThV40DzkdfzS8tbnT4uswzfRA=", + "user_permissions": [], + "username": "reviewer" + }, + "model": "auth.user", + "pk": 2 + }, + { + "fields": { + "date_joined": "2017-06-08T15:28:24.320Z", + "email": "", + "first_name": "", + "groups": [], + "is_active": true, + "is_staff": false, + "is_superuser": false, + "last_login": null, + "last_name": "", + "password": "pbkdf2_sha256$30000$JuuBxTeONPuc$Gfbo+MkZmxWJpVVOSf66Sz7Mvz/Of0WFXrosbSeQv24=", + "user_permissions": [], + "username": "tutor" + }, + "model": "auth.user", + "pk": 3 + }, + { + "fields": { + "date_joined": "2017-06-08T15:29:05Z", + "email": "", + "first_name": "", + "groups": [ + 1 + ], + "is_active": true, + "is_staff": false, + "is_superuser": false, + "last_login": null, + "last_name": "", + "password": "pbkdf2_sha256$30000$YaJGFEnSEejd$2dmLmeVFyhEZda/YWwJ/zRMvAbnULYA90IrakJsMYCw=", + "user_permissions": [], + "username": "student01" + }, + "model": "auth.user", + "pk": 4 + }, + { + "fields": { + "date_joined": "2017-06-08T15:31:44Z", + "email": "", + "first_name": "", + "groups": [ + 1 + ], + "is_active": true, + "is_staff": false, + "is_superuser": false, + "last_login": null, + "last_name": "", + "password": "pbkdf2_sha256$30000$SR5pr6OUXScN$NYQiX7J5wgjW8t4gROq46oqRPVWoGd+J1Od4ZWzkN3A=", + "user_permissions": [], + "username": "student02" + }, + "model": "auth.user", + "pk": 5 + } +] diff --git a/core/templates/base.html b/core/templates/base.html index 31fd6609a7d87addae6d51f77553877dde9da6da..139ac9151e59ddbbcdff08db28d5e607d9e1e48f 100644 --- a/core/templates/base.html +++ b/core/templates/base.html @@ -32,16 +32,16 @@ <script src="{% static 'lib/js/dataTables.bootstrap4.min.js' %}"></script> </head> -{# Navbar contaning: Brand - Title - Page Title <---> (Username - Logout || Login form) #} +{# Navbar contaning: Brand - Title - User menu bar <---> (Username - Logout || Login form) #} <nav class="navbar navbar-toggleable navbar-light bg-faded"> <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> - <ul class="nav nav-pills mr-auto"> - <li class="nav-item"><div class="nav-text">{% block nav_title %}{% endblock nav_title %}</div></li> - </ul> + <div class="navbar-nav mr-auto"> + {% block navbar %}{% endblock navbar %} + </div> {% if user.is_authenticated %} <ul class="nav nav-pills"> diff --git a/core/templates/core/feedback_badge.html b/core/templates/core/component/feedback_badge.html similarity index 100% rename from core/templates/core/feedback_badge.html rename to core/templates/core/component/feedback_badge.html diff --git a/core/templates/core/feedback_card.html b/core/templates/core/component/feedback_card.html similarity index 100% rename from core/templates/core/feedback_card.html rename to core/templates/core/component/feedback_card.html diff --git a/core/templates/core/message_box.html b/core/templates/core/component/message_box.html similarity index 100% rename from core/templates/core/message_box.html rename to core/templates/core/component/message_box.html diff --git a/core/templates/core/feedback_form.html b/core/templates/core/feedback_form.html index 6adda232370a524699c2badbcf9a5fc17634a8e8..9f38500d3fee6ff5af8a0e54f0c7d7459590c428 100644 --- a/core/templates/core/feedback_form.html +++ b/core/templates/core/feedback_form.html @@ -1,6 +1,6 @@ {% extends "base.html" %} -{% block nav_title %} {{ grady_says }} {% endblock nav_title %} +{% block navbar %} {{ grady_says }} {% endblock navbar %} {% block title %} Editing feedback {% endblock %} @@ -44,8 +44,7 @@ </div> </div> - {% include "core/feedback_card.html" with unique="1" header="Description" content=feedback.of_submission.type.task_description expanded="hide" %} - {# {% include "core/feedback_card.html" with unique="3" header="Correction Guideline" content=feedback.of_submission.type.correction_guideline expanded="hide" %} #} + {% include "core/component/feedback_card.html" with unique="1" header="Description" content=feedback.of_submission.type.task_description expanded="hide" %} <div class="my-2"> <button type="button" id="collapseAllOpen" class="btn btn-secondary">Open All</button> @@ -116,7 +115,7 @@ </form> </div> {# This is where all the messages pop up #} - {% include "core/message_box.html" %} + {% include "core/component/message_box.html" %} </div> </div> </div> @@ -146,6 +145,8 @@ editor_pre.setOptions({ readOnly: true, showGutter: false, + highlightActiveLine: false, + maxLines: Infinity, }) // we need this one for the sample solution readonly @@ -154,6 +155,8 @@ editor_solution.setOptions({ readOnly: true, showGutter: false, + highlightActiveLine: false, + maxLines: Infinity, }) // we need this one for the student readonly @@ -188,7 +191,6 @@ {% if feedback.status == feedback.ACCEPTED %} editor_tutor.setOptions({ readOnly: true, - }) {% endif %} diff --git a/core/templates/core/index.html b/core/templates/core/index.html index f6f5b833a9eebf08636ff636f9cbf66c068ef392..33df883846dcb4182cd4dbb2706f19ec1ae39847 100644 --- a/core/templates/core/index.html +++ b/core/templates/core/index.html @@ -9,28 +9,8 @@ <div class="col-6"> {# This is where all the messages pop up #} - {% include "core/message_box.html" %} + {% include "core/component/message_box.html" %} - <div class="page-header row my-2"> - <h1>The following tasks have been imported</h1> - </div> - - <div class="row my-2"> - <table class="table"> - <thead> - <th>Type Name</th> - <th>Received Submissions</th> - </thead> - <tbody> - {% for submission_type in submission_types %} - <tr> - <td> {{ submission_type }} </td> - <td> <code>{{ submission_type.submissions.all|length }}</code> </td> - </tr> - {% endfor %} - </tbody> - </table> - </div> </div> </div> diff --git a/core/templates/core/feedback_list.html b/core/templates/core/r/feedback_list.html similarity index 96% rename from core/templates/core/feedback_list.html rename to core/templates/core/r/feedback_list.html index 12b5cf0d3cf5f99ecc710cd075d9d4e560e91455..d5962d95766d4cf3d2a18b41c7800aabd34c9fa3 100644 --- a/core/templates/core/feedback_list.html +++ b/core/templates/core/r/feedback_list.html @@ -20,7 +20,7 @@ {% for feedback in feedback_list %} <tr> <td class="align-middle"> - {% include "core/feedback_badge.html" %} + {% include "core/component/feedback_badge.html" %} </td> <td class="align-middle"> {{ feedback.of_submission.type }} </td> <td class="align-middle"> {{ feedback.of_submission.student }} </td> diff --git a/core/templates/core/r/progress_card.html b/core/templates/core/r/progress_card.html new file mode 100644 index 0000000000000000000000000000000000000000..5ad002c433c13c3b3bc5f96213221483e206e285 --- /dev/null +++ b/core/templates/core/r/progress_card.html @@ -0,0 +1,18 @@ +{# This just gives an overview about what has been done already #} +<div class="card mb-2"> + <h5 class="card-header">Progress</h5> + <table class="table nomargin"> + <thead> + <th>Name</th> + <th>Progress</th> + </thead> + <tbody> + {% for submission_type in submission_type_list %} + <tr> + <td class="align-middle">{{ submission_type }}</td> + <td class="align-middle"><code>{{ submission_type.feedback_count }} / {{submission_type.submission_count}}</code></td> + </tr> + {% endfor %} + </tbody> + </table> +</div> diff --git a/core/templates/core/r/reviewer_base.html b/core/templates/core/r/reviewer_base.html new file mode 100644 index 0000000000000000000000000000000000000000..6ab586329f900be8c86ad062ac30ebdf0775ad8f --- /dev/null +++ b/core/templates/core/r/reviewer_base.html @@ -0,0 +1,54 @@ +{% extends "base.html" %} + +{% load staticfiles %} + +{% 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> +<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 + </a> + <div class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink"> + <a class="dropdown-item" href="{% url 'export' %}">Download Results CSV</a> + </div> +</div> +{% endblock navbar %} + +{% block body_block %} +<div class="row my-3"> + <div class="col-4"> + {% block sidebar %} + {# This just gives an overview about what has been done already #} + {% include "core/r/progress_card.html" %} + + {# Only a list of tutors. Currently no controls available #} + {% include "core/r/tutor_list_card.html" %} + {% endblock sidebar %} + </div> + + <div class="col-8"> + {% block main %} + + {% endblock main %} + </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": [[ 0, 'desc' ], [ 2, 'desc' ]], + "columnDefs": [ + { "orderable": false, "targets": -1 }, + ] + }); + }); +</script> +{% endblock script_block %} diff --git a/core/templates/core/r/reviewer_startpage.html b/core/templates/core/r/reviewer_startpage.html new file mode 100644 index 0000000000000000000000000000000000000000..3fdb2c1f378ee45949b1899d6615d355c0812b84 --- /dev/null +++ b/core/templates/core/r/reviewer_startpage.html @@ -0,0 +1,54 @@ +{% extends 'core/r/reviewer_base.html' %} + +{% load staticfiles %} + +{% block main %} +<div class="card mb-2"> + {% if feedback_list_manual|length == 0 %} + <h5 class="card-header">There is no feedback, yet. Maybe you have to kick some ass?</h5> + {% elif feedback_list_manual|length == 1 %} + <h5 class="card-header">Well, one is better than nothing</h5> + {% else %} + <h5 class="card-header">So far {{feedback_list_manual|length}} contributions were provided</h5> + {% endif %} + <div class="card-block"> + <table id="list-id-0" class="table nomargin"> + <thead> + <tr> + <th>Status</th> + <th>Submission Type</th> + <th>Student</th> + <th>Score</th> + <th>Tutor</th> + <th>Modified</th> + <th></th> + </tr> + </thead> + <tbody> + {% for feedback in feedback_list_manual %} + <tr> + <td class="align-middle fit"> + {% include "core/component/feedback_badge.html" %} + </td> + <td class="align-middle"> {{ feedback.of_submission.type }} </td> + <td class="align-middle"> {{ feedback.of_submission.student }} </td> + <td class="align-middle"> <code> {{ feedback.score }} / {{ feedback.of_submission.type.full_score }} </code> </td> + <td class="align-middle"> {{ feedback.of_tutor}} </td> + <td class="align-middle"> {{ feedback.modified | date:"m/d/y H:i"}} </td> + <td class="align-middle"> + <a href="{% url 'FeedbackEdit' feedback.slug %}" class="btn btn-outline-primary mb-1" name="edit" value="View">View</a> + <a href="{% url 'FeedbackDelete' feedback.slug %}" class="btn btn-outline-danger mb-1" name="delete" value="Delete">Delete</a> + </td> + </tr> + {% endfor %} + </tbody> + </table> + </div> +</div> + +{# This is card for empty feedback for the sake of completeness #} +{% include "core/r/feedback_list.html" with expanded="show" header="Did not compile feedback" unique="2" feedback_list=feedback_list_did_not_compile %} +{% include "core/r/feedback_list.html" with expanded="show" header="Could not link feedback" unique="3" feedback_list=feedback_list_could_not_link %} +{% include "core/r/feedback_list.html" with expanded="hide" header="Empty feedback" unique="1" feedback_list=feedback_list_empty %} + +{% endblock main %} diff --git a/core/templates/core/r/single_submission.html b/core/templates/core/r/single_submission.html new file mode 100644 index 0000000000000000000000000000000000000000..09d388b3849725e3530c8e7dce92124626a0c654 --- /dev/null +++ b/core/templates/core/r/single_submission.html @@ -0,0 +1,88 @@ +{% extends "core/r/reviewer_base.html" %} + +{% block body_block %} + +{% with submission.feedback as feedback %} +<div class="row justify-content-center"> + <div class="col-3 my-4"> + <div class="card"> + <div class="card-block"> + <div class="card-header"> + Student Submission + </div> + <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> + <li class="list-group-item"> + <strong class="mr-2">Status: </strong> {% include "core/component/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> + <li class="list-group-item"><strong class="mr-2">Score: </strong> + {% if feedback %} + <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 %} + </li> + </ul> + </div> + <div class="card-footer"> + <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> + </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">{{submission.text}}</div> + </div> + </div> + </div> + + {% if feedback %} + <div class="col-4 my-4"> + <div class="card"> + <div class="card-block"> + <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> + </div> + <script> + var feedback_editor = ace.edit("textarea_feedback"); + feedback_editor.setOptions({ + readOnly: true, + }) + </script> + {% endif %} +</div> +{% endwith %} +{% endblock body_block %} + + +{% block script_block %} +<script> + var submission_editor = ace.edit("textarea_submission"); + submission_editor.setOptions({ + readOnly: true, + }) +</script> +{% endblock script_block %} diff --git a/core/templates/core/r/student_submission_list.html b/core/templates/core/r/student_submission_list.html new file mode 100644 index 0000000000000000000000000000000000000000..2b28a8d814eb8acb8d6be0c38aa7801ac6a7346d --- /dev/null +++ b/core/templates/core/r/student_submission_list.html @@ -0,0 +1,58 @@ +{% extends 'core/r/reviewer_base.html' %} + +{% load staticfiles %} + +{% block main %} + +<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 'SubmissionViewReviewer' 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> + +{% endblock main %} diff --git a/core/templates/core/r/tutor_list_card.html b/core/templates/core/r/tutor_list_card.html new file mode 100644 index 0000000000000000000000000000000000000000..4fac5db444705dd4a9a48af36cef6f9d9b98bb74 --- /dev/null +++ b/core/templates/core/r/tutor_list_card.html @@ -0,0 +1,19 @@ +<div class="card mb-2"> + <h5 class="card-header">Tutor overview</h5> + <div class="card-block"> + <table class="table nomargin"> + <thead> + <th>Tutor</th> + <th># of feedbacks</th> + </thead> + {% for tutor in tutor_list %} + <tbody> + <tr> + <td>{{tutor.username}}</td> + <td><code>{{tutor.corrected_submissions__count}}</code></td> + </tr> + </tbody> + {% endfor %} + </table> + </div> +</div> diff --git a/core/templates/core/reviewer_startpage.html b/core/templates/core/reviewer_startpage.html deleted file mode 100644 index a9ede5adbb4130bd87e6cada6820da8f0f683585..0000000000000000000000000000000000000000 --- a/core/templates/core/reviewer_startpage.html +++ /dev/null @@ -1,125 +0,0 @@ -{% extends 'base.html' %} - -{% load staticfiles %} - -{% block nav_title %} Ready for an exam, captain? {% endblock nav_title %} - -{% block body_block %} - -<div class="row my-3"> - <div class="col-4 nopadding-right"> - - {# This just gives an overview about what has been done already #} - <div class="card mb-2"> - <h5 class="card-header">Progress</h5> - <table class="table nomargin"> - <thead> - <th>Name</th> - <th>Progress</th> - </thead> - <tbody> - {% for submission_type in submission_type_list %} - <tr> - <td class="align-middle">{{ submission_type }}</td> - <td class="align-middle"><code>{{ submission_type.feedback_count }} / {{submission_type.submission_count}}</code></td> - </tr> - {% endfor %} - </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> - - {# Only a list of tutors. Currently no controls available #} - <div class="card mb-2"> - <h5 class="card-header">Tutor overview</h5> - <div class="card-block"> - <table class="table nomargin"> - <thead> - <th>Tutor</th> - <th># of feedbacks</th> - </thead> - {% for tutor in tutor_list %} - <tbody> - <tr> - <td>{{tutor.username}}</td> - <td><code>{{tutor.corrected_submissions__count}}</code></td> - </tr> - </tbody> - {% endfor %} - </table> - </div> - </div> - </div> - - <div class="col-8"> - <div class="card mb-2"> - {% if feedback_list_manual|length == 0 %} - <h5 class="card-header">There is no feedback, yet. Maybe you have to kick some ass?</h5> - {% elif feedback_list_manual|length == 1 %} - <h5 class="card-header">Well, one is better than nothing</h5> - {% else %} - <h5 class="card-header">So far {{feedback_list_manual|length}} contributions were provided</h5> - {% endif %} - <div class="card-block"> - <table id="list-id-0" class="table nomargin"> - <thead> - <tr> - <th>Status</th> - <th>Submission Type</th> - <th>Student</th> - <th>Score</th> - <th>Tutor</th> - <th>Modified</th> - <th></th> - </tr> - </thead> - <tbody> - {% for feedback in feedback_list_manual %} - <tr> - <td class="align-middle fit"> - {% include "core/feedback_badge.html" %} - </td> - <td class="align-middle"> {{ feedback.of_submission.type }} </td> - <td class="align-middle"> {{ feedback.of_submission.student }} </td> - <td class="align-middle"> <code> {{ feedback.score }} / {{ feedback.of_submission.type.full_score }} </code> </td> - <td class="align-middle"> {{ feedback.of_tutor}} </td> - <td class="align-middle"> {{ feedback.modified | date:"m/d/y H:i"}} </td> - <td class="align-middle"> - <a href="{% url 'FeedbackEdit' feedback.slug %}" class="btn btn-outline-primary mb-1" name="edit" value="View">View</a> - <a href="{% url 'FeedbackDelete' feedback.slug %}" class="btn btn-outline-danger mb-1" name="delete" value="Delete">Delete</a> - </td> - </tr> - {% endfor %} - </tbody> - </table> - </div> - </div> - - {# This is card for empty feedback for the sake of completeness #} - {% include "core/feedback_list.html" with expanded="show" header="Did not compile feedback" unique="2" feedback_list=feedback_list_did_not_compile %} - {% include "core/feedback_list.html" with expanded="show" header="Could not link feedback" unique="3" feedback_list=feedback_list_could_not_link %} - {% include "core/feedback_list.html" with expanded="hide" header="Empty feedback" unique="1" feedback_list=feedback_list_empty %} - </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": [[ 0, 'desc' ], [ 2, 'desc' ]], - "columnDefs": [ - { "orderable": false, "targets": -1 }, - ] - }); - }); -</script> -{% endblock script_block %} diff --git a/core/templates/core/submission_view.html b/core/templates/core/s/single_submission.html similarity index 99% rename from core/templates/core/submission_view.html rename to core/templates/core/s/single_submission.html index 40a4d19b33a9827525c5f0628408d422ad0f538b..0a018c7ce279971284553026f7157c3f5a547929 100644 --- a/core/templates/core/submission_view.html +++ b/core/templates/core/s/single_submission.html @@ -2,7 +2,6 @@ {% block nav_title %} Student Exam View {% endblock nav_title %} - {% block body_block %} {% with submission.feedback as feedback %} diff --git a/core/templates/core/student_startpage.html b/core/templates/core/s/student_startpage.html similarity index 91% rename from core/templates/core/student_startpage.html rename to core/templates/core/s/student_startpage.html index baf9e7fd57ca6172cbb0cec26523cc43afeedcea..ca5a7f723c14abdcf7244beeedf3af11925638dc 100644 --- a/core/templates/core/student_startpage.html +++ b/core/templates/core/s/student_startpage.html @@ -2,7 +2,7 @@ {% load staticfiles %} -{% block nav_title %} Student Exam View {% endblock nav_title %} +{% block navbar %} Student Exam View {% endblock navbar %} {% block body_block %} @@ -12,7 +12,6 @@ <h2>Hello {{ student.student }}</h2> </div> - <div class="row my-2"> <table class="table"> <thead> @@ -39,7 +38,7 @@ {% endif %} {% endwith %} </td> - <td class="align-middle"><a class="btn btn-primary" href="{% url 'SubmissionView' submission.slug %}">View</a></td> + <td class="align-middle"><a class="btn btn-primary" href="{% url 'SubmissionViewStudent' submission.slug %}">View</a></td> </tr> {% endfor %} </tbody> diff --git a/core/templates/core/student_submission_list.html b/core/templates/core/student_submission_list.html deleted file mode 100644 index 5bb703e398f5a49d4d1f0684141e984bc4dc9654..0000000000000000000000000000000000000000 --- a/core/templates/core/student_submission_list.html +++ /dev/null @@ -1,83 +0,0 @@ -{% 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 %} diff --git a/core/templates/core/tutor_startpage.html b/core/templates/core/t/tutor_startpage.html similarity index 95% rename from core/templates/core/tutor_startpage.html rename to core/templates/core/t/tutor_startpage.html index 0347a72718b006d04e1b22f53d8de44c2e95fe9e..f69294223bf82f928fba1bb9bb6c08e7f048a0bc 100644 --- a/core/templates/core/tutor_startpage.html +++ b/core/templates/core/t/tutor_startpage.html @@ -2,7 +2,7 @@ {% load staticfiles %} -{% block nav_title %} Ready for an exam, commander? {% endblock nav_title %} +{% block navbar %} Ready for an exam, commander? {% endblock navbar %} {% block body_block %} @@ -61,7 +61,7 @@ {% endif %} {# This is where all the messages pop up #} - {% include "core/message_box.html" %} + {% include "core/component/message_box.html" %} </div> <div class="col-6"> @@ -81,7 +81,7 @@ {% for feedback in feedback_list %} <tr> <td> - {% include "core/feedback_badge.html" %} + {% include "core/component/feedback_badge.html" %} </td> <td> {{ feedback.of_submission.type }} </td> <td> <code> {{ feedback.score }} / {{feedback.of_submission.type.full_score}} </code> </td> diff --git a/core/urls.py b/core/urls.py index d77098cd01e54f6103db85eacd57f59d565d22d7..42c5917be07fa9905c8f360a0ffe5ec3b7ff47ab 100644 --- a/core/urls.py +++ b/core/urls.py @@ -11,14 +11,15 @@ urlpatterns = [ 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'^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'^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'), + url(r'^r/submission/download/(?P<slug>\w+)/$', views.download_submissions, name='download_submissions'), + + url(r'^s/submission/view/(?P<slug>\w+)/$', views.SubmissionViewStudent.as_view(), name='SubmissionViewStudent'), url(r'^csv/$', views.export_csv, name='export') ] diff --git a/core/views/feedback.py b/core/views/feedback.py index a7c00be7a1e507d63f0b2dc62da7c25c9bf2b39b..5c60bc4e254c638feede522bbc7906ff1b613861 100644 --- a/core/views/feedback.py +++ b/core/views/feedback.py @@ -22,13 +22,33 @@ def create_feedback(request, type_slug=None): messages.info(request, "No more submissions of type '%s' available." % SubmissionType.objects.get(slug=type_slug)) else: - messages.success(request, "Well done! There is no more work to do.") + messages.success( + request, "Well done! There is no more work to do.") return HttpResponseRedirect(reverse('start')) elif not assigned: - messages.info(request, "You have got unfinished business. Please finish this task first.") + messages.info( + request, "You have got unfinished business. Please finish this task first.") return HttpResponseRedirect(reverse('FeedbackEdit', args=(feedback.slug,))) +@group_required('Reviewers') +def create_feedback_for_submission(request, slug): + + submission = Submission.objects.get(slug=slug) + + if not hasattr(submission, 'feedback'): + 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 delete_feedback(request, feedback_slug): """ Hook to ensure object is owned by request.user. """ @@ -57,7 +77,8 @@ class FeedbackEdit(UpdateView): 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!") + messages.error( + self.request, "Get your hands of somebody else's feedback!") raise Http404 def form_valid(self, form): @@ -96,6 +117,7 @@ class FeedbackEdit(UpdateView): context['grady_says'] = choice(grady_says) return context + @group_required('Reviewers', 'Tutors') def download_submissions(request, slug): submission = Submission.objects.get(slug=slug) @@ -105,7 +127,8 @@ def download_submissions(request, slug): return HttpResponseRedirect(reverse('start')) response = HttpResponse(content_type='text/plain') - response['Content-Disposition'] = 'attachment; filename="%s_%s.c"' % (submission.type.name, submission.slug) + response['Content-Disposition'] = 'attachment; filename="%s_%s.c"' % ( + submission.type.name, submission.slug) response.write(submission.text) return response diff --git a/core/views/submission.py b/core/views/submission.py index c5522f188118d7396a337becdda039017e7be4df..99d4b13c3fb70592a19c7c503bf1497d8c9ecd52 100644 --- a/core/views/submission.py +++ b/core/views/submission.py @@ -1,21 +1,21 @@ 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, in_groups from core.models import Submission, Feedback +from .user_startpages import get_annotated_feedback_count, get_annotated_tutor_list -class SubmissionView(DetailView): - template_name = 'core/submission_view.html' +class SubmissionViewStudent(DetailView): + + template_name = 'core/s/single_submission.html' model = Submission - @method_decorator(group_required('Reviewers', 'Students')) + @method_decorator(group_required('Students',)) def dispatch(self, *args, **kwargs): - return super(SubmissionView, self).dispatch(*args, **kwargs) + return super(SubmissionViewStudent, self).dispatch(*args, **kwargs) def get_object(self): obj = Submission.objects.get(slug=self.kwargs['slug']) @@ -24,30 +24,25 @@ class SubmissionView(DetailView): 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): +class SubmissionViewReviewer(DetailView): - submission = Submission.objects.get(slug=slug) + template_name = 'core/r/single_submission.html' + model = Submission - submission.feedback = Feedback() - submission.feedback.of_reviewer = request.user - submission.feedback.of_tutor = request.user - submission.feedback.of_submission = submission + @method_decorator(group_required('Reviewers',)) + def dispatch(self, *args, **kwargs): + return super(SubmissionViewReviewer, self).dispatch(*args, **kwargs) - submission.feedback.save() - submission.save() + def get_object(self): + return Submission.objects.get(slug=self.kwargs['slug']) - return HttpResponseRedirect(reverse('FeedbackEdit', args=(submission.feedback.slug,))) @group_required('Reviewers') def get_submission_list(request): context = { 'submission_list': Submission.objects.all(), + 'submission_type_list': get_annotated_feedback_count(), + 'tutor_list': get_annotated_tutor_list(), } - return render(request, 'core/student_submission_list.html', context) - + return render(request, 'core/r/student_submission_list.html', context) diff --git a/core/views/user_startpages.py b/core/views/user_startpages.py index f4a4dc64f13def51e39284746c278af55ee6e3f2..1a0e4f4a2d273ca5aec3b971579466d172b54b8a 100644 --- a/core/views/user_startpages.py +++ b/core/views/user_startpages.py @@ -48,6 +48,10 @@ def get_annotated_feedback_count(): submission_count=Count('submissions') ).all().order_by('name') +def get_annotated_tutor_list(): + return User.objects.annotate(Count('corrected_submissions')).filter(groups__name='Tutors') \ + .order_by('-corrected_submissions__count') + @group_required('Tutors') def tutor_view(request): context = { @@ -55,7 +59,7 @@ def tutor_view(request): 'feedback_list': Feedback.objects.filter(of_tutor=request.user), 'feedback_open_list': Feedback.objects.filter(Q(status=Feedback.OPEN) & ~Q(of_tutor=request.user)), } - return render(request, 'core/tutor_startpage.html', context) + return render(request, 'core/t/tutor_startpage.html', context) @group_required('Students') @@ -64,18 +68,17 @@ def student_view(request): 'student': request.user, 'submission_list': Submission.objects.filter(student__user=request.user) } - return render(request, 'core/student_startpage.html', context) + return render(request, 'core/s/student_startpage.html', context) @group_required('Reviewers') 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__count'), + 'tutor_list': get_annotated_tutor_list(), '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), 'feedback_list_could_not_link': Feedback.objects.filter(origin=Feedback.COULD_NOT_LINK), } - return render(request, 'core/reviewer_startpage.html', context) + return render(request, 'core/r/reviewer_startpage.html', context)