I'm building a simple CRUD interface with Python, GraphQL (graphene-django) and Django. The CREATE mutation for an Object (Ingredient
) that includes Foreign Key relations to another Object (Category
) won't work. I want to give GraphQL the id of the CategoryObject and not a whole category instance. Then in the backend it should draw the relation to the Category object.
In the Django model the Ingredient Object contains an instance of the Foreign key Category Object (see code below). Is the whole Category Object needed here to draw the relation and to use Ingredient.objects.select_related('category').all()
?
The create mutation expects IngredientInput
that includes all properties and an integer field for the foreign key relation. So the graphQL mutation itself currently works as I want it to.
My question is similar if not the same as this one but these answers don't help me.
models.py:
class Category(models.Model):
name = models.CharField(max_length=50, unique=True)
notes = models.TextField()
class Meta:
verbose_name = u"Category"
verbose_name_plural = u"Categories"
ordering = ("id",)
def __str__(self):
return self.name
class Ingredient(models.Model):
name = models.CharField(max_length=100)
notes = models.TextField()
category = models.ForeignKey(Category, on_delete=models.CASCADE)
class Meta:
verbose_name = u"Ingredient"
verbose_name_plural = u"Ingredients"
ordering = ("id",)
def __str__(self):
return self.name
schema.py:
class CategoryType(DjangoObjectType):
class Meta:
model = Category
class CategoryInput(graphene.InputObjectType):
name = graphene.String(required=True)
notes = graphene.String()
class IngredientType(DjangoObjectType):
class Meta:
model = Ingredient
class IngredientInput(graphene.InputObjectType):
name = graphene.String(required=True)
notes = graphene.String()
category = graphene.Int()
class CreateIngredient(graphene.Mutation):
class Arguments:
ingredientData = IngredientInput(required=True)
ingredient = graphene.Field(IngredientType)
@staticmethod
def mutate(root, info, ingredientData):
_ingredient = Ingredient.objects.create(**ingredientData)
return CreateIngredient(ingredient=_ingredient)
class Mutation(graphene.ObjectType):
create_category = CreateCategory.Field()
create_ingredient = CreateIngredient.Field()
graphql_query:
mutation createIngredient($ingredientData: IngredientInput!) {
createIngredient(ingredientData: $ingredientData) {
ingredient {
id
name
notes
category{name}
}
graphql-variables:
{
"ingredientData": {
"name": "milk",
"notes": "from cow",
"category": 8 # here I ant to insert the id of an existing category object
}
}
error-message after executoin the query:
{
"errors": [
{
"message": "Cannot assign \"8\": \"Ingredient.category\" must be a \"Category\" instance.",
"locations": [
{
"line": 38,
"column": 3
}
],
"path": [
"createIngredient"
]
}
],
"data": {
"createIngredient": null
}
}
I had this same problem today.
The Cannot assign \"8\": \"Ingredient.category\" must be a \"Category\" instance.
error is a Django error that happens when you try to create an object using the foreign key integer directly instead of an object.
If you want to use the foreign key id directly you have to use the _id
suffix.
For example, instead of using:
_ingredient = Ingredient.objects.create(name="milk", notes="from_cow", category=8)
You have to use either
category_obj = Category.objects.get(id=8)
_ingredient = Ingredient.objects.create(name="milk", notes="from_cow", category=category_obj)
or
_ingredient = Ingredient.objects.create(name="milk", notes="from_cow", category_id=8)
In the case of using GraphQL, you would have to set your InputObjectType
field to <name>_id. In your case:
class IngredientInput(graphene.InputObjectType):
name = graphene.String(required=True)
notes = graphene.String()
category_id = graphene.Int()
This, however will make your field in the schema show up as categoryId
. If you wish to keep the category
name, you must change to:
category_id = graphene.Int(name="category")
Cheers!