djangographqlgraphene-django

Using Graphql with Django : Reverse Foreign key relationship using related name for all objects


I'm working with graphene-django ,graphql and django and i want to use the reverse foreign key concept of drf using related name but currently i facing difficulty in achieving that i have put up the models, code and also the output screenshot below

import asyncio

asyncio.set_event_loop(asyncio.new_event_loop())
import graphene
from graphene_django import DjangoObjectType
from .models import Category, Book, Grocery, FileUpload
from django.db.models import Q
from graphene_file_upload.scalars import Upload
import decimal


class BookType(DjangoObjectType):
    class Meta:
        model = Book
        fields = (
            'id',
            'title',
            'author',
            'isbn',
            'pages',
            'price',
            'quantity',
            'description',
            'status',
            'date_created',
        )


class FileUploadType(DjangoObjectType):
    class Meta:
        model = FileUpload
        fields = ('id', 'file')


class GroceryType(DjangoObjectType):
    class Meta:
        model = Grocery
        # fields = (
        #     'product_tag',
        #     'name',
        #     'category',
        #     'price',
        #     'quantity',
        #     'imageurl',
        #     'status',
        #     'date_created',
        # )


class CategoryType(DjangoObjectType):
    grocery = graphene.List(GroceryType)

    class Meta:
        model = Category
        fields = ('id', 'title', 'grocery')


class Query(graphene.ObjectType):
    books = graphene.List(
        BookType,
        search=graphene.String(),
        first=graphene.Int(),
        skip=graphene.Int(),
    )
    categories = graphene.List(CategoryType)
    # books = graphene.List(BookType)
    groceries = graphene.List(GroceryType)
    files = graphene.List(FileUploadType)

    # def resolve_books(root, info, **kwargs):
    #     # Querying a list
    #     return Book.objects.all()
    def resolve_books(self, info, search=None, first=None, skip=None, **kwargs):
        qs = Book.objects.all()

        if search:
            filter = (
                    Q(id=search) |
                    Q(title__icontains=search)
            )
            qs = qs.filter(filter)

        if skip:
            qs = qs[skip:]

        if first:
            qs = qs[:first]

        return qs

    def resolve_categories(root, info, **kwargs):
        # Querying a list
        r = []
        for ele in Category.objects.all().prefetch_related("grocery"):
            r.append(ele.grocery.all())
        return Category.objects.all().prefetch_related("grocery")

    def resolve_groceries(root, info, **kwargs):
        # Querying a list
        print("help")
        qs = Grocery.objects.all()
        print(qs)
        return qs

    def resolve_files(root, info, **kwargs):
        # Querying a list
        return FileUpload.objects.all()


class UpdateCategory(graphene.Mutation):
    class Arguments:
        # Mutation to update a category
        title = graphene.String(required=True)
        id = graphene.ID()

    category = graphene.Field(CategoryType)

    @classmethod
    def mutate(cls, root, info, title, id):
        category = Category.objects.get(pk=id)
        category.title = title
        category.save()

        return UpdateCategory(category=category)


class CreateCategory(graphene.Mutation):
    class Arguments:
        # Mutation to create a category
        title = graphene.String(required=True)

    # Class attributes define the response of the mutation
    category = graphene.Field(CategoryType)

    @classmethod
    def mutate(cls, root, info, title):
        category = Category()
        category.title = title
        category.save()

        return CreateCategory(category=category)


class FileUploadMutation(graphene.Mutation):
    class Arguments:
        file = Upload(required=True)

    success = graphene.Boolean()

    def mutate(self, info, file, **kwargs):
        # do something with your file
        file_obj = FileUpload()
        file_obj.file = file
        file_obj.save()
        return FileUploadMutation(success=True)


class BookInput(graphene.InputObjectType):
    title = graphene.String()
    author = graphene.String()
    pages = graphene.Int()
    price = graphene.Int()
    quantity = graphene.Int()
    description = graphene.String()
    status = graphene.String()


class GroceryInput(graphene.InputObjectType):
    product_tag = graphene.String()
    name = graphene.String()
    category_id = graphene.String(required=True)

    price = graphene.Int()
    quantity = graphene.Int()
    imageurl = graphene.String()
    status = graphene.String()


class CreateBook(graphene.Mutation):
    class Arguments:
        input = BookInput(required=True)

    book = graphene.Field(BookType)

    @classmethod
    def mutate(cls, root, info, input):
        book = Book()
        book.title = input.title
        book.author = input.author
        book.pages = input.pages
        book.price = input.price
        book.quantity = input.quantity
        book.description = input.description
        book.status = input.status
        book.save()
        return CreateBook(book=book)


