wagtail-deprecated / wagtail-react-streamfield

Powerful field for inserting multiple blocks with nesting. (NO LONGER MAINTAINED - See Wagtail 2.13 Release Notes)

Home Page:https://wagtail.github.io/react-streamfield/public/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

custom to_python function causes keyerror

unexceptable opened this issue · comments

Pardon the rather odd issue title, but I can't exactly think of a good way to summarise my particular issue.

I've got some code that works exactly as I expect it to using the current streamfield, both when rendering the page, and in the admin. Using this new streamfield that same code breaks, thus not quite being a drop in replacement for me. I don't mind if I need to change this code, especially if this new streamfield will become the version in core Wagtail going forward, but ideally I'd like to achieve something similar to what I've got.

I have a link block that asks for a page or a url, but to make the front end easier, and to make it so our front end devs don't need to handle this same logic in every template, I have a 'link' attribute that exists on the block and it returns either the page or the url depending on which has been given by the content editor. The template then just uses 'link' and the front end dev doesn't ever have to know if the link might be to a page, a url, or a document (another variant of this block).

Mostly, I'm trying to mimic this pattern but with blocks:

class LinkFields(models.Model):
    link_page = models.ForeignKey(
        'wagtailcore.Page',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+'
    )
    link_external = models.URLField("External link", blank=True)

    @property
    def link(self):
        if self.link_page:
            return self.link_page.url
        else:
            return self.link_external

    panels = [
        PageChooserPanel('link_page'),
        FieldPanel('link_external'),
    ]

    class Meta:
        abstract = True

So now the question is, are you doing anything different with this new Streamfield that I've missed that I can adapt my code to, or is this due to a part of the existing Streamfield logic you missed?

You can probably put together a simpler example using my LinkBlock, but below is what I'm doing, how I'm using it, and the error I got when trying to load the admin page.

class LinkBlock(StructBlock):
    _link_page = PageChooserBlock(required=False)
    _link_external = URLBlock(required=False)

    def to_python(self, value):
        page = self.child_blocks['_link_page'].to_python(value['_link_page'])
        external_link = self.child_blocks['_link_external'].to_python(
            value['_link_external'])

        if page:
            link = page.url
        else:
            link = external_link

        return StructValue(self, [
            ('_link_page', page),
            ('_link_external', external_link),
            ('link', link)
        ])

    def get_prep_value(self, value):
        if 'link' in value:
            value.pop('link')
        return super(LinkBlock, self).get_prep_value(value)

    class Meta:
        icon = "link"


class FeatureBlock(StructBlock):
    title = CharBlock(max_length=100, label="Title")
    description = TextBlock(max_length=300, label="Description")
    image = ImageChooserBlock(
        label="Image (can be empty), must be square (1:1)", required=False)
    read_more = LinkBlock(label="Read more link")

    class Meta:
        icon = "radio-full"


class BenefitFeatureListBase(StructBlock):
    title = CharBlock(max_length=100, label="Title", required=False)
    description = TextBlock(
        max_length=200, label="Description", required=False)
    image_size = IntegerBlock(
        max_value=200, min_value=80, default=100,
        help_text="Image width and height (1:1)")

    class Meta:
        abstract = True


class FeatureListBlock(BenefitFeatureListBase):
    features = ListBlock(FeatureBlock())

    class Meta:
        icon = "list-ul"
        template = 'core/blocks/feature_list_block.html'
{% load wagtailimages_tags %}

<div class="feature-list-wrapper">
  <div class="feature-list">
    <div class="container">
      {% if value.title %}
        <h3 class="feature-list-title">
          {{ value.title }}
          <a href="#{{ value.title|slugify }}" aria-hidden="true"
             class="title-anchor" id="{{ value.title|slugify }}">
            <span class="icon icon-link"></span>
          </a>
        </h3>
      {% endif %}

      {% if value.description %}
        <p class="feature-list-description">{{ value.description }}</p>
      {% endif %}

      <div class="row">
          {% for feature in value.features %}
              <div class="col-sm-6 feature-list-item">
                  {% if feature.image %}
                    {% image feature.image fill-200x200 as feature_img %}

                    {% if feature_img.width >= value.image_size %}
                    <img src="{{ feature_img.url }}" width="{{ value.image_size }}"
                         height="{{ value.image_size }}" alt="{{ feature_img.alt }}" class="feature-image" />
                    {% else %}
                    <img src="{{ feature_img.url }}" width="{{ feature_img.width }}"
                         height="{{ feature_img.height }}" alt="{{ feature_img.alt }}" class="feature-image" />
                    {% endif %}
                  {% endif %}
                  <h4>{{ feature.title }}</h4>
                  <p> {{ feature.description }} </p>
                  {% if read_more.link %}
                    <a href="{{ read_more.link }}" class="invisible-link">
                      <span class="sr-only">Read more</span>
                    </a>
                  {% endif %}
              </div>
          {% if forloop.counter|divisibleby:2 %}
          </div>
          <div class="row">
          {% endif %}
          {% endfor %}
      </div>
    </div>
  </div>
