Add mutations and tests

This commit is contained in:
Christian Cueni 2019-11-13 16:29:53 +01:00
parent add2c21815
commit f84efc7f1c
13 changed files with 357 additions and 10 deletions

View File

@ -3,7 +3,7 @@ import random
import factory
from books.factories import ModuleFactory
from .models import Assignment, StudentSubmission
from .models import Assignment, StudentSubmission, SubmissionFeedback
from core.factories import fake
@ -24,3 +24,12 @@ class StudentSubmissionFactory(factory.django.DjangoModelFactory):
text = factory.LazyAttribute(lambda x: fake.sentence(nb_words=random.randint(4, 8)))
assignment = factory.SubFactory(AssignmentFactory)
final = False
class SubmissionFeedbackFactory(factory.django.DjangoModelFactory):
class Meta:
model = SubmissionFeedback
text = factory.LazyAttribute(lambda x: fake.sentence(nb_words=random.randint(4, 8)))
student_submission = factory.SubFactory(StudentSubmissionFactory)
final = False

View File

@ -0,0 +1,18 @@
# Generated by Django 2.0.6 on 2019-11-13 12:47
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assignments', '0006_submissionfeedback'),
]
operations = [
migrations.AddField(
model_name='submissionfeedback',
name='final',
field=models.BooleanField(default=False),
),
]

View File

@ -0,0 +1,19 @@
# Generated by Django 2.0.6 on 2019-11-13 14:30
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('assignments', '0007_submissionfeedback_final'),
]
operations = [
migrations.AlterField(
model_name='submissionfeedback',
name='student_submission',
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='assignments.StudentSubmission'),
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 2.0.6 on 2019-11-13 14:33
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('assignments', '0008_auto_20191113_1430'),
]
operations = [
migrations.RemoveField(
model_name='submissionfeedback',
name='id',
),
migrations.AlterField(
model_name='submissionfeedback',
name='student_submission',
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='assignments.StudentSubmission'),
),
]

View File

@ -42,4 +42,6 @@ class StudentSubmission(TimeStampedModel):
class SubmissionFeedback(TimeStampedModel):
text = models.TextField(blank=True)
teacher = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, related_name='feedbacks')
student_submission = models.OneToOneField(StudentSubmission, on_delete=models.CASCADE, related_name='feedback')
student_submission = models.OneToOneField(StudentSubmission, on_delete=models.CASCADE, primary_key=True)
final = models.BooleanField(default=False)

View File

@ -7,3 +7,10 @@ class AssignmentInput(InputObjectType):
answer = graphene.String(required=True)
document = graphene.String()
final = graphene.Boolean()
class SubmissionFeedbackInput(InputObjectType):
id = graphene.ID()
student_submission = graphene.ID(required=True)
text = graphene.String(required=True)
final = graphene.Boolean()

View File

@ -1,10 +1,12 @@
import graphene
from graphene import relay
from graphql_relay import from_global_id
from rest_framework.exceptions import PermissionDenied
from api.utils import get_object
from assignments.models import Assignment
from assignments.schema.types import AssignmentNode, StudentSubmissionNode
from .inputs import AssignmentInput
from assignments.models import Assignment, SubmissionFeedback
from assignments.schema.types import AssignmentNode, StudentSubmissionNode, SubmissionFeedbackNode
from .inputs import AssignmentInput, SubmissionFeedbackInput
class UpdateAssignment(relay.ClientIDMutation):
@ -30,5 +32,33 @@ class UpdateAssignment(relay.ClientIDMutation):
return cls(successful=True, updated_assignment=assignment, submission=submission, errors=None)
class UpdateSubmissionFeedback(relay.ClientIDMutation):
class Input:
submission_feedback = graphene.Argument(SubmissionFeedbackInput)
updated_submission_feedback = graphene.Field(SubmissionFeedbackNode)
successful = graphene.Boolean()
errors = graphene.List(graphene.String)
@classmethod
def mutate_and_get_payload(cls, root, info, **kwargs):
submission_feedback_data = kwargs.get('submission_feedback')
user = info.context.user
student_submission_id = from_global_id(submission_feedback_data['student_submission'])[1]
if not user.has_perm('users.can_manage_school_class_content'):
raise PermissionDenied('Missing permissions')
(submission_feedback, created) = SubmissionFeedback.objects.get_or_create(teacher=user,
student_submission_id=student_submission_id)
submission_feedback.final = submission_feedback_data.get('final')
submission_feedback.text = submission_feedback_data.get('text')
submission_feedback.save()
return cls(successful=True, updated_submission_feedback=submission_feedback, errors=None)
class AssignmentMutations(object):
update_assignment = UpdateAssignment.Field()
update_submission_feedback = UpdateSubmissionFeedback.Field()

View File

@ -1,8 +1,11 @@
import graphene
from graphene import relay
from graphene_django.filter import DjangoFilterConnectionField
from rest_framework.exceptions import PermissionDenied
from assignments.models import StudentSubmission
from assignments.schema.types import AssignmentNode, StudentSubmissionNode
from api.utils import get_by_id_or_slug
from assignments.models import StudentSubmission, SubmissionFeedback
from assignments.schema.types import AssignmentNode, StudentSubmissionNode, SubmissionFeedbackNode
class AssignmentsQuery(object):

View File

@ -1,17 +1,48 @@
import graphene
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
from graphene import relay
from graphene_django import DjangoObjectType
from assignments.models import Assignment, StudentSubmission
from assignments.models import Assignment, StudentSubmission, SubmissionFeedback
from books.utils import are_solutions_enabled_for
class SubmissionFeedbackNode(DjangoObjectType):
class Meta:
model = SubmissionFeedback
filter_fields = []
interfaces = (relay.Node,)
class StudentSubmissionNode(DjangoObjectType):
submissionfeedback = graphene.Field(SubmissionFeedbackNode)
class Meta:
model = StudentSubmission
filter_fields = []
interfaces = (relay.Node,)
def resolve_submissionfeedback(self, info, **kwargs):
user = info.context.user
if not hasattr(self, 'submissionfeedback'):
return None
# teacher path
if user.has_perm('users.can_manage_school_class_content'):
if self.submissionfeedback.teacher == user:
return self.submissionfeedback
else:
raise PermissionDenied('Missing permissions')
# student path
if self.submissionfeedback.final:
return self.submissionfeedback
return None
class AssignmentNode(DjangoObjectType):
submission = graphene.Field(StudentSubmissionNode)

View File

@ -15,7 +15,6 @@ class AssignmentPermissionsTestCase(DefaultUserTestCase):
self.assignment_id = to_global_id('AssignmentNode', self.assignment.pk)
self.module_id = to_global_id('ModuleNode', self.assignment.module.pk)
def _submit_submission(self, user=None):
mutation = '''
mutation UpdateAssignment($input: UpdateAssignmentInput!) {

View File

@ -0,0 +1,205 @@
# -*- coding: utf-8 -*-
#
# ITerativ GmbH
# http://www.iterativ.ch/
#
# Copyright (c) 2019 ITerativ GmbH. All rights reserved.
#
# Created on 2019-11-13
# @author: chrigu <christian.cueni@iterativ.ch>
from graphql_relay import to_global_id
from api.test_utils import create_client, DefaultUserTestCase
from assignments.models import Assignment, StudentSubmission
from users.factories import SchoolClassFactory
from ..factories import AssignmentFactory, StudentSubmissionFactory, SubmissionFeedbackFactory
class SubmissionFeedbackTestCase(DefaultUserTestCase):
def setUp(self):
super(SubmissionFeedbackTestCase, self).setUp()
self.assignment = AssignmentFactory(
owner=self.teacher
)
self.assignment_id = to_global_id('AssignmentNode', self.assignment.pk)
self.student_submission = StudentSubmissionFactory(assignment=self.assignment, student=self.student1, final=False)
self.student_submission_id = to_global_id('StudentSubmissionNode', self.student_submission.pk)
school_class = SchoolClassFactory()
school_class.users.add(self.student1)
school_class.users.add(self.teacher)
school_class.users.add(self.teacher2)
def _create_submission_feedback(self, user, final, text, student_submission_id):
mutation = '''
mutation UpdateSubmissionFeedback($input: UpdateSubmissionFeedbackInput!) {
updateSubmissionFeedback(input: $input){
updatedSubmissionFeedback {
id
text
final
}
}
}
'''
client = create_client(user)
return client.execute(mutation, variables={
'input': {
"submissionFeedback": {
"studentSubmission": student_submission_id,
"text": text,
"final": final
}
}
})
def _fetch_assignment_student(self, user):
client = create_client(user)
query = '''
query AssignmentWithSubmissions($id: ID!) {
assignment(id: $id) {
title
submission {
id
text
document
submissionfeedback {
text
}
}
}
}
'''
return client.execute(query, variables={
'id': self.assignment_id
})
def _fetch_assignment_teacher(self, user):
client = create_client(user)
query = '''
query AssignmentWithSubmissions($id: ID!) {
assignment(id: $id) {
title
submissions {
id
text
document
submissionfeedback {
text
}
}
}
}
'''
return client.execute(query, variables={
'id': self.assignment_id
})
def _fetch_submission_feedback(self, user):
client = create_client(user)
query = '''
query AssignmentWithSubmissions($id: ID!) {
assignment(id: $id) {
title
submissions {
id
text
document
submissionfeedback {
text
}
}
}
}
'''
return client.execute(query, variables={
'id': self.assignment_id
})
def test_teacher_can_create_feedback(self):
result = self._create_submission_feedback(self.teacher, False, 'Balalal', self.student_submission_id)
self.assertIsNone(result.get('errors'))
self.assertIsNotNone(result.get('data').get('updateSubmissionFeedback').get('updatedSubmissionFeedback').get('id'))
def test_student_cannot_create_feedback(self):
result = self._create_submission_feedback(self.student1, False, 'Balalal', self.student_submission_id)
self.assertIsNotNone(result.get('errors'))
def test_teacher_can_update_feedback(self):
assignment = AssignmentFactory(
owner=self.teacher
)
student_submission = StudentSubmissionFactory(assignment=assignment, student=self.student1, final=False)
submission_feedback = SubmissionFeedbackFactory(teacher=self.teacher, final=False,
student_submission=student_submission)
submission_feedback_id = to_global_id('SubmissionFeedback', submission_feedback.pk)
result = self._create_submission_feedback(self.teacher, True, 'Some', submission_feedback_id)
self.assertIsNone(result.get('errors'))
submission_feedback_response = result.get('data').get('updateSubmissionFeedback').get('updatedSubmissionFeedback')
self.assertTrue(submission_feedback_response.get('final'))
self.assertEqual(submission_feedback_response.get('text'), 'Some')
def test_rogue_teacher_cannot_update_feedback(self):
assignment = AssignmentFactory(
owner=self.teacher
)
student_submission = StudentSubmissionFactory(assignment=assignment, student=self.student1, final=False)
submission_feedback = SubmissionFeedbackFactory(teacher=self.teacher, final=False,
student_submission=student_submission)
submission_feedback_id = to_global_id('SubmissionFeedback', submission_feedback.pk)
result = self._create_submission_feedback(self.teacher2, True, 'Some', submission_feedback_id)
self.assertIsNotNone(result.get('errors'))
def test_student_does_not_see_non_final_feedback(self):
SubmissionFeedbackFactory(teacher=self.teacher, final=False, student_submission=self.student_submission)
result = self._fetch_assignment_student(self.student1)
self.assertIsNone(result.get('data').get('submissionfeedback'))
def test_student_does_see_final_feedback(self):
submission_feedback = SubmissionFeedbackFactory(teacher=self.teacher, final=True,
student_submission=self.student_submission)
result = self._fetch_assignment_student(self.student1)
print(result)
self.assertEqual(result.get('data').get('assignment').get('submission').get('submissionfeedback')
.get('text'), submission_feedback.text)
def test_teacher_can_see_feedback_for_submission(self):
submission_feedback = SubmissionFeedbackFactory(teacher=self.teacher, final=False,
student_submission=self.student_submission)
self.student_submission.final = True
self.student_submission.save()
result = self._fetch_assignment_teacher(self.teacher)
self.assertEqual(result.get('data').get('assignment').get('submissions')[0].get('submissionfeedback')
.get('text'), submission_feedback.text)
def test_rogue_teacher_cannot_see_feedback(self):
submission_feedback = SubmissionFeedbackFactory(teacher=self.teacher, final=False,
student_submission=self.student_submission)
self.student_submission.final = True
self.student_submission.save()
result = self._fetch_assignment_teacher(self.teacher2)
print(result)
self.assertIsNone(result.get('data').get('assignment').get('submissions')[0].get('submissionfeedback'))

View File

@ -1,5 +1,6 @@
import graphene
from graphene import relay
from django.db.models import Q
from graphene_django import DjangoObjectType
from graphene_django.filter import DjangoFilterConnectionField

View File

@ -44,7 +44,7 @@ class User(AbstractUser):
def get_teacher(self):
if self.user_roles.filter(role__key='teacher').exists():
return self
elif self.school_classes.count()>0:
elif self.school_classes.count() > 0:
return self.school_classes.first().get_teacher()
else:
return None