Django Graphene使用多层嵌套外键编写变异

时间:2020-10-19 05:47:29

标签: python-3.x django graphql graphene-django

如何编写模式并查询嵌套的外键?我检查了文档,但没有找到如何执行此操作的示例。所以这是我基于github的尝试,而stackoverflow答案可以说我有以下模型:

class Address(models.Model):
    name = models.CharField()

class Person(models.Model):
    name = models.CharField()
    address = models.ForeignKey('Address', on_delete=models.CASCADE, blank=False, null=False)

class Blog(models.Model):
    person = models.ForeignKey('Person', on_delete=models.CASCADE, blank=False, null=False)
    text = models.TextField()

我尝试编写这样的模式:

class AddressInput(graphene.InputObjectType):

    name = graphene.String(required=True)


class PersonInput(graphene.InputObjectType):

    name = graphene.String(required=True)
    address =graphene.Field(AddressInput)

class CreateNewBlog(graphene.Mutation):

    blog=graphene.Field(BlogType)

    class Arguments:
        address_data = AddressInput()
        person_data = PersonInput()
        text = graphene.String()

    @staticmethod
    def mutate(root, info, person_data=None, address_data=None, **input):

        address = Address.objects.create(name=address_data.name)
        person = Person.objects.create(address=address, name=person_data.name)
        blog = Blog.objects.create(person =person, text=input['text'])
        blog.save()

        return CreateNewBlog(blog=blog)

我使用了这样的查询:

mutation {
        CreateNewBlog(person: { address: {name: "aaa"}, 
            name: "First Last" }, text: "hi hi") {
            Blog {
              person{
                name
                address{
                  name
                }
              },
              text
                
            }
        }
}

我收到此错误消息:

{
  "errors": [
    {
      "message": "'NoneType' object has no attribute 'name'",
      "locations": [
        {
          "line": 32,
          "column": 9
        }
      ],
      "path": [
        "CreateNewBlog"
      ]
    }
  ],
  "data": {
    "CreateNewBlog": null
  }
}

我认为问题出在我编写schema.py文件的方式中。无法将InputField嵌套在另一个InputField中的地方。还有其他写单个突变的方法吗?

1 个答案:

答案 0 :(得分:1)

好的,这里有几件事。首先,您应该生成schema.graphql文件,因为这将向您显示Graphene构建的架构的实际最终形状,这将使调试更加容易。或者,您可以使用GraphiQL来测试您的查询,并让其文档和自动完成功能为您完成繁重的工作。

但是,具体来说,您的石墨烯突变定义将产生如下所示的突变:

input AddressInput {
  name: String!
}

input PersonInput {
  name: String!
  address: AddressInput
}

type CreateNewBlogOutput {
  blog: Blog
}

type Mutation {
  CreateNewBlog(addressData: AddressInput, personData: PersonInput, text: String): CreateNewBlogOutput!
}

值得一提的是,这里有两种方法可以提供AddressInput,一种是在根目录下提供,另一种是在PersonInput内提供。这可能不是您打算要做的。其次,不需要任何根参数,这会导致您的错误消息相当无用,因为问题在于您调用了不正确的突变参数,但是查询验证器却允许它通过,因为您的类型非常宽松。 >

相信,如果您要像下面那样运行突变,它实际上会起作用:

mutation {
  CreateNewBlog(
    personData: {
      address: {
        name: "aaa"
      }, 
      name: "First Last"
    },
    text: "hi hi"
  ) {
    blog {
      person {
        name
        address {
          name
        }
      }
      text
    }
  }
}

我在这里只作了两个更改,person更改为personData(为了匹配您的突变定义,Graphene自动进行从蛇形到驼峰形的对话),以及Blogblog在字段选择中。

但是,让我们再进一步一点,这就是我进行突变的方式。

class AddressInput(graphene.InputObjectType):
    name = graphene.String(required=True)


class PersonInput(graphene.InputObjectType):
    name = graphene.String(required=True)
    address = AddressInput(required=True)


class CreateNewBlogInput(graphene.InputObjectType):
    person = PersonInput(required=True)
    text = graphene.String(required=True)


class CreateNewBlogPayload(graphene.ObjectType):
    blog = graphene.Field(BlogType, required=True)


class CreateNewBlog(graphene.Mutation):
    class Arguments:
        input_data = CreateNewBlogInput(required=True, name="input")

    Output = CreateNewBlogPayload


    @staticmethod
    def mutate(root, info, input_data):
        address = Address.objects.create(name=input_data.person.address.name)
        person = Person.objects.create(address=address, name=input_data.person.name)
        blog = Blog.objects.create(person=person, text=input_data.text)
        blog.save()

        return CreateNewBlogPayload(blog=blog)

在构造Graphene的突变对象时,我也会将CreateNewBlog更改为createNewBlog,因为GraphQL约定是使用小驼峰进行突变。

然后您将像这样运行它:

mutation {
  createNewBlog(
    input: {
      person: {
        address: {
          name: "aaa"
        }, 
        name: "First Last"
      }
      text: "hi hi"
    }
  ) {
    blog {
      person {
        name
        address {
          name
        }
      }
      text
    }
  }
}

为什么将整个输入包装在单个输入字段中?主要是因为它使使用变量时在客户端中更容易调用突变,因此您只能提供正确形状的单个输入arg,而不是多个。

// So instead of this
mutation OldCreateNewBlog($person: PersonInput, $text: String) {
  createNewBlog(
    personData: $person
    text: $text
  ) {
    blog {
      person {
        name
        address {
          name
        }
      }
      text
    }
  }
}

// You have this
mutation NewCreateNewBlog($input: CreateNewBlogInput!) {
  createNewBlog(
    input: $input
  ) {
    blog {
      person {
        name
        address {
          name
        }
      }
      text
    }
  }
}

后者使随着时间的推移更改输入形状变得更加容易,而只需在客户端代码中的一个位置进行更改。