class UpdateBook(graphene.Mutation):
    class Arguments:
        input = BookInput(required=True)
        id = graphene.ID()

    book = graphene.Field(BookType)

    @classmethod
    def mutate(cls, root, info, input, id):
        book = Book.objects.get(pk=id)
        book.name = input.name
        book.description = input.description
        book.price = decimal.Decimal(input.price)
        book.quantity = input.quantity
        book.save()
        return UpdateBook(book=book)


class CreateGrocery(graphene.Mutation):
    class Arguments:
        input = GroceryInput(required=True)

    grocery = graphene.Field(GroceryType)
    category = graphene.Field(CategoryType)

    @classmethod
    def mutate(cls, root, info, input):
        if input is None:
            return CreateGrocery(grocery=None)
        grocery = Grocery.objects.create(**input)
        return CreateGrocery(grocery=grocery)


class Mutation(graphene.ObjectType):
    update_category = UpdateCategory.Field()
    create_category = CreateCategory.Field()
    create_book = CreateBook.Field()
    update_book = UpdateBook.Field()
    create_grocery = CreateGrocery.Field()
    # file_upload = FileUpload.Field()


schema = graphene.Schema(query=Query, mutation=Mutation)

Models that i have created are as follows

from django.db import models

# Create your models here.
class Category(models.Model):
    title = models.CharField(max_length=255)

    class Meta:
        verbose_name_plural = 'Categories'
    def __str__(self):
        return self.title

class Book(models.Model):
    title = models.CharField(max_length=150)
    author = models.CharField(max_length=100, default='John Doe')
    isbn = models.CharField(max_length=13)
    pages = models.IntegerField()
    price = models.IntegerField()
    quantity = models.IntegerField()
    description = models.TextField()
    status = models.BooleanField()
    date_created = models.DateField(auto_now_add=True)

    class Meta:
        ordering = ['-date_created']

    def __str__(self):
        return self.title

class Grocery(models.Model):
    product_tag = models.CharField(max_length=10)
    name = models.CharField(max_length=100)
    category = models.ForeignKey(Category, related_name='grocery', on_delete=models.CASCADE)
    price = models.IntegerField()
    quantity = models.IntegerField()
    imageurl = models.URLField()
    status = models.BooleanField()
    date_created = models.DateField(auto_now_add=True)

    class Meta:
        ordering = ['-date_created']

    def __str__(self):
        return self.name

class FileUpload(models.Model):
    file = models.FileField()

Now i want for each category i should be able to list groceries as well as shown below

{
  "data": {
    "categories": [
      {
        "id": "1",
        "title": "Plastic"
        "grocery" :{
            #grocery related fields
         }
      },
      
    ]
  }
}

Currently when i try i get the following error enter image description here

Request that i'm using currently

query listCategories{
  categories {
      id
        title
        grocery {
        name
      }
    }
}

Error Response which i'm getting

{
  "errors": [
    {
      "message": "Expected Iterable, but did not find one for field 'CategoryType.grocery'.",
      "locations": [
        {
          "line": 56,
          "column": 7
        }
      ],
      "path": [
        "categories",
        0,
        "grocery"
      ]
    },
    {
      "message": "Expected Iterable, but did not find one for field 'CategoryType.grocery'.",
      "locations": [
        {
          "line": 56,
          "column": 7
        }
      ],
      "path": [
        "categories",
        1,
        "grocery"
      ]
    },
    {
      "message": "Expected Iterable, but did not find one for field 'CategoryType.grocery'.",
      "locations": [
        {
          "line": 56,
          "column": 7
        }
      ],
      "path": [
        "categories",
        2,
        "grocery"
      ]
    },
    {
      "message": "Expected Iterable, but did not find one for field 'CategoryType.grocery'.",
      "locations": [
        {
          "line": 56,
          "column": 7
        }
      ],
      "path": [
        "categories",
        3,
        "grocery"
      ]
    }
  ],
  "data": {
    "categories": [
      {
        "id": "1",
        "title": "Plastic",
        "grocery": null
      },
      {
        "id": "2",
        "title": "Plastic",
        "grocery": null
      },
      {
        "id": "3",
        "title": "Plastic",
        "grocery": null
      },
      {
        "id": "4",
        "title": "Plastic",
        "grocery": null
      }
    ]
  }
}

Solution

  • Use graphene_django.DjangoListField:

    from graphene_django import DjangoListField, DjangoObjectType
    
    class CategoryType(DjangoObjectType):
        grocery = DjangoListField(GroceryType)
    

    Alternatively, implement a resolver:

    import graphene
    from graphene_django import DjangoObjectType
    
    class CategoryType(DjangoObjectType):
        grocery = graphene.List(GroceryType)
    
        @staticmethod
        def resolve_grocery(parent, info):
            return parent.grocery.all()