Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for Wagtail 4.3 ImageBlock #419

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
### Added

- Support for Wagtail 6.3 and 6.4 and Python 3.13 ([#416](https://github.com/torchbox/wagtail-grapple/pull/416)) @mgax
- Support for `ImageBlock` ([#419](https://github.com/torchbox/wagtail-grapple/pull/419)) @mgax

### Removed

Expand Down
18 changes: 11 additions & 7 deletions docs/getting-started/examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ Wagtail docs:

.. code-block:: python

from grapple.models import (
GraphQLRichText,
GraphQLString,
GraphQLStreamfield,
)
from django.db import models
from grapple.models import GraphQLRichText, GraphQLStreamfield, GraphQLString
from wagtail import blocks
from wagtail.models import Page
from wagtail.images.blocks import ImageBlock
from wagtail.fields import RichTextField, StreamField
from wagtail.admin.edit_handlers import FieldPanel


class BlogPage(Page):
Expand All @@ -28,7 +30,7 @@ Wagtail docs:
[
("heading", blocks.CharBlock(classname="full title")),
("paragraph", blocks.RichTextBlock()),
("image", ImageChooserBlock()),
("image", ImageBlock()),
]
)

Expand Down Expand Up @@ -62,10 +64,12 @@ something like:
summary
body {
rawValue
...on ImageChooserBlock {
...on ImageBlock {
image {
src
}
decorative
altText
}
}
}
Expand Down
18 changes: 18 additions & 0 deletions grapple/types/streamfield.py
Original file line number Diff line number Diff line change
Expand Up @@ -382,11 +382,29 @@ class Meta:
def resolve_image(self, info, **kwargs):
return self.value

class ImageBlock(graphene.ObjectType):
image = graphene.Field(get_image_type, required=False)
decorative = graphene.Boolean(required=False)
alt_text = graphene.String(required=False)

class Meta:
interfaces = (StreamFieldInterface,)

def resolve_image(self, info, **kwargs):
return self.value

def resolve_decorative(self, info, **kwargs):
return self.value.decorative

def resolve_alt_text(self, info, **kwargs):
return self.value.contextual_alt_text

registry.streamfield_blocks.update(
{
blocks.PageChooserBlock: PageChooserBlock,
wagtail.documents.blocks.DocumentChooserBlock: DocumentChooserBlock,
wagtail.images.blocks.ImageChooserBlock: ImageChooserBlock,
wagtail.images.blocks.ImageBlock: ImageBlock,
}
)

Expand Down
48 changes: 48 additions & 0 deletions tests/test_blog.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from testapp.factories import (
AdvertFactory,
BlogPageFactory,
ImageBlockFactory,
PersonFactory,
TextWithCallableBlockFactory,
)
Expand Down Expand Up @@ -64,6 +65,7 @@ def setUp(self):
),
("heading", "Test heading 2"),
("image", wagtail_factories.ImageFactory()),
("image_with_alt", ImageBlockFactory(is_decorative=False)),
("decimal", decimal.Decimal(1.2)),
("date", datetime.date.today()),
("datetime", datetime.datetime.now()),
Expand Down Expand Up @@ -202,6 +204,7 @@ def get_blocks_from_body(self, block_type, block_query="rawValue", page_id=None)
def test_blog_body_charblock(self):
block_type = "CharBlock"
query_blocks = self.get_blocks_from_body(block_type)
self.assertEqual(len(query_blocks), 2)

# Check output.
count = 0
Expand All @@ -217,6 +220,7 @@ def test_blog_body_charblock(self):
def test_streamfield_richtextblock(self):
block_type = "RichTextBlock"
query_blocks = self.get_blocks_from_body(block_type)
self.assertEqual(len(query_blocks), 1)

# Check the raw value.
count = 0
Expand Down Expand Up @@ -296,6 +300,7 @@ def test_blog_body_imagechooserblock(self):
}
""",
)
self.assertEqual(len(query_blocks), 1)

# Check output.
count = 0
Expand Down Expand Up @@ -340,6 +345,44 @@ def test_blog_body_imagechooserblock_in_streamblock(self):
except ValidationError:
self.fail(f"{url} is not a valid url")

def test_blog_body_imageblock(self):
block_type = "ImageBlock"
query_blocks = self.get_blocks_from_body(
block_type,
block_query="""
image {
id
src
}
decorative
altText
""",
)
self.assertEqual(len(query_blocks), 1)

# Check output.
count = 0
for block in self.blog_page.body:
if type(block.block).__name__ == block_type:
# Test the values
self.assertEqual(
query_blocks[count]["image"]["id"], str(block.value.id)
)
self.assertEqual(
query_blocks[count]["image"]["src"],
settings.BASE_URL + block.value.file.url,
)
self.assertEqual(
query_blocks[count]["decorative"], block.value.decorative
)
self.assertEqual(
query_blocks[count]["altText"], block.value.contextual_alt_text
)
# Increment the count
count += 1
# Check that we test all blocks that were returned.
self.assertEqual(len(query_blocks), count)

def test_blog_body_calloutblock(self):
block_type = "CalloutBlock"
query_blocks = self.get_blocks_from_body(block_type, block_query="text")
Expand All @@ -361,6 +404,7 @@ def test_blog_body_calloutblock(self):
def test_blog_body_decimalblock(self):
block_type = "DecimalBlock"
query_blocks = self.get_blocks_from_body(block_type)
self.assertEqual(len(query_blocks), 1)

# Check output.
count = 0
Expand All @@ -376,6 +420,7 @@ def test_blog_body_decimalblock(self):
def test_blog_body_dateblock(self):
block_type = "DateBlock"
query_blocks = self.get_blocks_from_body(block_type)
self.assertEqual(len(query_blocks), 1)

# Check output.
count = 0
Expand All @@ -395,6 +440,7 @@ def test_blog_body_datetimeblock(self):
block_type,
block_query=f'value(format: "{date_format_string}")',
)
self.assertEqual(len(query_blocks), 1)

# Check output.
count = 0
Expand Down Expand Up @@ -424,6 +470,7 @@ def test_blog_body_imagegalleryblock(self):
}
""",
)
self.assertEqual(len(query_blocks), 1)

# Check output.
count = 0
Expand Down Expand Up @@ -535,6 +582,7 @@ def test_blog_body_pagechooserblock(self):
query_blocks = self.get_blocks_from_body(
block_type, block_query=block_query, page_id=another_blog_post.id
)
self.assertEqual(len(query_blocks), 1)

# Check output.
count = 0
Expand Down
14 changes: 13 additions & 1 deletion tests/test_models_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from django.test import TestCase
from wagtail.blocks.field_block import PageChooserBlock
from wagtail.documents.blocks import DocumentChooserBlock
from wagtail.images.blocks import ImageChooserBlock
from wagtail.images.blocks import ImageBlock, ImageChooserBlock
from wagtail.snippets.blocks import SnippetChooserBlock

from grapple import registry
Expand Down Expand Up @@ -232,6 +232,18 @@ def test_image_chooser_block_value_field_not_required(self):
self.assertIsInstance(field, graphene.types.field.Field)
self.assertNotIsInstance(field.type, graphene.NonNull)

def test_image_block_value_field_not_required(self):
"""
Test that the ImageBlock image field is nullable in the GraphQL
schema.
"""
block = registry.registry.streamfield_blocks[ImageBlock]
field = block.image

# Check that field is not required by asserting type isn't `NonNull`
self.assertIsInstance(field, graphene.types.field.Field)
self.assertNotIsInstance(field.type, graphene.NonNull)

def test_page_chooser_block_value_field_not_required(self):
"""
Test that the PageChooserBlock page field is nullable in the GraphQL
Expand Down
6 changes: 5 additions & 1 deletion tests/testapp/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
from django.utils.text import slugify
from wagtail import blocks
from wagtail.embeds.blocks import EmbedBlock
from wagtail.images.blocks import ImageChooserBlock
from wagtail.images.blocks import (
ImageBlock,
ImageChooserBlock,
)
from wagtail.snippets.blocks import SnippetChooserBlock

from grapple.helpers import register_streamfield_block
Expand Down Expand Up @@ -311,6 +314,7 @@ class StreamFieldBlock(blocks.StreamBlock):
heading = blocks.CharBlock(classname="full title")
paragraph = blocks.RichTextBlock()
image = ImageChooserBlock()
image_with_alt = ImageBlock()
decimal = blocks.DecimalBlock()
date = blocks.DateBlock()
datetime = blocks.DateTimeBlock()
Expand Down
14 changes: 14 additions & 0 deletions tests/testapp/factories.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import datetime

import factory
import wagtail.images.blocks as image_blocks
import wagtail_factories

from django.core.exceptions import ValidationError
Expand Down Expand Up @@ -28,6 +29,18 @@


# START: Block Factories


# TODO: Contribute upstream: https://github.com/wagtail/wagtail-factories/issues/97
class ImageBlockFactory(wagtail_factories.StructBlockFactory):
image = factory.SubFactory(wagtail_factories.ImageChooserBlockFactory)
decorative = factory.Faker("boolean")
alt_text = factory.Sequence(lambda n: f"Alt text {n}")

class Meta:
model = image_blocks.ImageBlock


class DateBlockFactory(wagtail_factories.blocks.BlockFactory):
class Meta:
model = blocks.DateBlock
Expand Down Expand Up @@ -130,6 +143,7 @@ class BlogPageFactory(wagtail_factories.PageFactory):
"heading": wagtail_factories.CharBlockFactory,
"paragraph": RichTextBlockFactory,
"image": wagtail_factories.ImageChooserBlockFactory,
"image_with_alt": ImageBlockFactory,
"decimal": DecimalBlockFactory,
"date": DateBlockFactory,
"datetime": DateTimeBlockFactory,
Expand Down
Loading
Loading