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

Added first integration tests (of many to follow)

 * Defnied more APIendpoints for feedback and subscriptions
 * Added the feedback spec to the docs folder
 * The previous commit also closes #43 and #68
parent 6db1cf05
No related branches found
No related tags found
No related merge requests found
Pipeline #
# Generated by Django 2.0.1 on 2018-01-04 20:01
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
......
......@@ -526,6 +526,15 @@ class GeneralTaskSubscription(models.Model):
subscription=self,
submission=task)[0]
def reserve_all_assignments_for_a_student(self):
assert self.query_type == self.STUDENT_QUERY
try:
while True:
self.get_or_create_work_assignment()
except SubscriptionEnded as err:
log.info(f'Loaded all subscriptions of student {self.query_key}')
def _create_new_assignment_if_subscription_empty(self):
if self.assignments.filter(is_done=False).count() < 1:
self.get_or_create_work_assignment()
......
......@@ -4,7 +4,6 @@ from django.http import HttpRequest
from django.views import View
from rest_framework import permissions
log = logging.getLogger(__name__)
......
import logging
from django.db import IntegrityError
from django.core.exceptions import ObjectDoesNotExist
from drf_dynamic_fields import DynamicFieldsMixin
from rest_framework import serializers
from core import models
from core.models import (ExamType, Feedback, GeneralTaskSubscription,
StudentInfo,
Submission, SubmissionType,
StudentInfo, Submission, SubmissionType,
TutorSubmissionAssignment)
from util.factories import GradyUserFactory
......@@ -37,16 +34,12 @@ class FeedbackSerializer(DynamicFieldsModelSerializer):
log.debug(data)
assignment_id = data.pop('assignment_id')
score = data.get('score')
creator = self.context.get('request').user
try:
assignment = TutorSubmissionAssignment.objects.get(
assignment_id=assignment_id)
except ObjectDoesNotExist as err:
raise serializers.ValidationError('No assignment for id')
if not assignment.subscription.owner == creator:
raise serializers.ValidationError('This is not your assignment')
raise serializers.ValidationError('No assignment for given id.')
submission = assignment.submission
if not 0 <= score <= submission.type.full_score:
......@@ -60,12 +53,10 @@ class FeedbackSerializer(DynamicFieldsModelSerializer):
return {
**data,
'assignment': assignment,
'of_tutor': creator,
'of_submission': submission
}
def create(self, validated_data) -> Feedback:
log.debug(validated_data)
assignment = validated_data.pop('assignment')
assignment.set_done()
......@@ -176,27 +167,29 @@ class SubscriptionSerializer(DynamicFieldsModelSerializer):
assignments = AssignmentSerializer(read_only=True, many=True)
def validate(self, data):
data['owner'] = self.context['request'].user
if 'query_key' in data != \
data['query_type'] == GeneralTaskSubscription.RANDOM:
raise serializers.ValidationError(
f'The {data["query_type"]} query_type does not work with the'
f'provided key')
return data
def create(self, validated_data) -> GeneralTaskSubscription:
subscription = GeneralTaskSubscription.objects.create(
owner=self.context.get("request").user,
**validated_data)
try:
subscription._create_new_assignment_if_subscription_empty()
except IntegrityError as err:
log.debug(err)
raise
GeneralTaskSubscription.objects.get(
owner=data['owner'],
query_type=data['query_type'],
query_key=data.get('query_key', None))
except ObjectDoesNotExist:
pass
else:
raise serializers.ValidationError(
"Oh great, you raised an IntegrityError. I'm disappointed.")
'The user already has the subscription')
return data
return subscription
def create(self, validated_data) -> GeneralTaskSubscription:
return GeneralTaskSubscription.objects.create(**validated_data)
class Meta:
model = GeneralTaskSubscription
......
from django.test import TestCase
from rest_framework import status
from rest_framework.test import APIClient, APITestCase
from core.models import (GeneralTaskSubscription, Submission, SubmissionType,
SubscriptionEnded)
from util.factories import GradyUserFactory
from util.factories import GradyUserFactory, make_test_data
class GeneralTaskSubscriptionRandomTest(TestCase):
class GeneralTaskSubscriptionRandomTest(APITestCase):
@classmethod
def setUpTestData(cls):
......@@ -56,3 +56,120 @@ class GeneralTaskSubscriptionRandomTest(TestCase):
assignment = self.subscription.get_oldest_unfinished_assignment()
self.assertEqual(assignment,
self.subscription.get_oldest_unfinished_assignment())
class TestApiEndpoints(APITestCase):
@classmethod
def setUpTestData(cls):
cls.data = make_test_data(data_dict={
'submission_types': [
{
'name': '01. Sort this or that',
'full_score': 35,
'description': 'Very complicated',
'solution': 'Trivial!'
},
{
'name': '02. Merge this or that or maybe even this',
'full_score': 35,
'description': 'Very complicated',
'solution': 'Trivial!'
},
{
'name': '03. This one exists for the sole purpose to test',
'full_score': 30,
'description': 'Very complicated',
'solution': 'Trivial!'
}
],
'students': [
{'username': 'student01'},
{'username': 'student02'}
],
'tutors': [
{'username': 'tutor01'},
{'username': 'tutor02'}
],
'submissions': [
{
'text': 'function blabl\n'
' on multi lines\n'
' for blabla in bla:\n'
' lorem ipsum und so\n',
'type': '01. Sort this or that',
'user': 'student01',
'feedback': {
'text': 'Not good!',
'score': 5,
'of_tutor': 'tutor01',
'is_final': True
}
},
{
'text': 'function blabl\n'
' asasxasx\n'
' lorem ipsum und so\n',
'type': '02. Merge this or that or maybe even this',
'user': 'student01'
},
{
'text': 'function blabl\n'
' on multi lines\n'
' asasxasx\n'
' lorem ipsum und so\n',
'type': '03. This one exists for the sole purpose to test',
'user': 'student01'
},
{
'text': 'function lorem ipsum etc\n',
'type': '03. This one exists for the sole purpose to test',
'user': 'student02'
},
]}
)
def test_can_create_a_subscription(self):
client = APIClient()
client.force_authenticate(user=self.data['tutors'][0])
response = client.post('/api/subscription/', {'query_type': 'random'})
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
def test_create_subscription_and_get_one_assignment(self):
client = APIClient()
client.force_authenticate(user=self.data['tutors'][0])
response = client.post('/api/subscription/', {'query_type': 'random'})
self.assertEqual('tutor01', response.data['owner'])
def test_subscription_has_next_assignment(self):
client = APIClient()
client.force_authenticate(user=self.data['tutors'][0])
response_subs = client.post(
'/api/subscription/', {'query_type': 'random'})
subscription_id = response_subs.data['subscription_id']
assignment_id = response_subs.data['assignments'][0]['assignment_id']
response_current = client.get(
f'/api/subscription/{subscription_id}/assignments/current/')
self.assertEqual(1, len(response_subs.data['assignments']))
self.assertEqual(assignment_id,
response_current.data['assignment_id'])
def test_subscription_can_assign_to_student(self):
client = APIClient()
client.force_authenticate(user=self.data['tutors'][0])
response_subs = client.post(
'/api/subscription/', {
'query_type': 'student',
'query_key': 'student01'
})
assignments = response_subs.data['assignments']
self.assertEqual(2, len(assignments))
......@@ -109,7 +109,6 @@ class TutorDetailViewTests(APITestCase):
@classmethod
def setUpTestData(cls):
cls.factory = APIClient()
cls.user_factory = GradyUserFactory()
def setUp(self):
......
......@@ -7,16 +7,16 @@ from rest_framework.decorators import api_view, detail_route
from rest_framework.response import Response
from core import models
from core.models import (ExamType, GeneralTaskSubscription, StudentInfo,
SubmissionType, TutorSubmissionAssignment,
Feedback)
from core.models import (ExamType, Feedback, GeneralTaskSubscription,
StudentInfo, SubmissionType,
TutorSubmissionAssignment)
from core.permissions import IsReviewer, IsStudent, IsTutorOrReviewer
from core.serializers import (AssignmentSerializer, ExamSerializer,
from core.serializers import (AssignmentDetailSerializer, AssignmentSerializer,
ExamSerializer, FeedbackSerializer,
StudentInfoSerializer,
StudentInfoSerializerForListView,
SubmissionTypeSerializer, SubscriptionSerializer,
TutorSerializer, FeedbackSerializer,
AssignmentDetailSerializer)
TutorSerializer)
@api_view()
......@@ -57,6 +57,21 @@ class FeedbackApiView(
serializer_class = FeedbackSerializer
lookup_field = 'submission__submission_id'
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(
data={**request.data, 'of_tutor': request.user})
serializer.is_valid(raise_exception=True)
if serializer.data['assignment'].subscription.owner != request.user:
return Response({'You do not have permission to edit this'},
status=status.HTTP_403_FORBIDDEN)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data,
status=status.HTTP_201_CREATED,
headers=headers)
class TutorApiViewSet(
mixins.RetrieveModelMixin,
......@@ -129,6 +144,21 @@ class SubscriptionApiViewSet(
instance.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
subscription = serializer.save()
if subscription.query_type == GeneralTaskSubscription.STUDENT_QUERY:
subscription.reserve_all_assignments_for_a_student()
else:
subscription.get_oldest_unfinished_assignment()
headers = self.get_success_headers(serializer.data)
return Response(serializer.data,
status=status.HTTP_201_CREATED,
headers=headers)
class AssignmentApiViewSet(
mixins.RetrieveModelMixin,
......
GET /subscription/<id>
{
"subscription_id": "e313e608-7453-4053-a536-5d18fc9ec3a9",
"owner": "reviewer01",
"query_type": "random",
"query_key": "",
"assignments": [
{
"assignment_id": "dbdde0d0-b1a6-474c-b2be-41edb5229803",
"submission_id": "1558c390-5598-482b-abd3-1f5780e75e0d",
"is_done": false
}
]
}
POST /subscription/
{
"owner": "<some user>",
"query_type": "random|student|submission_type|exam",
"query_key": "<pk for query type>?"
}
DELETE /subscription/<id>
PATCH /subscription/<id> {
"deactivate": true // or false for reactivation
}
GET /subscription/assignments/current
GET /subscription/assignments/next
GET /subscription/assignments/past
GET /assignment/<id> // only those belonging to the requests user
{
"assignment_id": "dbdde0d0-b1a6-474c-b2be-41edb5229803",
"submission_id": "1558c390-5598-482b-abd3-1f5780e75e0d",
"is_done": false
}
DELETE /assignment/<id> // check done conditions
// done conditions
// * feedback was posted
// * feedback was patched (every)
......@@ -123,9 +123,11 @@ def make_submission_types(submission_types=[], **kwargs):
def make_students(students=[], **kwargs):
return [GradyUserFactory().make_student(
username=student['username'],
exam=ExamType.objects.get(module_reference=student['exam'])
exam=ExamType.objects.get(
module_reference=student['exam']) if 'exam' in student else None
) for student in students]
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment