diff --git a/client/src/components/FeedbackForm.vue b/client/src/components/FeedbackForm.vue index 9d7fb918..7f08c8d5 100644 --- a/client/src/components/FeedbackForm.vue +++ b/client/src/components/FeedbackForm.vue @@ -157,12 +157,9 @@ const MAX_STEPS = 12; const sendFeedbackMutation = graphql(` mutation SendFeedbackMutation($input: SendFeedbackInput!) { sendFeedback(input: $input) { - id - satisfaction - goalAttainment - proficiency - receivedMaterials - materialsRating + feedbackResponse { + id + } errors { field messages @@ -205,17 +202,19 @@ const sendFeedback = () => { return; } const input: SendFeedbackInput = reactive({ - materialsRating, - courseNegativeFeedback, - coursePositiveFeedback, - goalAttainment, - instructorCompetence, - instructorRespect, - instructorOpenFeedback, - satisfaction, - proficiency, - receivedMaterials, - wouldRecommend, + data: { + materials_rating: materialsRating, + course_negative_feedback: courseNegativeFeedback, + course_positive_feedback: coursePositiveFeedback, + goald_attainment: goalAttainment, + instructor_competence: instructorCompetence, + instructor_respect: instructorRespect, + instructor_open_feedback: instructorOpenFeedback, + satisfaction, + proficiency, + received_materials: receivedMaterials, + would_recommend: wouldRecommend, + }, page: props.page.translation_key, courseSession: courseSession.id, }); diff --git a/client/src/gql/gql.ts b/client/src/gql/gql.ts index 0fe86c5b..24f468fc 100644 --- a/client/src/gql/gql.ts +++ b/client/src/gql/gql.ts @@ -3,13 +3,13 @@ import type { TypedDocumentNode as DocumentNode } from "@graphql-typed-document- import * as types from "./graphql"; const documents = { - "\n mutation SendFeedbackMutation($input: SendFeedbackInput!) {\n sendFeedback(input: $input) {\n id\n satisfaction\n goalAttainment\n proficiency\n receivedMaterials\n materialsRating\n errors {\n field\n messages\n }\n }\n }\n": + "\n mutation SendFeedbackMutation($input: SendFeedbackInput!) {\n sendFeedback(input: $input) {\n feedbackResponse {\n id\n }\n errors {\n field\n messages\n }\n }\n }\n": types.SendFeedbackMutationDocument, }; export function graphql( - source: "\n mutation SendFeedbackMutation($input: SendFeedbackInput!) {\n sendFeedback(input: $input) {\n id\n satisfaction\n goalAttainment\n proficiency\n receivedMaterials\n materialsRating\n errors {\n field\n messages\n }\n }\n }\n" -): typeof documents["\n mutation SendFeedbackMutation($input: SendFeedbackInput!) {\n sendFeedback(input: $input) {\n id\n satisfaction\n goalAttainment\n proficiency\n receivedMaterials\n materialsRating\n errors {\n field\n messages\n }\n }\n }\n"]; + source: "\n mutation SendFeedbackMutation($input: SendFeedbackInput!) {\n sendFeedback(input: $input) {\n feedbackResponse {\n id\n }\n errors {\n field\n messages\n }\n }\n }\n" +): typeof documents["\n mutation SendFeedbackMutation($input: SendFeedbackInput!) {\n sendFeedback(input: $input) {\n feedbackResponse {\n id\n }\n errors {\n field\n messages\n }\n }\n }\n"]; export function graphql(source: string): unknown; export function graphql(source: string) { diff --git a/client/src/gql/graphql.ts b/client/src/gql/graphql.ts index fc729991..6295ed2b 100644 --- a/client/src/gql/graphql.ts +++ b/client/src/gql/graphql.ts @@ -17,6 +17,7 @@ export type Scalars = { Int: number; Float: number; DateTime: any; + GenericScalar: any; JSONString: any; PositiveInt: any; UUID: any; @@ -152,6 +153,11 @@ export type CircleSiblingsArgs = { searchQuery?: InputMaybe; }; +export type CircleDocument = { + __typename?: "CircleDocument"; + id?: Maybe; +}; + export type CollectionObjectType = { __typename?: "CollectionObjectType"; ancestors: Array>; @@ -520,6 +526,14 @@ export type ErrorType = { messages: Array; }; +export type FeedbackResponse = Node & { + __typename?: "FeedbackResponse"; + circle: Circle; + courseSession: CourseSession; + data?: Maybe; + id: Scalars["ID"]; +}; + export type FloatBlock = StreamFieldInterface & { __typename?: "FloatBlock"; blockType: Scalars["String"]; @@ -1170,6 +1184,10 @@ export type MutationSendFeedbackArgs = { input: SendFeedbackInput; }; +export type Node = { + id: Scalars["ID"]; +}; + export type Page = PageInterface & { __typename?: "Page"; aliasOf?: Maybe; @@ -1581,6 +1599,7 @@ export type RichTextBlock = StreamFieldInterface & { export type Search = | Circle + | CircleDocument | CompetencePage | CompetenceProfilePage | Course @@ -1609,37 +1628,16 @@ export type SecurityRequestResponseLog = { export type SendFeedbackInput = { clientMutationId?: InputMaybe; - courseNegativeFeedback?: InputMaybe; - coursePositiveFeedback?: InputMaybe; - goalAttainment?: InputMaybe; - id?: InputMaybe; - instructorCompetence?: InputMaybe; - instructorOpenFeedback?: InputMaybe; - instructorRespect?: InputMaybe; - materialsRating?: InputMaybe; + courseSession: Scalars["Int"]; + data?: InputMaybe; page: Scalars["String"]; - proficiency?: InputMaybe; - receivedMaterials?: InputMaybe; - satisfaction?: InputMaybe; - wouldRecommend?: InputMaybe; }; export type SendFeedbackPayload = { __typename?: "SendFeedbackPayload"; clientMutationId?: Maybe; - courseNegativeFeedback?: Maybe; - coursePositiveFeedback?: Maybe; errors?: Maybe>>; - goalAttainment?: Maybe; - id?: Maybe; - instructorCompetence?: Maybe; - instructorOpenFeedback?: Maybe; - instructorRespect?: Maybe; - materialsRating?: Maybe; - proficiency?: Maybe; - receivedMaterials?: Maybe; - satisfaction?: Maybe; - wouldRecommend?: Maybe; + feedbackResponse?: Maybe; }; export type SiteObjectType = { @@ -1851,12 +1849,7 @@ export type SendFeedbackMutationMutation = { __typename?: "Mutation"; sendFeedback?: { __typename?: "SendFeedbackPayload"; - id?: number | null; - satisfaction?: number | null; - goalAttainment?: number | null; - proficiency?: number | null; - receivedMaterials?: boolean | null; - materialsRating?: number | null; + feedbackResponse?: { __typename?: "FeedbackResponse"; id: string } | null; errors?: Array<{ __typename?: "ErrorType"; field: string; @@ -1901,12 +1894,16 @@ export const SendFeedbackMutationDocument = { selectionSet: { kind: "SelectionSet", selections: [ - { kind: "Field", name: { kind: "Name", value: "id" } }, - { kind: "Field", name: { kind: "Name", value: "satisfaction" } }, - { kind: "Field", name: { kind: "Name", value: "goalAttainment" } }, - { kind: "Field", name: { kind: "Name", value: "proficiency" } }, - { kind: "Field", name: { kind: "Name", value: "receivedMaterials" } }, - { kind: "Field", name: { kind: "Name", value: "materialsRating" } }, + { + kind: "Field", + name: { kind: "Name", value: "feedbackResponse" }, + selectionSet: { + kind: "SelectionSet", + selections: [ + { kind: "Field", name: { kind: "Name", value: "id" } }, + ], + }, + }, { kind: "Field", name: { kind: "Name", value: "errors" }, diff --git a/server/schema.graphql b/server/schema.graphql index 09be9a12..50a86407 100644 --- a/server/schema.graphql +++ b/server/schema.graphql @@ -79,6 +79,10 @@ type Circle implements PageInterface { ancestors(limit: PositiveInt, offset: PositiveInt, order: String, searchQuery: String, id: ID): [PageInterface!]! } +type CircleDocument { + id: ID +} + type CollectionObjectType { id: ID! path: String! @@ -281,6 +285,13 @@ type ErrorType { messages: [String!]! } +type FeedbackResponse implements Node { + id: ID! + data: GenericScalar + circle: Circle! + courseSession: CourseSession! +} + type FloatBlock implements StreamFieldInterface { id: String blockType: String! @@ -289,6 +300,8 @@ type FloatBlock implements StreamFieldInterface { value: Float! } +scalar GenericScalar + type ImageChooserBlock implements StreamFieldInterface { id: String blockType: String! @@ -608,6 +621,10 @@ type Mutation { sendFeedback(input: SendFeedbackInput!): SendFeedbackPayload } +interface Node { + id: ID! +} + type Page implements PageInterface { id: ID path: String! @@ -783,42 +800,21 @@ type RichTextBlock implements StreamFieldInterface { value: String! } -union Search = CoursePage | LearningPath | Topic | Circle | LearningSequence | LearningUnit | LearningContent | CompetenceProfilePage | CompetencePage | PerformanceCriteria | MediaLibraryPage | MediaCategoryPage | Page | LibraryDocument | User | SecurityRequestResponseLog | Course | CourseCategory | CourseCompletion | CourseSession | CourseSessionUser +union Search = CoursePage | LearningPath | Topic | Circle | LearningSequence | LearningUnit | LearningContent | CompetenceProfilePage | CompetencePage | PerformanceCriteria | MediaLibraryPage | MediaCategoryPage | Page | LibraryDocument | User | SecurityRequestResponseLog | Course | CourseCategory | CourseCompletion | CourseSession | CourseSessionUser | CircleDocument type SecurityRequestResponseLog { id: ID } input SendFeedbackInput { - id: Int page: String! - satisfaction: Int - goalAttainment: Int - proficiency: Int - receivedMaterials: Boolean - materialsRating: Int - instructorCompetence: Int - instructorRespect: Int - instructorOpenFeedback: String - wouldRecommend: Boolean - coursePositiveFeedback: String - courseNegativeFeedback: String + courseSession: Int! + data: GenericScalar clientMutationId: String } type SendFeedbackPayload { - id: Int - satisfaction: Int - goalAttainment: Int - proficiency: Int - receivedMaterials: Boolean - materialsRating: Int - instructorCompetence: Int - instructorRespect: Int - instructorOpenFeedback: String - wouldRecommend: Boolean - coursePositiveFeedback: String - courseNegativeFeedback: String + feedbackResponse: FeedbackResponse errors: [ErrorType] clientMutationId: String } diff --git a/server/vbv_lernwelt/feedback/factories.py b/server/vbv_lernwelt/feedback/factories.py index 73dd1285..eb21d3c8 100644 --- a/server/vbv_lernwelt/feedback/factories.py +++ b/server/vbv_lernwelt/feedback/factories.py @@ -1,3 +1,4 @@ +from factory import Dict from factory.django import DjangoModelFactory from factory.fuzzy import FuzzyChoice, FuzzyInteger @@ -5,33 +6,37 @@ from vbv_lernwelt.feedback.models import FeedbackResponse class FeedbackFactory(DjangoModelFactory): + data = Dict( + { + "satisfaction": FuzzyInteger(2, 4), + "goal_attainment": FuzzyInteger(3, 4), + "proficiency": FuzzyChoice([20, 40, 60, 80]), + "received_materials": FuzzyChoice([True, False]), + "materials_rating": FuzzyInteger(2, 4), + "instructor_competence": FuzzyInteger(3, 4), + "instructor_respect": FuzzyInteger(3, 4), + "instructor_open_feedback": FuzzyChoice( + [ + "Alles gut, manchmal etwas langfädig", + "Super, bin begeistert", + "Ok, enspricht den Erwartungen", + ] + ), + "would_recommend": FuzzyChoice([True, False]), + "course_positive_feedback": FuzzyChoice( + [ + "Die Präsentation war super", + "Das Beispiel mit der Katze fand ich sehr gut veranschaulicht!", + ] + ), + "course_negative_feedback": FuzzyChoice( + [ + "Es wäre praktisch, Zugang zu einer FAQ zu haben.", + "Es wäre schön, mehr Videos hinzuzufügen.", + ] + ), + } + ) + class Meta: model = FeedbackResponse - - satisfaction = FuzzyInteger(2, 4) - goal_attainment = FuzzyInteger(3, 4) - proficiency = FuzzyChoice([20, 40, 60, 80]) - received_materials = FuzzyChoice([True, False]) - materials_rating = FuzzyInteger(2, 4) - instructor_competence = FuzzyInteger(3, 4) - instructor_respect = FuzzyInteger(3, 4) - instructor_open_feedback = FuzzyChoice( - [ - "Alles gut, manchmal etwas langfädig", - "Super, bin begeistert", - "Ok, enspricht den Erwartungen", - ] - ) - would_recommend = FuzzyChoice([True, False]) - course_positive_feedback = FuzzyChoice( - [ - "Die Präsentation war super", - "Das Beispiel mit der Katze fand ich sehr gut veranschaulicht!", - ] - ) - course_negative_feedback = FuzzyChoice( - [ - "Es wäre praktisch, Zugang zu einer FAQ zu haben.", - "Es wäre schön, mehr Videos hinzuzufügen.", - ] - ) diff --git a/server/vbv_lernwelt/feedback/graphql/mutations.py b/server/vbv_lernwelt/feedback/graphql/mutations.py index 0d0c109f..7ffa1bc8 100644 --- a/server/vbv_lernwelt/feedback/graphql/mutations.py +++ b/server/vbv_lernwelt/feedback/graphql/mutations.py @@ -1,13 +1,56 @@ -import graphene -from graphene_django.rest_framework.mutation import SerializerMutation +import structlog +from graphene import ClientIDMutation, Field, Int, List, String +from graphene.types.generic import GenericScalar +from graphene_django.types import ErrorType -from vbv_lernwelt.feedback.serializers import FeedbackResponseSerializer +from vbv_lernwelt.course.models import CourseSession +from vbv_lernwelt.feedback.graphql.types import FeedbackResponse as FeedbackResponseType +from vbv_lernwelt.feedback.models import FeedbackResponse +from vbv_lernwelt.feedback.serializers import CourseFeedbackSerializer +from wagtail.models import Page + +logger = structlog.get_logger(__name__) -class SendFeedback(SerializerMutation): - class Meta: - serializer_class = FeedbackResponseSerializer - model_operations = ["create"] +# https://medium.com/open-graphql/jsonfield-models-in-graphene-django-308ae43d14ee +class SendFeedback(ClientIDMutation): + feedback_response = Field(FeedbackResponseType) + errors = List( + ErrorType, description="May contain more than one error for same field." + ) + + class Input: + page = String(required=True) + course_session = Int(required=True) + data = GenericScalar() + + @classmethod + def mutate_and_get_payload(cls, _, info, **input): + page_key = input["page"] + course_session_id = input["course_session"] + logger.info("creating feedback") + + learning_content = Page.objects.get( + translation_key=page_key, locale__language_code="de-CH" + ) + circle = learning_content.get_parent().specific + course_session = CourseSession.objects.get(id=course_session_id) + data = input.get("data", {}) + + serializer = CourseFeedbackSerializer(data=data) + + if not serializer.is_valid(): + logger.error(serializer.errors) + return SendFeedback(errors=serializer.errors) + + feedback_response = FeedbackResponse.objects.create( + circle=circle, + course_session=course_session, + data=serializer.validated_data, + ) + logger.info(feedback_response) + + return SendFeedback(feedback_response=feedback_response) class Mutation(object): diff --git a/server/vbv_lernwelt/feedback/graphql/types.py b/server/vbv_lernwelt/feedback/graphql/types.py index 8007b083..2f3ef9ad 100644 --- a/server/vbv_lernwelt/feedback/graphql/types.py +++ b/server/vbv_lernwelt/feedback/graphql/types.py @@ -1,8 +1,13 @@ +from graphene.relay import Node +from graphene.types.generic import GenericScalar from graphene_django import DjangoObjectType -from vbv_lernwelt.feedback.models import Feedback +from vbv_lernwelt.feedback.models import FeedbackResponse as FeedbackResponseModel -# class FeedbackType(DjangoObjectType): -# class Meta: -# model = Feedback +class FeedbackResponse(DjangoObjectType): + data = GenericScalar() + + class Meta: + model = FeedbackResponseModel + interfaces = (Node,) diff --git a/server/vbv_lernwelt/feedback/migrations/0003_auto_20230206_1125.py b/server/vbv_lernwelt/feedback/migrations/0003_auto_20230206_1125.py new file mode 100644 index 00000000..541f3a40 --- /dev/null +++ b/server/vbv_lernwelt/feedback/migrations/0003_auto_20230206_1125.py @@ -0,0 +1,66 @@ +# Generated by Django 3.2.13 on 2023-02-06 10:25 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("feedback", "0002_auto_20230111_1044"), + ] + + operations = [ + migrations.RemoveField( + model_name="feedbackresponse", + name="course_negative_feedback", + ), + migrations.RemoveField( + model_name="feedbackresponse", + name="course_positive_feedback", + ), + migrations.RemoveField( + model_name="feedbackresponse", + name="goal_attainment", + ), + migrations.RemoveField( + model_name="feedbackresponse", + name="instructor_competence", + ), + migrations.RemoveField( + model_name="feedbackresponse", + name="instructor_open_feedback", + ), + migrations.RemoveField( + model_name="feedbackresponse", + name="instructor_respect", + ), + migrations.RemoveField( + model_name="feedbackresponse", + name="materials_rating", + ), + migrations.RemoveField( + model_name="feedbackresponse", + name="proficiency", + ), + migrations.RemoveField( + model_name="feedbackresponse", + name="received_materials", + ), + migrations.RemoveField( + model_name="feedbackresponse", + name="satisfaction", + ), + migrations.RemoveField( + model_name="feedbackresponse", + name="would_recommend", + ), + migrations.AddField( + model_name="feedbackresponse", + name="data", + field=models.JSONField(default=dict), + ), + migrations.AddField( + model_name="feedbackresponse", + name="created_at", + field=models.DateTimeField(auto_now_add=True), + ), + ] diff --git a/server/vbv_lernwelt/feedback/models.py b/server/vbv_lernwelt/feedback/models.py index 4f457a92..87206fc8 100644 --- a/server/vbv_lernwelt/feedback/models.py +++ b/server/vbv_lernwelt/feedback/models.py @@ -37,17 +37,20 @@ class FeedbackResponse(models.Model): EIGHTY = 80, "80%" HUNDRED = 100, "100%" - satisfaction = FeedbackIntegerField() - goal_attainment = FeedbackIntegerField() - proficiency = models.IntegerField(null=True) - received_materials = models.BooleanField(null=True) - materials_rating = FeedbackIntegerField() - instructor_competence = FeedbackIntegerField() - instructor_respect = FeedbackIntegerField() - instructor_open_feedback = models.TextField(blank=True) - would_recommend = models.BooleanField(null=True) - course_positive_feedback = models.TextField(blank=True) - course_negative_feedback = models.TextField(blank=True) + # satisfaction = FeedbackIntegerField() + # goal_attainment = FeedbackIntegerField() + # proficiency = models.IntegerField(null=True) + # received_materials = models.BooleanField(null=True) + # materials_rating = FeedbackIntegerField() + # instructor_competence = FeedbackIntegerField() + # instructor_respect = FeedbackIntegerField() + # instructor_open_feedback = models.TextField(blank=True) + # would_recommend = models.BooleanField(null=True) + # course_positive_feedback = models.TextField(blank=True) + # course_negative_feedback = models.TextField(blank=True) + + data = models.JSONField(default=dict) + created_at = models.DateTimeField(auto_now_add=True) circle = models.ForeignKey("learnpath.Circle", models.PROTECT) course_session = models.ForeignKey("course.CourseSession", models.PROTECT) diff --git a/server/vbv_lernwelt/feedback/serializers.py b/server/vbv_lernwelt/feedback/serializers.py index 988ac934..17d149c4 100644 --- a/server/vbv_lernwelt/feedback/serializers.py +++ b/server/vbv_lernwelt/feedback/serializers.py @@ -1,32 +1,31 @@ import structlog from rest_framework import serializers -from wagtail.models import Page - -from vbv_lernwelt.course.models import CourseSession -from vbv_lernwelt.feedback.models import FeedbackResponse logger = structlog.get_logger(__name__) -class FeedbackResponseSerializer(serializers.ModelSerializer): - page = serializers.CharField(write_only=True) - course_session = serializers.CharField(write_only=True) - - class Meta: - model = FeedbackResponse - exclude = ["circle"] - # extra_kwargs = {"course", {"read_only": True}} - - def create(self, validated_data): - logger.info("creating feedback") - page_key = validated_data.pop("page") - course_session_id = validated_data.pop("course_session") - - learning_content = Page.objects.get( - translation_key=page_key, locale__language_code="de-CH" - ) - circle = learning_content.get_parent().specific - course_session = CourseSession.objects.get(id=course_session_id) - return FeedbackResponse.objects.create( - **validated_data, circle=circle, course_session=course_session +class FeedbackIntegerField(serializers.IntegerField): + def __init__(self, **kwargs): + super().__init__( + required=False, allow_null=True, min_value=1, max_value=5, **kwargs ) + + +class CourseFeedbackSerializer(serializers.Serializer): + satisfaction = FeedbackIntegerField() + goal_attainment = FeedbackIntegerField() + proficiency = serializers.IntegerField(required=False, allow_null=True) + received_materials = serializers.BooleanField(required=False, allow_null=True) + materials_rating = FeedbackIntegerField() + instructor_competence = FeedbackIntegerField() + instructor_respect = FeedbackIntegerField() + instructor_open_feedback = serializers.CharField( + required=False, allow_null=True, allow_blank=True + ) + would_recommend = serializers.BooleanField(required=False, allow_null=True) + course_positive_feedback = serializers.CharField( + required=False, allow_null=True, allow_blank=True + ) + course_negative_feedback = serializers.CharField( + required=False, allow_null=True, allow_blank=True + ) diff --git a/server/vbv_lernwelt/feedback/tests/test_feedback_api.py b/server/vbv_lernwelt/feedback/tests/test_feedback_api.py index 871f41bc..55d2b70e 100644 --- a/server/vbv_lernwelt/feedback/tests/test_feedback_api.py +++ b/server/vbv_lernwelt/feedback/tests/test_feedback_api.py @@ -159,17 +159,25 @@ class FeedbackDetailApiTestCase(FeedbackApiBaseTestCase): FeedbackFactory( circle=circle, course_session=csu.course_session, - satisfaction=feedback_data["satisfaction"][i], - goal_attainment=feedback_data["goal_attainment"][i], - proficiency=feedback_data["proficiency"][i], - received_materials=feedback_data["received_materials"][i], - materials_rating=feedback_data["materials_rating"][i], - instructor_competence=feedback_data["instructor_competence"][i], - instructor_open_feedback=feedback_data["instructor_open_feedback"][i], - instructor_respect=feedback_data["instructor_respect"][i], - would_recommend=feedback_data["would_recommend"][i], - course_positive_feedback=feedback_data["course_positive_feedback"][i], - course_negative_feedback=feedback_data["course_negative_feedback"][i], + data={ + "satisfaction": feedback_data["satisfaction"][i], + "goal_attainment": feedback_data["goal_attainment"][i], + "proficiency": feedback_data["proficiency"][i], + "received_materials": feedback_data["received_materials"][i], + "materials_rating": feedback_data["materials_rating"][i], + "instructor_competence": feedback_data["instructor_competence"][i], + "instructor_open_feedback": feedback_data[ + "instructor_open_feedback" + ][i], + "instructor_respect": feedback_data["instructor_respect"][i], + "would_recommend": feedback_data["would_recommend"][i], + "course_positive_feedback": feedback_data[ + "course_positive_feedback" + ][i], + "course_negative_feedback": feedback_data[ + "course_negative_feedback" + ][i], + }, ).save() response = self.client.get( diff --git a/server/vbv_lernwelt/feedback/views.py b/server/vbv_lernwelt/feedback/views.py index 3e515e03..150ae855 100644 --- a/server/vbv_lernwelt/feedback/views.py +++ b/server/vbv_lernwelt/feedback/views.py @@ -49,7 +49,7 @@ def get_feedback_for_circle(request, course_id, circle_id): course_session__course_id=course_id, circle__expert__user=request.user, circle_id=circle_id, - ) + ).order_by("created_at") # I guess this is ok for the üK case feedback_data = {"amount": len(feedbacks), "questions": {}} @@ -62,6 +62,8 @@ def get_feedback_for_circle(request, course_id, circle_id): for feedback in feedbacks: for field in FEEDBACK_FIELDS: - feedback_data["questions"][field].append(getattr(feedback, field)) + data = feedback.data.get(field, None) + if data is not None: + feedback_data["questions"][field].append(data) return Response(status=200, data=feedback_data)