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

Posted

技术标签:

【中文标题】Django Graphene 使用多层嵌套外键编写突变【英文标题】:Django Graphene writing mutations with multiple layers of nested foreign keys 【发布时间】:2021-02-01 21:37:30 【问题描述】:

如何编写嵌套外键的架构和查询?我检查了文档,没有发现如何执行此操作的示例。所以这是我基于 github 和 *** 答案的尝试,可以说我有这些模型:

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 文件的方式。将 InputFields 嵌套在另一个 InputField 中不起作用的地方。还有其他方法可以编写单个突变吗?

【问题讨论】:

具体的错误信息好像和这个***.com/questions/49809261/…有关。 -- 我建议您首先检查您的代码是否达到blog.save。如果是这样,。问题在于格式化响应。 @MarkChackerian 我不认为它达到了与name: "aaa" 部分冲突的程度。与 schema.py 部分存在匹配问题。 我不认为你需要 "address = graphene.Field(AddressType)" 和 "person=graphene.Field(PersonType)" 行——那是你放置输出的地方,不输入。而你只想输出“blog”的值。 @MarkChackerian 我想创建这两个模型。如果我删除它,它仍然会给出同样的错误。 【参考方案1】:

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

但具体而言,您的 Graphene 突变定义将生成如下所示的突变:

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 会自动进行从蛇情况到骆驼情况的对话),以及在字段中将Blog 更改为blog选择。

但让我们更进一步,这就是我将如何进行突变。

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
    
  

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

【讨论】:

以上是关于Django Graphene 使用多层嵌套外键编写突变的主要内容,如果未能解决你的问题,请参考以下文章

使用嵌套结构的参数使用 django-graphene 过滤父级

如何隐藏/排除graphene_django中请求实体的某些外键字段?

Graphene-Django 嵌套过滤器(中继)

如何删除 Graphene Django 突变查询(中继)中的嵌套输入对象?

Django - 使用 2 个嵌套外键复制模型实例

如何使用 Django 嵌套对象添加 GraphQL 突变?