</div>
Internal Server Error: /admin/pages/3/edit/
Traceback (most recent call last):
  File "/usr/local/lib/python3.6/site-packages/django/core/handlers/exception.py", line 35, in inner
    response = get_response(request)
  File "/usr/local/lib/python3.6/site-packages/django/core/handlers/base.py", line 128, in _get_response
    response = self.process_exception_by_middleware(e, request)
  File "/usr/local/lib/python3.6/site-packages/django/core/handlers/base.py", line 126, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/usr/local/lib/python3.6/site-packages/django/views/decorators/cache.py", line 31, in _cache_controlled
    response = viewfunc(request, *args, **kw)
  File "/usr/local/lib/python3.6/site-packages/wagtail/admin/urls/__init__.py", line 102, in wrapper
    return view_func(request, *args, **kwargs)
  File "/usr/local/lib/python3.6/site-packages/wagtail/admin/decorators.py", line 34, in decorated_view
    return view_func(request, *args, **kwargs)
  File "/usr/local/lib/python3.6/site-packages/wagtail/admin/views/pages.py", line 535, in edit
    'has_unsaved_changes': has_unsaved_changes,
  File "/usr/local/lib/python3.6/site-packages/django/shortcuts.py", line 36, in render
    content = loader.render_to_string(template_name, context, request, using=using)
  File "/usr/local/lib/python3.6/site-packages/django/template/loader.py", line 62, in render_to_string
    return template.render(context, request)
  File "/usr/local/lib/python3.6/site-packages/django/template/backends/django.py", line 61, in render
    return self.template.render(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/base.py", line 175, in render
    return self._render(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/base.py", line 167, in _render
    return self.nodelist.render(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/base.py", line 943, in render
    bit = node.render_annotated(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/base.py", line 910, in render_annotated
    return self.render(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/loader_tags.py", line 155, in render
    return compiled_parent._render(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/base.py", line 167, in _render
    return self.nodelist.render(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/base.py", line 943, in render
    bit = node.render_annotated(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/base.py", line 910, in render_annotated
    return self.render(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/loader_tags.py", line 155, in render
    return compiled_parent._render(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/base.py", line 167, in _render
    return self.nodelist.render(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/base.py", line 943, in render
    bit = node.render_annotated(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/base.py", line 910, in render_annotated
    return self.render(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/loader_tags.py", line 155, in render
    return compiled_parent._render(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/base.py", line 167, in _render
    return self.nodelist.render(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/base.py", line 943, in render
    bit = node.render_annotated(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/base.py", line 910, in render_annotated
    return self.render(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/loader_tags.py", line 67, in render
    result = block.nodelist.render(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/base.py", line 943, in render
    bit = node.render_annotated(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/base.py", line 910, in render_annotated
    return self.render(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/loader_tags.py", line 67, in render
    result = block.nodelist.render(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/base.py", line 943, in render
    bit = node.render_annotated(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/base.py", line 910, in render_annotated
    return self.render(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/base.py", line 993, in render
    output = self.filter_expression.resolve(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/base.py", line 676, in resolve
    obj = self.var.resolve(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/base.py", line 802, in resolve
    value = self._resolve_lookup(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/base.py", line 864, in _resolve_lookup
    current = current()
  File "/usr/local/lib/python3.6/site-packages/wagtail/admin/edit_handlers.py", line 230, in render_form_content
    return mark_safe(self.render_as_object() + self.render_missing_fields())
  File "/usr/local/lib/python3.6/site-packages/wagtail/admin/edit_handlers.py", line 198, in render_as_object
    return self.render()
  File "/usr/local/lib/python3.6/site-packages/wagtail/admin/edit_handlers.py", line 299, in render
    'self': self
  File "/usr/local/lib/python3.6/site-packages/django/template/loader.py", line 62, in render_to_string
    return template.render(context, request)
  File "/usr/local/lib/python3.6/site-packages/django/template/backends/django.py", line 61, in render
    return self.template.render(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/base.py", line 175, in render
    return self._render(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/base.py", line 167, in _render
    return self.nodelist.render(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/base.py", line 943, in render
    bit = node.render_annotated(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/base.py", line 910, in render_annotated
    return self.render(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/defaulttags.py", line 211, in render
    nodelist.append(node.render_annotated(context))
  File "/usr/local/lib/python3.6/site-packages/django/template/base.py", line 910, in render_annotated
    return self.render(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/base.py", line 993, in render
    output = self.filter_expression.resolve(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/base.py", line 676, in resolve
    obj = self.var.resolve(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/base.py", line 802, in resolve
    value = self._resolve_lookup(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/base.py", line 864, in _resolve_lookup
    current = current()
  File "/usr/local/lib/python3.6/site-packages/wagtail/admin/edit_handlers.py", line 198, in render_as_object
    return self.render()
  File "/usr/local/lib/python3.6/site-packages/wagtail/admin/edit_handlers.py", line 299, in render
    'self': self
  File "/usr/local/lib/python3.6/site-packages/django/template/loader.py", line 62, in render_to_string
    return template.render(context, request)
  File "/usr/local/lib/python3.6/site-packages/django/template/backends/django.py", line 61, in render
    return self.template.render(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/base.py", line 175, in render
    return self._render(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/base.py", line 167, in _render
    return self.nodelist.render(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/base.py", line 943, in render
    bit = node.render_annotated(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/base.py", line 910, in render_annotated
    return self.render(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/defaulttags.py", line 211, in render
    nodelist.append(node.render_annotated(context))
  File "/usr/local/lib/python3.6/site-packages/django/template/base.py", line 910, in render_annotated
    return self.render(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/base.py", line 993, in render
    output = self.filter_expression.resolve(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/base.py", line 676, in resolve
    obj = self.var.resolve(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/base.py", line 802, in resolve
    value = self._resolve_lookup(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/base.py", line 864, in _resolve_lookup
    current = current()
  File "/usr/local/lib/python3.6/site-packages/wagtail/admin/edit_handlers.py", line 457, in render_as_object
    'field': self.bound_field,
  File "/usr/local/lib/python3.6/site-packages/django/template/loader.py", line 62, in render_to_string
    return template.render(context, request)
  File "/usr/local/lib/python3.6/site-packages/django/template/backends/django.py", line 61, in render
    return self.template.render(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/base.py", line 175, in render
    return self._render(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/base.py", line 167, in _render
    return self.nodelist.render(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/base.py", line 943, in render
    bit = node.render_annotated(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/base.py", line 910, in render_annotated
    return self.render(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/loader_tags.py", line 194, in render
    return template.render(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/base.py", line 177, in render
    return self._render(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/base.py", line 167, in _render
    return self.nodelist.render(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/base.py", line 943, in render
    bit = node.render_annotated(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/base.py", line 910, in render_annotated
    return self.render(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/loader_tags.py", line 58, in render
    result = self.nodelist.render(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/base.py", line 943, in render
    bit = node.render_annotated(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/base.py", line 910, in render_annotated
    return self.render(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/base.py", line 993, in render
    output = self.filter_expression.resolve(context)
  File "/usr/local/lib/python3.6/site-packages/django/template/base.py", line 703, in resolve
    new_obj = func(obj, *arg_vals)
  File "/usr/local/lib/python3.6/site-packages/wagtail/admin/templatetags/wagtailadmin_tags.py", line 244, in render_with_errors
    return bound_field.as_widget()
  File "/usr/local/lib/python3.6/site-packages/django/forms/boundfield.py", line 118, in as_widget
    **kwargs
  File "/usr/local/lib/python3.6/site-packages/wagtail/core/blocks/base.py", line 505, in render
    return self.render_with_errors(name, value, attrs=attrs, errors=None)
  File "/usr/local/lib/python3.6/site-packages/wagtail_react_streamfield/widget.py", line 153, in render_with_errors
    errors=errors),
  File "/usr/local/lib/python3.6/site-packages/wagtail_react_streamfield/widget.py", line 74, in prepare_value
    for i, child_block_data in enumerate(value)]
  File "/usr/local/lib/python3.6/site-packages/wagtail_react_streamfield/widget.py", line 74, in <listcomp>
    for i, child_block_data in enumerate(value)]
  File "/usr/local/lib/python3.6/site-packages/wagtail_react_streamfield/widget.py", line 59, in prepare_value
    for k, v in value.items()]
  File "/usr/local/lib/python3.6/site-packages/wagtail_react_streamfield/widget.py", line 59, in <listcomp>
    for k, v in value.items()]
  File "/usr/local/lib/python3.6/site-packages/wagtail_react_streamfield/widget.py", line 59, in prepare_value
    for k, v in value.items()]
  File "/usr/local/lib/python3.6/site-packages/wagtail_react_streamfield/widget.py", line 59, in <listcomp>
    for k, v in value.items()]
KeyError: 'link'

Interesting use case.
What’s causing the issue is that I’m iterating over the StreamValue to parse data and convert it to the new StreamField format (mostly the same format, with little additions).
And obviously, I’m doing it by comparing the data with the defined blocks, that’s why it fails, because link is not a child block of your StructBlock.

I just need to treat "lost" data like this as if it was coming from a CharBlock, like what’s currently done by Wagtail.

ty!

Works as expected when I build from this commit.

Fix released in 0.8.3.