torchbox / wagtail-grapple

A Wagtail app that makes building GraphQL endpoints a breeze!
https://wagtail-grapple.readthedocs.io/en/latest/
Other
152 stars 57 forks source link

Nested StructBlock leads to "'str' object has no attribute 'value'" #117

Closed mmmente closed 3 years ago

mmmente commented 3 years ago

Hi! I really like how easy it is to use Grapple to get a GraphQL API up and running! I just came across this. When having a StructBlock within a StructBlock, I cannot query it and the endpoint returns the error"'str' object has no attribute 'value'"

To test this, I copied the provided example/home/blocks.py and extend it with this use case:

@register_streamfield_block
class TextAndButtonsBlock(blocks.StructBlock):
    text = blocks.TextBlock()
    buttons = blocks.ListBlock(ButtonBlock())
    mainbutton = ButtonBlock()

    graphql_fields = [
        GraphQLString("text"),
        GraphQLImage("image"),
        GraphQLStreamfield("buttons"),
        GraphQLStreamfield("mainbutton"),
    ] 

mainbutton is the new field, which uses the same ButtonBlock as buttons. The buttons work just as intended, however, mainbutton does return the error:

"errors": [
    {
      "message": "'str' object has no attribute 'value'",
      "locations": [
        {
          "line": 10,
          "column": 13
        }
      ],
      "path": [
        "page",
        "body",
        3,
        "mainbutton",
        0,
        "rawValue"
      ]
    },
    {
      "message": "'str' object has no attribute 'value'",
      "locations": [
        {
          "line": 10,
          "column": 13
        }
      ],
      "path": [
        "page",
        "body",
        3,
        "mainbutton",
        1,
        "rawValue"
      ]
    }
  ],

Am I using the wrong GraphQLType here or is this an actual bug?

Thanks!

zerolab commented 3 years ago

Hey @mmmente,

GraphQLStreamfield should just work 🤔 did you decorate ButtonBlock with register_streamfield_block, or at least define graphql_fields in it? Would help to have the fullwe code here

mmmente commented 3 years ago

Hey @zerolab,

thanks for getting back to me. Yes, I even can query the buttons ListBlock.

I reduced the code to relevant parts, as everything is exactly like here: https://github.com/GrappleGQL/wagtail-grapple/tree/master/example except the added mainbutton

blocks.py

@register_streamfield_block
class ButtonBlock(blocks.StructBlock):
    button_text = blocks.CharBlock(required=True, max_length=50, label="Text")
    button_link = blocks.CharBlock(required=True, max_length=255, label="Link")

    graphql_fields = [GraphQLString(
        "button_text"), GraphQLString("button_link")]

@register_streamfield_block
class TextAndButtonsBlock(blocks.StructBlock):
    text = blocks.TextBlock()
    buttons = blocks.ListBlock(ButtonBlock())
    mainbutton = ButtonBlock()

    graphql_fields = [
        GraphQLString("text"),
        GraphQLImage("image"),
        GraphQLStreamfield("buttons"),
        GraphQLStreamfield("mainbutton"),
    ]

Everything is migrated and data is entered.

Query:

{
  page(id: 7) {
    ... on BlogPage {
      body {
        id
        __typename
        ... on TextAndButtonsBlock {
          rawValue
          text
          mainbutton {
            rawValue
            ... on ButtonBlock {
              buttonText
              buttonLink
            }
          }
          buttons {
            __typename
            ... on ButtonBlock {
              buttonText
              buttonLink
            }
          }
        }
        ... on CalloutBlock {
          text
          image {
            url
          }
        }
      }
    }
  }
}

Result form that Query:

{
  "errors": [
    {
      "message": "'str' object has no attribute 'value'",
      "locations": [
        {
          "line": 13,
          "column": 15
        }
      ],
      "path": [
        "page",
        "body",
        0,
        "mainbutton",
        0,
        "rawValue"
      ]
    },
    {
      "message": "'str' object has no attribute 'value'",
      "locations": [
        {
          "line": 13,
          "column": 15
        }
      ],
      "path": [
        "page",
        "body",
        0,
        "mainbutton",
        1,
        "rawValue"
      ]
    }
  ],
  "data": {
    "page": {
      "body": [
        {
          "id": "0e41a821-1c05-455b-b553-0a6a725078b9",
          "__typename": "TextAndButtonsBlock",
          "rawValue": "{'text': 'Am Ende kommt das!', 'buttons': [StructValue([('button_text', 'alles ansehen'), ('button_link', 'www.google.com')]), StructValue([('button_text', 'nichts ansehen'), ('button_link', 'www.google.com')])], 'mainbutton': StructValue([('button_text', 'HAUPTLINK'), ('button_link', 'www.facebook.de')])}",
          "text": "Am Ende kommt das!",
          "mainbutton": [
            null,
            null
          ],
          "buttons": [
            {
              "__typename": "ButtonBlock",
              "buttonText": "alles ansehen",
              "buttonLink": "www.google.com"
            },
            {
              "__typename": "ButtonBlock",
              "buttonText": "nichts ansehen",
              "buttonLink": "www.google.com"
            }
          ]
        }
      ]
    }
  }
}

I just realized, that when I only query the typenames of mainbutton and buttons, mainbutton is reported as a StreamFieldBlock, while buttons comes out as ButtonBlock:

{
  page(id: 7) {
    ... on BlogPage {
      body {
        __typename
        ... on TextAndButtonsBlock {
          mainbutton {
            __typename
          }
          buttons {
            __typename
          }
        }
      }
    }
  }
}

Result:

{
  "data": {
    "page": {
      "body": [
        {
          "__typename": "TextAndButtonsBlock",
          "mainbutton": [
            {
              "__typename": "StreamFieldBlock"
            },
            {
              "__typename": "StreamFieldBlock"
            }
          ],
          "buttons": [
            {
              "__typename": "ButtonBlock"
            },
            {
              "__typename": "ButtonBlock"
            }
          ]
        }
      ]
    }
  }
}

Hope this helps to get some more insight :)

zerolab commented 3 years ago

To me,

          mainbutton {
            rawValue
          }
            ... on ButtonBlock {
              buttonText
              buttonLink
            }
          }

should read

          mainbutton {
            ... on ButtonBlock {
              rawValue
              buttonText
              buttonLink
            }
          }

but it could be just an artifact of copy pasting different bits of code to make the example relevant to the issue. If that is the case, then we'll investigate closer

mmmente commented 3 years ago

Hi @zerolab, you're right, it should read that and I edited the comment to be correct. It was just a mistake due to the copy pasting.

zerolab commented 3 years ago

@mmmente thank you for this. It reminded of a similar issue I experienced this week on a project but did not get time to look into.

Submitted a PR #118 -- can you give it a go?

The tl;dr of it is that the nested StructBlock's field were defined as a list of fields which in turn would fail to resolve. Changed to allow settings is_list=False (so you'd do GraphQLStreamfield("mainbutton", is_list=False) and this now resolves correctly

mmmente commented 3 years ago

Wow, that was fast, @zerolab

I gave it a go, with setting GraphQLStreamfield("mainbutton", is_list=False), no other changes.

Query:

{
  page(id: 7) {
    ... on BlogPage {
      body {
        id
        __typename
        ... on TextAndButtonsBlock {
          rawValue
          text
          mainbutton {
            rawValue
            ... on ButtonBlock {
              buttonText
              buttonLink
            }
          }
          buttons {
            __typename
            ... on ButtonBlock {
              buttonText
              buttonLink
            }
          }
        }
      }
    }
  }
}

Result:

{
  "errors": [
    {
      "message": "'StreamValue' object has no attribute 'id'",
      "locations": [
        {
          "line": 5,
          "column": 9
        }
      ],
      "path": [
        "page",
        "body",
        "id"
      ]
    }
  ],
  "data": {
    "page": {
      "body": {
        "id": null,
        "__typename": "StreamFieldBlock"
      }
    }
  }
}

It seems to me that now the StreamField on the Model (body) is not correctly resolving.

zerolab commented 3 years ago

Sorry, I pushed a change that broke things. Undid it. Can you try again with the latest from that PR?

mmmente commented 3 years ago

Thanks @zerolab ! This fixes the issue and so far I did not come across any new bugs.

zerolab commented 3 years ago

This is now on master. Will look at making a new release in the next day or two