Add grading api endpoint
This commit is contained in:
parent
9580d79559
commit
baf5801b6a
|
|
@ -8,11 +8,15 @@ from django.urls import include, path, re_path
|
||||||
from django.views import defaults as default_views
|
from django.views import defaults as default_views
|
||||||
from grapple import urls as grapple_urls
|
from grapple import urls as grapple_urls
|
||||||
from ratelimit.exceptions import Ratelimited
|
from ratelimit.exceptions import Ratelimited
|
||||||
|
from wagtail import urls as wagtail_urls
|
||||||
|
from wagtail.admin import urls as wagtailadmin_urls
|
||||||
|
from wagtail.documents import urls as wagtaildocs_urls
|
||||||
|
|
||||||
from vbv_lernwelt.assignment.views import (
|
from vbv_lernwelt.assignment.views import (
|
||||||
request_assignment_completion,
|
request_assignment_completion,
|
||||||
request_assignment_completion_for_user,
|
request_assignment_completion_for_user,
|
||||||
update_assignment_input,
|
upsert_user_assignment_completion,
|
||||||
|
grade_assignment_completion,
|
||||||
)
|
)
|
||||||
from vbv_lernwelt.core.middleware.auth import django_view_authentication_exempt
|
from vbv_lernwelt.core.middleware.auth import django_view_authentication_exempt
|
||||||
from vbv_lernwelt.core.views import (
|
from vbv_lernwelt.core.views import (
|
||||||
|
|
@ -43,9 +47,6 @@ from vbv_lernwelt.feedback.views import (
|
||||||
get_feedback_for_circle,
|
get_feedback_for_circle,
|
||||||
)
|
)
|
||||||
from vbv_lernwelt.notify.views import email_notification_settings
|
from vbv_lernwelt.notify.views import email_notification_settings
|
||||||
from wagtail import urls as wagtail_urls
|
|
||||||
from wagtail.admin import urls as wagtailadmin_urls
|
|
||||||
from wagtail.documents import urls as wagtaildocs_urls
|
|
||||||
|
|
||||||
|
|
||||||
def raise_example_error(request):
|
def raise_example_error(request):
|
||||||
|
|
@ -99,8 +100,10 @@ urlpatterns = [
|
||||||
name="request_course_completion_for_user"),
|
name="request_course_completion_for_user"),
|
||||||
|
|
||||||
# assignment
|
# assignment
|
||||||
path(r"api/assignment/update/", update_assignment_input,
|
path(r"api/assignment/upsert/", upsert_user_assignment_completion,
|
||||||
name="update_assignment_input"),
|
name="upsert_user_assignment_completion"),
|
||||||
|
path(r"api/assignment/grade/", grade_assignment_completion,
|
||||||
|
name="grade_assignment_completion"),
|
||||||
path(r"api/assignment/<int:assignment_id>/<int:course_session_id>/",
|
path(r"api/assignment/<int:assignment_id>/<int:course_session_id>/",
|
||||||
request_assignment_completion,
|
request_assignment_completion,
|
||||||
name="request_assignment_completion"),
|
name="request_assignment_completion"),
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ def update_assignment_completion(
|
||||||
it can also contain "trainer_input" when the trainer has entered grading data
|
it can also contain "trainer_input" when the trainer has entered grading data
|
||||||
{
|
{
|
||||||
"<user_text_input:uuid>": {
|
"<user_text_input:uuid>": {
|
||||||
"trainer_data": {"points": 4, "text": "Gute Antwort"}
|
"expert_data": {"points": 4, "text": "Gute Antwort"}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
:param copy_task_data: if true, the task data will be copied to the completion data
|
:param copy_task_data: if true, the task data will be copied to the completion data
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,13 @@
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
from django.utils import timezone
|
||||||
from rest_framework.test import APITestCase
|
from rest_framework.test import APITestCase
|
||||||
|
|
||||||
from vbv_lernwelt.assignment.models import Assignment, AssignmentCompletion
|
from vbv_lernwelt.assignment.models import (
|
||||||
|
Assignment,
|
||||||
|
AssignmentCompletion,
|
||||||
|
AssignmentCompletionAuditLog,
|
||||||
|
)
|
||||||
from vbv_lernwelt.core.create_default_users import create_default_users
|
from vbv_lernwelt.core.create_default_users import create_default_users
|
||||||
from vbv_lernwelt.core.models import User
|
from vbv_lernwelt.core.models import User
|
||||||
from vbv_lernwelt.core.utils import find_first
|
from vbv_lernwelt.core.utils import find_first
|
||||||
|
|
@ -11,7 +16,7 @@ from vbv_lernwelt.course.creators.test_course import create_test_course
|
||||||
from vbv_lernwelt.course.models import CourseSession, CourseSessionUser
|
from vbv_lernwelt.course.models import CourseSession, CourseSessionUser
|
||||||
|
|
||||||
|
|
||||||
class AssignmentApiStudentTestCase(APITestCase):
|
class AssignmentApiTestCase(APITestCase):
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
create_default_users()
|
create_default_users()
|
||||||
create_test_course(include_vv=False)
|
create_test_course(include_vv=False)
|
||||||
|
|
@ -23,15 +28,20 @@ class AssignmentApiStudentTestCase(APITestCase):
|
||||||
course_id=COURSE_TEST_ID,
|
course_id=COURSE_TEST_ID,
|
||||||
title="Test Lehrgang Session",
|
title="Test Lehrgang Session",
|
||||||
)
|
)
|
||||||
self.user = User.objects.get(username="student")
|
self.student = User.objects.get(username="student")
|
||||||
csu = CourseSessionUser.objects.create(
|
self.student_csu = CourseSessionUser.objects.create(
|
||||||
course_session=self.cs,
|
course_session=self.cs,
|
||||||
user=self.user,
|
user=self.student,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_can_updateAssignmentCompletion_asStudent(self):
|
self.expert = User.objects.get(username="admin")
|
||||||
|
self.expert_csu = CourseSessionUser.objects.create(
|
||||||
|
course_session=self.cs, user=self.expert, role="EXPERT"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_student_can_upsertAssignmentCompletion(self):
|
||||||
self.client.login(username="student", password="test")
|
self.client.login(username="student", password="test")
|
||||||
url = f"/api/assignment/update/"
|
url = f"/api/assignment/upsert/"
|
||||||
|
|
||||||
user_text_input = find_first(
|
user_text_input = find_first(
|
||||||
self.assignment_subtasks, pred=lambda x: x["type"] == "user_text_input"
|
self.assignment_subtasks, pred=lambda x: x["type"] == "user_text_input"
|
||||||
|
|
@ -53,7 +63,7 @@ class AssignmentApiStudentTestCase(APITestCase):
|
||||||
print(json.dumps(response.json(), indent=2))
|
print(json.dumps(response.json(), indent=2))
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(response_json["assignment_user"], self.user.id)
|
self.assertEqual(response_json["assignment_user"], self.student.id)
|
||||||
self.assertEqual(response_json["assignment"], self.assignment.id)
|
self.assertEqual(response_json["assignment"], self.assignment.id)
|
||||||
self.assertEqual(response_json["completion_status"], "in_progress")
|
self.assertEqual(response_json["completion_status"], "in_progress")
|
||||||
self.assertDictEqual(
|
self.assertDictEqual(
|
||||||
|
|
@ -64,7 +74,7 @@ class AssignmentApiStudentTestCase(APITestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
db_entry = AssignmentCompletion.objects.get(
|
db_entry = AssignmentCompletion.objects.get(
|
||||||
assignment_user=self.user,
|
assignment_user=self.student,
|
||||||
course_session_id=self.cs.id,
|
course_session_id=self.cs.id,
|
||||||
assignment_id=self.assignment.id,
|
assignment_id=self.assignment.id,
|
||||||
)
|
)
|
||||||
|
|
@ -107,7 +117,7 @@ class AssignmentApiStudentTestCase(APITestCase):
|
||||||
print(json.dumps(response.json(), indent=2))
|
print(json.dumps(response.json(), indent=2))
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(response_json["assignment_user"], self.user.id)
|
self.assertEqual(response_json["assignment_user"], self.student.id)
|
||||||
self.assertEqual(response_json["assignment"], self.assignment.id)
|
self.assertEqual(response_json["assignment"], self.assignment.id)
|
||||||
self.assertEqual(response_json["completion_status"], "submitted")
|
self.assertEqual(response_json["completion_status"], "submitted")
|
||||||
self.assertDictEqual(
|
self.assertDictEqual(
|
||||||
|
|
@ -134,3 +144,153 @@ class AssignmentApiStudentTestCase(APITestCase):
|
||||||
print(json.dumps(response.json(), indent=2))
|
print(json.dumps(response.json(), indent=2))
|
||||||
self.assertEqual(response.status_code, 404)
|
self.assertEqual(response.status_code, 404)
|
||||||
self.assertTrue("Cannot update completion status" in str(response_json))
|
self.assertTrue("Cannot update completion status" in str(response_json))
|
||||||
|
|
||||||
|
def test_expert_can_gradeAssignmentCompletion(self):
|
||||||
|
# setup AssignmentCompletion
|
||||||
|
subtasks = self.assignment.filter_user_subtasks(
|
||||||
|
subtask_types=["user_text_input"]
|
||||||
|
)
|
||||||
|
user_text_input = find_first(
|
||||||
|
subtasks,
|
||||||
|
pred=lambda x: (value := x.get("value"))
|
||||||
|
and value.get("text", "").startswith(
|
||||||
|
"Gibt es zusätzliche Deckungen, die du der Person empfehlen würdest?"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
ac = AssignmentCompletion.objects.create(
|
||||||
|
assignment_user=self.student,
|
||||||
|
assignment=self.assignment,
|
||||||
|
course_session=self.cs,
|
||||||
|
completion_status="submitted",
|
||||||
|
submitted_at=timezone.now(),
|
||||||
|
completion_data={
|
||||||
|
user_text_input["id"]: {
|
||||||
|
"user_data": {"text": "Ich würde nichts weiteres empfehlen."}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
# make api call
|
||||||
|
self.client.login(username="admin", password="test")
|
||||||
|
url = f"/api/assignment/grade/"
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
"assignment_id": self.assignment.id,
|
||||||
|
"assignment_user_id": self.student.id,
|
||||||
|
"course_session_id": self.cs.id,
|
||||||
|
"completion_status": "grading_in_progress",
|
||||||
|
"completion_data": {
|
||||||
|
user_text_input["id"]: {
|
||||||
|
"expert_data": {"points": 1, "comment": "Gut gemacht!"}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
format="json",
|
||||||
|
)
|
||||||
|
response_json = response.json()
|
||||||
|
print(json.dumps(response.json(), indent=2))
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(response_json["assignment_user"], self.student.id)
|
||||||
|
self.assertEqual(response_json["assignment"], self.assignment.id)
|
||||||
|
self.assertEqual(response_json["completion_status"], "grading_in_progress")
|
||||||
|
self.assertDictEqual(
|
||||||
|
response_json["completion_data"],
|
||||||
|
{
|
||||||
|
user_text_input["id"]: {
|
||||||
|
"user_data": {"text": "Ich würde nichts weiteres empfehlen."},
|
||||||
|
"expert_data": {"points": 1, "comment": "Gut gemacht!"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
db_entry = AssignmentCompletion.objects.get(
|
||||||
|
assignment_user=self.student,
|
||||||
|
course_session_id=self.cs.id,
|
||||||
|
assignment_id=self.assignment.id,
|
||||||
|
)
|
||||||
|
self.assertEqual(db_entry.completion_status, "grading_in_progress")
|
||||||
|
self.assertDictEqual(
|
||||||
|
db_entry.completion_data,
|
||||||
|
{
|
||||||
|
user_text_input["id"]: {
|
||||||
|
"user_data": {"text": "Ich würde nichts weiteres empfehlen."},
|
||||||
|
"expert_data": {"points": 1, "comment": "Gut gemacht!"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
# finish grading
|
||||||
|
response = self.client.post(
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
"assignment_id": self.assignment.id,
|
||||||
|
"assignment_user_id": self.student.id,
|
||||||
|
"course_session_id": self.cs.id,
|
||||||
|
"completion_status": "graded",
|
||||||
|
"completion_data": {
|
||||||
|
user_text_input["id"]: {
|
||||||
|
"expert_data": {"points": 1, "comment": "Gut gemacht!"}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
format="json",
|
||||||
|
)
|
||||||
|
response_json = response.json()
|
||||||
|
print(json.dumps(response.json(), indent=2))
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(response_json["assignment_user"], self.student.id)
|
||||||
|
self.assertEqual(response_json["assignment"], self.assignment.id)
|
||||||
|
self.assertEqual(response_json["completion_status"], "graded")
|
||||||
|
self.assertDictEqual(
|
||||||
|
response_json["completion_data"],
|
||||||
|
{
|
||||||
|
user_text_input["id"]: {
|
||||||
|
"user_data": {"text": "Ich würde nichts weiteres empfehlen."},
|
||||||
|
"expert_data": {"points": 1, "comment": "Gut gemacht!"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
db_entry = AssignmentCompletion.objects.get(
|
||||||
|
assignment_user=self.student,
|
||||||
|
course_session_id=self.cs.id,
|
||||||
|
assignment_id=self.assignment.id,
|
||||||
|
)
|
||||||
|
self.assertEqual(db_entry.completion_status, "graded")
|
||||||
|
self.assertDictEqual(
|
||||||
|
db_entry.completion_data,
|
||||||
|
{
|
||||||
|
user_text_input["id"]: {
|
||||||
|
"user_data": {"text": "Ich würde nichts weiteres empfehlen."},
|
||||||
|
"expert_data": {"points": 1, "comment": "Gut gemacht!"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
# `graded` will create a new AssignmentCompletionAuditLog
|
||||||
|
acl = AssignmentCompletionAuditLog.objects.get(
|
||||||
|
assignment_user=self.student,
|
||||||
|
course_session_id=self.cs.id,
|
||||||
|
assignment_id=self.assignment.id,
|
||||||
|
completion_status="graded",
|
||||||
|
)
|
||||||
|
self.maxDiff = None
|
||||||
|
self.assertDictEqual(
|
||||||
|
acl.completion_data,
|
||||||
|
{
|
||||||
|
user_text_input["id"]: {
|
||||||
|
"id": user_text_input["id"],
|
||||||
|
"type": "user_text_input",
|
||||||
|
"value": {
|
||||||
|
"text": "Gibt es zusätzliche Deckungen, die du der Person empfehlen würdest? Begründe deine Empfehlung",
|
||||||
|
},
|
||||||
|
"user_data": {"text": "Ich würde nichts weiteres empfehlen."},
|
||||||
|
"expert_data": {"points": 1, "comment": "Gut gemacht!"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -326,7 +326,7 @@ class UpdateAssignmentCompletionTestCase(TestCase):
|
||||||
course_session=self.course_session,
|
course_session=self.course_session,
|
||||||
completion_data={
|
completion_data={
|
||||||
user_text_input["id"]: {
|
user_text_input["id"]: {
|
||||||
"trainer_data": {"points": 1, "comment": "Gut gemacht!"}
|
"expert_data": {"points": 1, "comment": "Gut gemacht!"}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
completion_status="grading_in_progress",
|
completion_status="grading_in_progress",
|
||||||
|
|
@ -342,7 +342,7 @@ class UpdateAssignmentCompletionTestCase(TestCase):
|
||||||
self.assertEqual(ac.completion_status, "grading_in_progress")
|
self.assertEqual(ac.completion_status, "grading_in_progress")
|
||||||
user_input = ac.completion_data[user_text_input["id"]]
|
user_input = ac.completion_data[user_text_input["id"]]
|
||||||
self.assertDictEqual(
|
self.assertDictEqual(
|
||||||
user_input["trainer_data"], {"points": 1, "comment": "Gut gemacht!"}
|
user_input["expert_data"], {"points": 1, "comment": "Gut gemacht!"}
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
user_input["user_data"]["text"], "Ich würde nichts weiteres empfehlen."
|
user_input["user_data"]["text"], "Ich würde nichts weiteres empfehlen."
|
||||||
|
|
@ -380,7 +380,7 @@ class UpdateAssignmentCompletionTestCase(TestCase):
|
||||||
completion_status="grading_in_progress",
|
completion_status="grading_in_progress",
|
||||||
completion_data={
|
completion_data={
|
||||||
user_text_input["id"]: {
|
user_text_input["id"]: {
|
||||||
"trainer_data": {"points": 1, "comment": "Gut gemacht!"}
|
"expert_data": {"points": 1, "comment": "Gut gemacht!"}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ from wagtail.models import Page
|
||||||
from vbv_lernwelt.assignment.models import AssignmentCompletion
|
from vbv_lernwelt.assignment.models import AssignmentCompletion
|
||||||
from vbv_lernwelt.assignment.serializers import AssignmentCompletionSerializer
|
from vbv_lernwelt.assignment.serializers import AssignmentCompletionSerializer
|
||||||
from vbv_lernwelt.assignment.services import update_assignment_completion
|
from vbv_lernwelt.assignment.services import update_assignment_completion
|
||||||
|
from vbv_lernwelt.core.models import User
|
||||||
from vbv_lernwelt.course.models import CourseSession
|
from vbv_lernwelt.course.models import CourseSession
|
||||||
from vbv_lernwelt.course.permissions import (
|
from vbv_lernwelt.course.permissions import (
|
||||||
has_course_access,
|
has_course_access,
|
||||||
|
|
@ -64,7 +65,7 @@ def request_assignment_completion_for_user(
|
||||||
|
|
||||||
|
|
||||||
@api_view(["POST"])
|
@api_view(["POST"])
|
||||||
def update_assignment_input(request):
|
def upsert_user_assignment_completion(request):
|
||||||
try:
|
try:
|
||||||
assignment_id = request.data.get("assignment_id")
|
assignment_id = request.data.get("assignment_id")
|
||||||
course_session_id = request.data.get("course_session_id")
|
course_session_id = request.data.get("course_session_id")
|
||||||
|
|
@ -88,11 +89,11 @@ def update_assignment_input(request):
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"store_assignment_input successful",
|
"upsert_user_assignment_completion successful",
|
||||||
label="assignment_api",
|
label="assignment_api",
|
||||||
assignment_id=assignment.id,
|
assignment_id=assignment.id,
|
||||||
assignment_title=assignment.title,
|
assignment_title=assignment.title,
|
||||||
user_id=request.user.id,
|
assignment_user_id=request.user.id,
|
||||||
course_session_id=course_session_id,
|
course_session_id=course_session_id,
|
||||||
completion_status=completion_status,
|
completion_status=completion_status,
|
||||||
)
|
)
|
||||||
|
|
@ -103,3 +104,52 @@ def update_assignment_input(request):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(e, exc_info=True)
|
logger.error(e, exc_info=True)
|
||||||
return Response({"error": str(e)}, status=404)
|
return Response({"error": str(e)}, status=404)
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(["POST"])
|
||||||
|
def grade_assignment_completion(request):
|
||||||
|
try:
|
||||||
|
assignment_id = request.data.get("assignment_id")
|
||||||
|
assignment_user_id = request.data.get("assignment_user_id")
|
||||||
|
course_session_id = request.data.get("course_session_id")
|
||||||
|
completion_status = request.data.get("completion_status", "grading_in_progress")
|
||||||
|
completion_data = request.data.get("completion_data", {})
|
||||||
|
|
||||||
|
assignment_page = Page.objects.get(id=assignment_id)
|
||||||
|
assignment_user = User.objects.get(id=assignment_user_id)
|
||||||
|
|
||||||
|
if not has_course_access_by_page_request(request, assignment_page):
|
||||||
|
raise PermissionDenied()
|
||||||
|
|
||||||
|
if not is_course_session_expert(request.user, course_session_id):
|
||||||
|
raise PermissionDenied()
|
||||||
|
|
||||||
|
assignment = assignment_page.specific
|
||||||
|
|
||||||
|
ac = update_assignment_completion(
|
||||||
|
assignment_user=assignment_user,
|
||||||
|
assignment=assignment,
|
||||||
|
course_session=CourseSession.objects.get(id=course_session_id),
|
||||||
|
completion_data=completion_data,
|
||||||
|
completion_status=completion_status,
|
||||||
|
copy_task_data=False,
|
||||||
|
grading_user=request.user,
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
"grade_assignment_completion successful",
|
||||||
|
label="assignment_api",
|
||||||
|
assignment_id=assignment.id,
|
||||||
|
assignment_title=assignment.title,
|
||||||
|
assignment_user_id=assignment_user_id,
|
||||||
|
course_session_id=course_session_id,
|
||||||
|
completion_status=completion_status,
|
||||||
|
grading_user_id=request.user.id,
|
||||||
|
)
|
||||||
|
|
||||||
|
return Response(status=200, data=AssignmentCompletionSerializer(ac).data)
|
||||||
|
except PermissionDenied as e:
|
||||||
|
raise e
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(e, exc_info=True)
|
||||||
|
return Response({"error": str(e)}, status=404)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue