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

Translated preview crashes when ChooserBlock's related object does not exist anymore #841

Open
th3hamm0r opened this issue Dec 20, 2024 · 2 comments

Comments

@th3hamm0r
Copy link
Contributor

Normally, if you use a ChooserBlock (like a page chooser) in a StreamField, if the chosen page gets deleted, the field still renders in the frontend and does not crash.
In the backend, if it is a required field, the now empty chooser would trigger a validation error.

Unfortunately, this is not the case with wagtail-localize and translated pages/objects. If for example the chosen page gets deleted, the preview crashes with the following error:

2024-12-20 08:23:23 ERROR django.request: Internal Server Error: /admin/content/localize/translate/2/preview/ (log.py:241)
Traceback (most recent call last):
  File "/***/lib/python3.11/site-packages/django/core/handlers/exception.py", line 55, in inner
    response = get_response(request)
               ^^^^^^^^^^^^^^^^^^^^^
  File "/***/lib/python3.11/site-packages/django/core/handlers/base.py", line 197, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/***/lib/python3.11/site-packages/sentry_sdk/integrations/django/views.py", line 84, in sentry_wrapped_callback
    return callback(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/***/lib/python3.11/site-packages/django/views/decorators/cache.py", line 62, in _wrapper_view_func
    response = view_func(request, *args, **kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/***/lib/python3.11/site-packages/wagtail/admin/urls/__init__.py", line 173, in wrapper
    return view_func(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/***/lib/python3.11/site-packages/wagtail/admin/auth.py", line 165, in decorated_view
    response = view_func(request, *args, **kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/***/lib/python3.11/site-packages/django/utils/decorators.py", line 46, in _wrapper
    return bound_method(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/***/lib/python3.11/site-packages/wagtail/utils/decorators.py", line 78, in wrapped_view
    resp = view_func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/***/lib/python3.11/site-packages/wagtail_localize/views/edit_translation.py", line 1134, in preview_translation
    translation = translation.source.get_ephemeral_translated_instance(
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/***/lib/python3.11/site-packages/wagtail_localize/models.py", line 897, in get_ephemeral_translated_instance
    ingest_segments(original, translation, self.locale, locale, segments)
  File "/***/lib/python3.11/site-packages/wagtail_localize/segments/ingest.py", line 263, in ingest_segments
    ).handle_stream_block(data, field_segments)
      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/***/lib/python3.11/site-packages/wagtail_localize/segments/ingest.py", line 228, in handle_stream_block
    block.value = self.handle_block(block.block, block.value, segments)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/***/lib/python3.11/site-packages/wagtail_localize/segments/ingest.py", line 161, in handle_block
    return self.handle_related_object_block(block_value, segments)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/***/lib/python3.11/site-packages/wagtail_localize/segments/ingest.py", line 178, in handle_related_object_block
    return handle_related_object(
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/***/lib/python3.11/site-packages/wagtail_localize/segments/ingest.py", line 90, in handle_related_object
    return related_model.objects.get(pk=segment.data)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/***/lib/python3.11/site-packages/django/db/models/manager.py", line 87, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/***/lib/python3.11/site-packages/django/db/models/query.py", line 637, in get
    raise self.model.DoesNotExist(
wagtail.models.Page.DoesNotExist: Page matching query does not exist.
[20/Dec/2024 08:23:23] "GET /admin/content/localize/translate/2/preview/ HTTP/1.1" 500 150427

I would expect that the preview still works, but the value is handled as None similar to how Wagtail's ChooserBlock handles it here.

In the UI the translated chooser block stays in the "Fetching page information..." state, because the API request fails with a 404.
Clicking the publish button also leads to a related crash:

2024-12-20 08:30:31 ERROR django.request: Internal Server Error: /admin/content/pages/18/edit/ (log.py:241)
Traceback (most recent call last):
  File "/***/lib/python3.11/site-packages/django/core/handlers/exception.py", line 55, in inner
    response = get_response(request)
               ^^^^^^^^^^^^^^^^^^^^^
  File "/***/lib/python3.11/site-packages/django/core/handlers/base.py", line 197, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/***/lib/python3.11/site-packages/sentry_sdk/integrations/django/views.py", line 84, in sentry_wrapped_callback
    return callback(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/***/lib/python3.11/site-packages/django/views/decorators/cache.py", line 62, in _wrapper_view_func
    response = view_func(request, *args, **kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/***/lib/python3.11/site-packages/wagtail/admin/urls/__init__.py", line 173, in wrapper
    return view_func(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/***/lib/python3.11/site-packages/wagtail/admin/auth.py", line 165, in decorated_view
    response = view_func(request, *args, **kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/***/lib/python3.11/site-packages/django/views/generic/base.py", line 104, in view
    return self.dispatch(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/***/lib/python3.11/site-packages/wagtail/admin/views/pages/edit.py", line 375, in dispatch
    response = self.run_hook("before_edit_page", self.request, self.page)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/***/lib/python3.11/site-packages/wagtail/admin/views/generic/mixins.py", line 49, in run_hook
    result = fn(*args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^
  File "/***/lib/python3.11/site-packages/wagtail_localize/wagtail_hooks.py", line 268, in before_edit_page
    return edit_translation.edit_translation(request, translation, page)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/***/lib/python3.11/site-packages/wagtail_localize/views/edit_translation.py", line 565, in edit_translation
    translation.save_target(user=request.user, publish=True)
  File "/***/lib/python3.11/site-packages/wagtail_localize/models.py", line 1344, in save_target
    self.source.create_or_update_translation(
  File "/***/lib/python3.11/site-packages/wagtail_localize/models.py", line 760, in create_or_update_translation
    ingest_segments(original, translation, self.locale, locale, segments)
  File "/***/lib/python3.11/site-packages/wagtail_localize/segments/ingest.py", line 263, in ingest_segments
    ).handle_stream_block(data, field_segments)
      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/***/lib/python3.11/site-packages/wagtail_localize/segments/ingest.py", line 228, in handle_stream_block
    block.value = self.handle_block(block.block, block.value, segments)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/***/lib/python3.11/site-packages/wagtail_localize/segments/ingest.py", line 161, in handle_block
    return self.handle_related_object_block(block_value, segments)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/***/lib/python3.11/site-packages/wagtail_localize/segments/ingest.py", line 178, in handle_related_object_block
    return handle_related_object(
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/***/lib/python3.11/site-packages/wagtail_localize/segments/ingest.py", line 90, in handle_related_object
    return related_model.objects.get(pk=segment.data)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/***/lib/python3.11/site-packages/django/db/models/manager.py", line 87, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/***/lib/python3.11/site-packages/django/db/models/query.py", line 637, in get
    raise self.model.DoesNotExist(
wagtail.models.Page.DoesNotExist: Page matching query does not exist.
[20/Dec/2024 08:30:31] "POST /admin/content/pages/18/edit/ HTTP/1.1" 500 167964

I would expect a validation error or at least no crash and an empty chooser.

@zerolab
Copy link
Collaborator

zerolab commented Dec 20, 2024

Thanks @th3hamm0r
This is similar to #769

@th3hamm0r
Copy link
Contributor Author

th3hamm0r commented Dec 20, 2024

Sry, missed that during my search 😅
I've now tested it with the ImageChooserBlock too and it produces the same issue as above. Maybe the error has changed from breaking the UI to the 500 during preview/publish(?)

At least for the backend part, replacing

def handle_related_object_block(self, related_object, segments):
return handle_related_object(
related_object.__class__, self.src_locale, self.tgt_locale, segments
)

with

    def handle_related_object_block(self, related_object, segments):
        try:
            return handle_related_object(
                related_object.__class__, self.src_locale, self.tgt_locale, segments
            )
        except related_object.__class__.DoesNotExist:
            return None

avoids the 500 errors. Of course, this is just a partial fix, but at least the preview and publishing works (in our case) 😬

edit: I have to admit, this is just a quick fix, I'm not sure if the other case where handle_related_object() is used (here) would need that fix too. In that case the try-except would be better placed in handle_related_object()...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants