class ChartBlock(blocks.StructBlock): title = blocks.CharBlock( label='Title', max_length=120, default='Chart Title', ) chart_type = ChartChoiceBlock( label='Chart Type', default='bar' ) labels = blocks.ListBlock( blocks.CharBlock(max_length=40, label="Label", default='Label'), label='Chart Labels', ) datasets = blocks.ListBlock( ChartDataset(), label='Dataset', ) def get_context(self, value, parent_context=None): context = super().get_context(value, parent_context=parent_context) value['datasets'] = json.dumps(value['datasets']) return context class Meta: template = 'wagtail_blocks/chart.html' icon = 'fa-bar-chart'
def test_include_block_tag_with_streamvalue(self): """ The include_block tag should be able to render a StreamValue's template while keeping the parent template's context """ block = blocks.StreamBlock( [ ( "heading", blocks.CharBlock( template="tests/jinja2/heading_block.html"), ), ("paragraph", blocks.CharBlock()), ], template="tests/jinja2/stream_with_language.html", ) stream_value = block.to_python([{ "type": "heading", "value": "Bonjour" }]) result = render_to_string( "tests/jinja2/include_block_test.html", { "test_block": stream_value, "language": "fr", }, ) self.assertIn( '<div class="heading" lang="fr"><h1 lang="fr">Bonjour</h1></div>', result)
class ResourcesPage(Page): resources = StreamField( [ ( "resource", blocks.StreamBlock( [ ("resource_name", blocks.CharBlock()), ("information", blocks.RichTextBlock()), ( "structured_info_block", blocks.StreamBlock( [ ("heading", blocks.CharBlock()), ("body", blocks.RichTextBlock()), ] ), ), ] ), ) ], use_json_field=True, ) content_panels = Page.content_panels + [ FieldPanel("resources"), ]
def test_internal_type(self): text = StreamField([("paragraph", blocks.CharBlock())], use_json_field=False) json = StreamField([("paragraph", blocks.CharBlock())], use_json_field=True) self.assertEqual(text.get_internal_type(), "TextField") self.assertEqual(json.get_internal_type(), "JSONField")
def test_include_block_tag_with_filtered_value(self): """ The block parameter on include_block tag should support complex values including filters, e.g. {% include_block foo|default:123 %} """ block = blocks.CharBlock(template="tests/jinja2/heading_block.html") bound_block = block.bind("bonjour") result = render_to_string( "tests/jinja2/include_block_test_with_filter.html", { "test_block": bound_block, "language": "fr", }, ) self.assertIn('<body><h1 lang="fr">bonjour</h1></body>', result) result = render_to_string( "tests/jinja2/include_block_test_with_filter.html", { "test_block": None, "language": "fr", }, ) self.assertIn("<body>999</body>", result)
class DeprecatedStreamModel(models.Model): body = StreamField( [ ("heading", blocks.CharBlock()), ("rich text", blocks.RichTextBlock()), ], )
def test_block_render_result_is_safe(self): """ Ensure that any results of template rendering in block.render are marked safe so that they don't get double-escaped when inserted into a parent template (#2541) """ stream_block = blocks.StreamBlock([ ("paragraph", blocks.CharBlock(template="tests/jinja2/paragraph.html")) ]) stream_value = stream_block.to_python([ { "type": "paragraph", "value": "hello world" }, ]) result = render_to_string( "tests/jinja2/stream.html", { "value": stream_value, }, ) self.assertIn("<p>hello world</p>", result)
def test_render_within_structblock(self, get_embed): """ When rendering the value of an EmbedBlock directly in a template (as happens when accessing it as a child of a StructBlock), the proper embed output should be rendered, not the URL. """ get_embed.return_value = Embed(html="<h1>Hello world!</h1>") block = blocks.StructBlock([ ("title", blocks.CharBlock()), ("embed", EmbedBlock()), ]) block_val = block.to_python({ "title": "A test", "embed": "http://www.example.com/foo" }) temp = template.Template("embed: {{ self.embed }}") context = template.Context({"self": block_val}) result = temp.render(context) self.assertIn("<h1>Hello world!</h1>", result) # Check that get_embed was called correctly get_embed.assert_any_call("http://www.example.com/foo", None, None)
class SectionBlock(blocks.StructBlock): title = blocks.CharBlock() body = blocks.RichTextBlock() class Meta: icon = "form" template = "tests/blocks/section_block.html"
def test_blank_false_is_implied_by_default(self): # passing a block list field = StreamField([("paragraph", blocks.CharBlock())], use_json_field=self.use_json_field) self.assertTrue(field.stream_block.required) with self.assertRaises(StreamBlockValidationError): field.stream_block.clean([]) class MyStreamBlock(blocks.StreamBlock): paragraph = blocks.CharBlock() class Meta: required = False # passing a block instance field = StreamField(MyStreamBlock(), use_json_field=self.use_json_field) self.assertTrue(field.stream_block.required) with self.assertRaises(StreamBlockValidationError): field.stream_block.clean([]) field = StreamField(MyStreamBlock(required=False), use_json_field=self.use_json_field) self.assertTrue(field.stream_block.required) with self.assertRaises(StreamBlockValidationError): field.stream_block.clean([]) # passing a block class field = StreamField(MyStreamBlock, use_json_field=self.use_json_field) self.assertTrue(field.stream_block.required) with self.assertRaises(StreamBlockValidationError): field.stream_block.clean([])
def test_blank_field_is_not_required(self): # passing a block list field = StreamField( [("paragraph", blocks.CharBlock())], blank=True, use_json_field=self.use_json_field, ) self.assertFalse(field.stream_block.required) field.stream_block.clean([]) # no validation error on empty stream class MyStreamBlock(blocks.StreamBlock): paragraph = blocks.CharBlock() class Meta: required = True # passing a block instance field = StreamField(MyStreamBlock(), blank=True, use_json_field=self.use_json_field) self.assertFalse(field.stream_block.required) field.stream_block.clean([]) # no validation error on empty stream field = StreamField(MyStreamBlock(required=True), blank=True, use_json_field=self.use_json_field) self.assertFalse(field.stream_block.required) field.stream_block.clean([]) # no validation error on empty stream # passing a block class field = StreamField(MyStreamBlock, blank=True, use_json_field=self.use_json_field) self.assertFalse(field.stream_block.required) field.stream_block.clean([]) # no validation error on empty stream
class InvalidStreamModel(models.Model): body = StreamField( [ ("heading", blocks.CharBlock()), ("rich text", blocks.RichTextBlock()), ], use_json_field=self.use_json_field, )
class TestStreamBlock(blocks.StreamBlock): heading = blocks.CharBlock() paragraph = blocks.RichTextBlock() class Meta: min_num = 2 max_num = 5 block_counts = {"heading": {"max_num": 1}}
class SingleImageWithText(blocks.StructBlock): image = ImageChooserBlock( label='Image', ) text = blocks.CharBlock( label='Text', max_length=200, )
def setUp(self): self.block = TypedTableBlock([ ("text", blocks.CharBlock()), ( "country", CountryChoiceBlock(choices=[ ("be", "Belgium"), ("fr", "France"), ("nl", "Netherlands"), ]), ), ]) self.form_data = { "table-column-count": "2", "table-row-count": "3", "table-column-0-type": "country", "table-column-0-order": "0", "table-column-0-deleted": "", "table-column-0-heading": "Country", "table-column-1-type": "text", "table-column-1-order": "1", "table-column-1-deleted": "", "table-column-1-heading": "Description", "table-row-0-order": "1", "table-row-0-deleted": "", "table-cell-0-0": "fr", "table-cell-0-1": "A large country with baguettes", "table-row-1-order": "0", "table-row-1-deleted": "", "table-cell-1-0": "nl", "table-cell-1-1": "A small country with stroopwafels", "table-row-2-order": "2", "table-row-2-deleted": "1", "table-cell-2-0": "be", "table-cell-2-1": "A small country with sprouts", } self.db_data = { "columns": [ { "type": "country", "heading": "Country" }, { "type": "text", "heading": "Description" }, ], "rows": [ { "values": ["nl", "A small country with stroopwafels"] }, { "values": ["fr", "A large country with baguettes"] }, ], }
class ListBlock(blocks.StructBlock): content = blocks.ListBlock( blocks.CharBlock(), label='Items', ) class Meta: template = 'wagtail_blocks/list.html' icon = "list-ul"
class ChartDataset(blocks.StructBlock): label = blocks.CharBlock( label='Dataset Label', max_length=120, default='Dataset #1', ) dataset_data = blocks.ListBlock( blocks.IntegerBlock(), label='Data', default='0', )
class ImageTextOverlayBlock(blocks.StructBlock): image = ImageChooserBlock( label='Image', ) text = blocks.CharBlock( label='Text', max_length=200, ) class Meta: template = 'wagtail_blocks/image_text_overlay.html' icon = 'fa-image'
class HeaderBlock(blocks.StructBlock): header = HeaderChoiceBlock( label='Header Size', ) text = blocks.CharBlock( label='Text', max_length=50, ) class Meta: template = 'wagtail_blocks/header.html' icon = "title"
class SingleListImage(blocks.StructBlock): image = ImageChooserBlock( label='Image', ) title = blocks.CharBlock( label='Title', max_length=200, ) text = blocks.CharBlock( label='Text', max_length=200, ) link_text = blocks.CharBlock( label='Link Text', max_length=200, required=False, ) link_url = blocks.URLBlock( label='Link URL', max_length=200, required=False, )
def test_include_block_tag_with_additional_variable(self): """ The include_block tag should be able to pass local variables from parent context to the child context """ block = blocks.CharBlock(template="tests/blocks/heading_block.html") bound_block = block.bind("bonjour") result = render_to_string( "tests/jinja2/include_block_tag_with_additional_variable.html", {"test_block": bound_block}, ) self.assertIn('<body><h1 class="important">bonjour</h1></body>', result)
def test_include_block_tag_with_boundblock(self): """ The include_block tag should be able to render a BoundBlock's template while keeping the parent template's context """ block = blocks.CharBlock(template="tests/jinja2/heading_block.html") bound_block = block.bind("bonjour") result = render_to_string( "tests/jinja2/include_block_test.html", { "test_block": bound_block, "language": "fr", }, ) self.assertIn('<body><h1 lang="fr">bonjour</h1></body>', result)
class LinkBlock(blocks.StructBlock): title = blocks.CharBlock() url = blocks.URLBlock() def get_context(self, value, parent_context=None): context = super().get_context(value, parent_context) context["classname"] = ( parent_context["classname"] if value["title"] == "Torchbox" else "normal" ) return context def get_form_context(self, value, prefix="", errors=None): context = super().get_form_context(value, prefix=prefix, errors=errors) context["extra_var"] = "Hello from get_form_context!" return context class Meta: icon = "site" template = "tests/blocks/link_block.html" form_template = "tests/block_forms/link_block.html"
class MapBlock(blocks.StructBlock): marker_title = blocks.CharBlock(max_length=120, default="Marker Title 'This will be updated after you save changes.'") marker_description = blocks.RichTextBlock() zoom_level = blocks.IntegerBlock(min_value=0, max_value=18, default='2', required=False) location_x = blocks.FloatBlock(default='35.0', required=False) location_y = blocks.FloatBlock(default='0.16', required=False) marker_x = blocks.FloatBlock(default='51.5', required=False) marker_y = blocks.FloatBlock(default='-0.09', required=False) @property def media(self): return forms.Media( js=["https://unpkg.com/[email protected]/dist/leaflet.js"], css={'all': ["https://unpkg.com/[email protected]/dist/leaflet.css"]} ) class Meta: form_template = 'wagtail_blocks/admin_blocks/map.html' template = 'wagtail_blocks/map.html' icon = "fa-globe"
def test_use_json_field_warning(self): message = "StreamField must explicitly set use_json_field argument to True/False instead of None." with self.assertWarnsMessage(RemovedInWagtail50Warning, message): StreamField([("paragraph", blocks.CharBlock())])
class MyStreamBlock(blocks.StreamBlock): paragraph = blocks.CharBlock() class Meta: required = True
class RelatedStoryBlock(blocks.StructBlock): heading = blocks.CharBlock(required=False) copy = blocks.TextBlock(required=False) image = ImageChooserBlock(required=False)
class Migration(migrations.Migration): initial = True dependencies = [ ("wagtailcore", "0066_collection_management_permissions"), ] operations = [ migrations.CreateModel( name="ClassicGeoPage", fields=[ ( "page_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="wagtailcore.page", ), ), ("address", models.CharField(blank=True, max_length=250, null=True)), ("location", models.CharField(blank=True, max_length=250, null=True)), ], options={ "abstract": False, }, bases=("wagtailcore.page", ), ), migrations.CreateModel( name="ClassicGeoPageWithLeaflet", fields=[ ( "page_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="wagtailcore.page", ), ), ( "address", models.CharField( blank=True, help_text="Search powered by MapBox", max_length=250, null=True, ), ), ("location", models.CharField(blank=True, max_length=250, null=True)), ], options={ "abstract": False, }, bases=("wagtailcore.page", ), ), migrations.CreateModel( name="ClassicGeoPageWithLeafletAndZoom", fields=[ ( "page_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="wagtailcore.page", ), ), ( "address", models.CharField( blank=True, help_text="Search powered by Nominatim", max_length=250, null=True, ), ), ("location", models.CharField(blank=True, max_length=250, null=True)), ("zoom", models.SmallIntegerField(blank=True, null=True)), ], options={ "abstract": False, }, bases=("wagtailcore.page", ), ), migrations.CreateModel( name="ClassicGeoPageWithZoom", fields=[ ( "page_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="wagtailcore.page", ), ), ("address", models.CharField(blank=True, max_length=250, null=True)), ("location", models.CharField(blank=True, max_length=250, null=True)), ("zoom", models.SmallIntegerField(blank=True, null=True)), ], options={ "abstract": False, }, bases=("wagtailcore.page", ), ), migrations.CreateModel( name="GeoLocation", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("title", models.CharField(max_length=255)), ("address", models.CharField(blank=True, max_length=250, null=True)), ("zoom", models.SmallIntegerField(blank=True, null=True)), ( "location", django.contrib.gis.db.models.fields.PointField(blank=True, null=True, srid=4326), ), ], ), migrations.CreateModel( name="GeoLocationWithLeaflet", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("title", models.CharField(max_length=255)), ( "address", models.CharField( blank=True, help_text="Search powered by Nominatim", max_length=250, null=True, ), ), ("zoom", models.SmallIntegerField(blank=True, null=True)), ( "location", django.contrib.gis.db.models.fields.PointField(blank=True, null=True, srid=4326), ), ], ), migrations.CreateModel( name="GeoPage", fields=[ ( "page_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="wagtailcore.page", ), ), ("address", models.CharField(blank=True, max_length=250, null=True)), ( "location", django.contrib.gis.db.models.fields.PointField(blank=True, null=True, srid=4326), ), ], options={ "abstract": False, }, bases=("wagtailcore.page", ), ), migrations.CreateModel( name="GeoPageWithLeaflet", fields=[ ( "page_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="wagtailcore.page", ), ), ( "address", models.CharField( blank=True, help_text="Search powered by Nominatim", max_length=250, null=True, ), ), ( "location", django.contrib.gis.db.models.fields.PointField(blank=True, null=True, srid=4326), ), ], options={ "abstract": False, }, bases=("wagtailcore.page", ), ), migrations.CreateModel( name="GeoStreamPage", fields=[ ( "page_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="wagtailcore.page", ), ), ( "body", wagtail_fields.StreamField([ ("map", wagtailgeowidget.blocks.GoogleMapsBlock()), ( "map_with_leaflet", wagtailgeowidget.blocks.LeafletBlock(), ), ( "map_struct", wagtail_blocks.StructBlock( [ ( "address", wagtailgeowidget.blocks. GeoAddressBlock(required=True), ), ( "map", wagtailgeowidget.blocks. GoogleMapsBlock( address_field="address"), ), ], icon="user", ), ), ( "map_struct_with_deprecated_geopanel", wagtail_blocks.StructBlock( [ ( "address", wagtail_blocks.CharBlock( required=True), ), ( "map", wagtailgeowidget.blocks.GeoBlock( address_field="address"), ), ], icon="user", ), ), ( "map_struct_with_leaflet", wagtail_blocks.StructBlock( [ ( "address", wagtailgeowidget.blocks. GeoAddressBlock(required=True), ), ( "map", wagtailgeowidget.blocks.LeafletBlock( address_field="address"), ), ], icon="user", ), ), ( "map_struct_with_zoom", wagtail_blocks.StructBlock( [ ( "address", wagtailgeowidget.blocks. GeoAddressBlock(required=True), ), ( "zoom", wagtailgeowidget.blocks.GeoZoomBlock( required=False), ), ( "map", wagtailgeowidget.blocks. GoogleMapsBlock( address_field="address", zoom_field="zoom", ), ), ], icon="user", ), ), ( "map_struct_leaflet_with_zoom", wagtail_blocks.StructBlock( [ ( "address", wagtailgeowidget.blocks. GeoAddressBlock(required=True), ), ( "zoom", wagtailgeowidget.blocks.GeoZoomBlock( required=False), ), ( "map", wagtailgeowidget.blocks.LeafletBlock( address_field="address", zoom_field="zoom", ), ), ], icon="user", ), ), ]), ), ], options={ "abstract": False, }, bases=("wagtailcore.page", ), ), migrations.CreateModel( name="GeoPageWithLeafletRelatedLocations", fields=[ ( "geolocationwithleaflet_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="geopage.geolocationwithleaflet", ), ), ( "sort_order", models.IntegerField(blank=True, editable=False, null=True), ), ( "page", modelcluster.fields.ParentalKey( on_delete=django.db.models.deletion.CASCADE, related_name="related_locations", to="geopage.geopagewithleaflet", ), ), ], options={ "ordering": ["sort_order"], "abstract": False, }, bases=("geopage.geolocationwithleaflet", models.Model), ), migrations.CreateModel( name="GeoPageRelatedLocations", fields=[ ( "geolocation_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="geopage.geolocation", ), ), ( "sort_order", models.IntegerField(blank=True, editable=False, null=True), ), ( "page", modelcluster.fields.ParentalKey( on_delete=django.db.models.deletion.CASCADE, related_name="related_locations", to="geopage.geopage", ), ), ], options={ "ordering": ["sort_order"], "abstract": False, }, bases=("geopage.geolocation", models.Model), ), ]
class Migration(migrations.Migration): dependencies = [ ('tests', '0012_pagewithrelatedpages'), ] operations = [ migrations.AlterField( model_name='pagewithstreamfield', name='body', field=wagtail_fields.StreamField([('link_block', wagtail_blocks.StructBlock([('page', wagtail_blocks.PageChooserBlock(required=False)), ('text', wagtail_blocks.CharBlock(max_length=250))])), ('page', wagtail_blocks.PageChooserBlock()), ('stream', wagtail_blocks.StreamBlock([('page', wagtail_blocks.PageChooserBlock())])), ('rich_text', wagtail_blocks.RichTextBlock()), ('list_of_pages', wagtail_blocks.ListBlock(wagtail_blocks.PageChooserBlock()))], blank=True, verbose_name='Page body'), ), ]
class GeoStreamPage(Page): body = StreamField([ ("map", GoogleMapsBlock()), ("map_with_leaflet", LeafletBlock()), ( "map_struct", blocks.StructBlock( [ ("address", GeoAddressBlock(required=True)), ("map", GoogleMapsBlock(address_field="address")), ], icon="user", ), ), ( "map_struct_with_deprecated_geopanel", blocks.StructBlock( [ ("address", blocks.CharBlock(required=True)), ("map", GeoBlock(address_field="address")), ], icon="user", ), ), ( "map_struct_with_leaflet", blocks.StructBlock( [ ("address", GeoAddressBlock(required=True)), ("map", LeafletBlock(address_field="address")), ], icon="user", ), ), ( "map_struct_with_zoom", blocks.StructBlock( [ ("address", GeoAddressBlock(required=True)), ("zoom", GeoZoomBlock(required=False)), ( "map", GoogleMapsBlock(address_field="address", zoom_field="zoom"), ), ], icon="user", ), ), ( "map_struct_leaflet_with_zoom", blocks.StructBlock( [ ("address", GeoAddressBlock(required=True)), ("zoom", GeoZoomBlock(required=False)), ( "map", LeafletBlock(address_field="address", zoom_field="zoom"), ), ], icon="user", ), ), ]) content_panels = Page.content_panels + [ StreamFieldPanel("body"), ] def get_context(self, request): data = super(GeoStreamPage, self).get_context(request) return data