1
votes

I am new to Django/Graphql/Graphene and struggling to write a nested mutation with several foreign keys and a many-to-many relation. I have read several posts on this topic but still struggling.

I have highlighted several questions within the below code. It would be great to help me both on the foreign keys (Fare, Year) and on the Many-to-Many field (Parent), which I have not even included yet in the graphical request. It would be good to have help both on how to write the Python graphene code and then the Graphiql request. Many thanks to all

Here is the code:

Model (extract):

class Student(models.Model):
    first = models.CharField(max_length=64)
    last = models.CharField(max_length=64)
    email = models.EmailField()
    phone = models.CharField(max_length=64)
    year = models.ForeignKey(Year, on_delete=models.CASCADE, related_name="students", blank=True)
    fare = models.ForeignKey(Fare, on_delete=models.CASCADE, related_name="at_fare", blank=True)
    parents = models.ManyToManyField(Parent, related_name="children", blank=True)

Schema.py:

class StudentType(DjangoObjectType):
    class Meta:
        model = Student

// is the Student Type necessary????

class FareType(DjangoObjectType):
    class Meta:
        model = Fare

class YearType(DjangoObjectType):
    class Meta:
        model = Year

class ParentType(DjangoObjectType):
    class Meta:
        model = Parent

Input object types:

class StudentInput(graphene.InputObjectType):
    #id = graphene.ID() // is this field necessary??
    first = graphene.String()
    last = graphene.String()
    email = graphene.String()
    phone = graphene.String()
    year = graphene.Field(YearInput)
    fare = graphene.Field(FareInput)
    parents = graphene.List(ParentInput)

class FareInput(graphene.InputObjectType):
    #id = graphene.ID()
    level = graphene.String()
    price = graphene.Int()
    currency = graphene.String()

class YearInput(graphene.InputObjectType):
    #id = graphene.ID()
    name = graphene.String()

class ParentInput(graphene.InputObjectType):
    id = graphene.ID()
    first = graphene.String()
    last = graphene.String()
    email = graphene.String()
    phone = graphene.String()
    is_primary_contact = graphene.Boolean()
    is_billing_contact = graphene.Boolean()
    address_line1 = graphene.String()
    address_line2 = graphene.String()
    postcode = graphene.String()
    city = graphene.String()
    country = graphene.String()

Mutation (I haven't even managed to include the Parents field so far):

class CreateStudent(graphene.Mutation):
    ok = graphene.Boolean()
    student = graphene.Field(StudentType)

    class Arguments:
        student_data=StudentInput(required=True)
        year_data=YearInput(required=True)
        fare_data=FareInput(required=True)

    def mutate(self, info, student_data, year_data, fare_data):
        student_data.year = year_data
        student_data.fare = fare_data
        student = Student.objects.create(**student_data)
        return CreateStudent(ok=ok, student=student)

GraphIQL mutation:

mutation CreateStudent($studentData: StudentInput!, $yearData: YearInput!, $fareData: FareInput!) {
     createStudent(studentData: $studentData, yearData: $yearData, fareData: $fareData) {
student {
  id
  first
  last
  phone
  email
  year {
    id
    name
  }
  fare {
    id
    level
    price
    currency
      }
    }
  }
}

Variables

{ "studentData": 
    {
      "first":"Jake",
      "last":"Blog",
      "phone":"+447917421894",
      "email":"[email protected]"
    },
  "yearData": {
  "name": "Test"
    },
  "fareData": {"level": "MatTest", "price": 160, "currency": "EUR"}
}

Here is the error message from Graphiql, but I suspect there could be other issues.

{
  "errors": [
    {
      "message": "null value in column \"fare_id\" of relation \"tutor_student\" violates not-null constraint\nDETAIL:  Failing row contains (13, Jake, Blog, [email protected], +447817421894, null, null).\n",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "path": [
        "createStudent"
      ]
    }
  ],
  "data": {
    "createStudent": null
  }
}
1

1 Answers

0
votes

You need to create or get the year and fare instances and pass that into your Student.objects.create() call.

def mutate(self, info, student_data, year_data, fare_data):
    student_data.year = Year.objects.create(**year_data)
    student_data.fare = Fare.objects.create(**fare_data)
    student = Student.objects.create(**student_data)
    return CreateStudent(ok=ok, student=student)

If you're potentially reusing existing years and fares, you can use get_or_create:

    student_data.year, _ = Year.objects.get_or_create(**year_data)
    student_data.fare, _ = Fare.objects.get_or_create(**fare_data)