Add mutations and tests
This commit is contained in:
parent
add2c21815
commit
f84efc7f1c
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
),
|
||||
]
|
||||
|
|
@ -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'),
|
||||
),
|
||||
]
|
||||
|
|
@ -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'),
|
||||
),
|
||||
]
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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!) {
|
||||
|
|
|
|||
|
|
@ -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'))
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue