From 204b47c1ec75d2f4b665b7d89d59aa6b7c1d6b6d Mon Sep 17 00:00:00 2001
From: Mark Bakhit <16909269+Archmonger@users.noreply.github.com>
Date: Mon, 26 Jun 2023 23:15:15 -0700
Subject: [PATCH 1/4] Add error checking to `component` template tag (#154)
- Make it so that `component` template tag render failures don't cause catastrophic failure.
- `component` template tag render failures now output a visual indicator if `REACTPY_DEBUG_MODE` is enabled.
- Render `
` via raw HTML within the tests so that component render failures don't break formatting
- Fix typo on the docs
- Ensure `generate_object_name` always returns a string
---
CHANGELOG.md | 8 +-
docs/python/settings.py | 2 +-
src/reactpy_django/exceptions.py | 6 +
src/reactpy_django/hooks.py | 6 +-
.../templates/reactpy/component.html | 6 +
src/reactpy_django/templatetags/reactpy.py | 58 +++++++--
src/reactpy_django/utils.py | 45 +++++--
src/reactpy_django/websocket/consumer.py | 4 +-
tests/test_app/components.py | 68 +++--------
tests/test_app/templates/base.html | 112 ++++++++++++------
.../test_app/templates/view_to_component.html | 2 +-
tests/test_app/tests/test_components.py | 40 +++++++
12 files changed, 233 insertions(+), 124 deletions(-)
create mode 100644 src/reactpy_django/exceptions.py
diff --git a/CHANGELOG.md b/CHANGELOG.md
index dcda688d..ef52d7de 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -34,7 +34,13 @@ Using the following categories, list your changes in this order:
## [Unreleased]
-- Nothing (yet)
+### Added
+
+- Template tag exception details are now rendered on the webpage when `DEBUG` is enabled.
+
+### Fixed
+
+- Prevent exceptions within the `component` template tag from causing the whole template to fail to render.
## [3.2.0] - 2023-06-08
diff --git a/docs/python/settings.py b/docs/python/settings.py
index 9633da43..a08dbc55 100644
--- a/docs/python/settings.py
+++ b/docs/python/settings.py
@@ -17,6 +17,6 @@
# Dotted path to the Django authentication backend to use for ReactPy components
# This is only needed if:
# 1. You are using `AuthMiddlewareStack` and...
-# 2. You are using Django's `AUTHENTICATION_BACKENDS` settings and...
+# 2. You are using Django's `AUTHENTICATION_BACKENDS` setting and...
# 3. Your Django user model does not define a `backend` attribute
REACTPY_AUTH_BACKEND = None
diff --git a/src/reactpy_django/exceptions.py b/src/reactpy_django/exceptions.py
new file mode 100644
index 00000000..072f1d4f
--- /dev/null
+++ b/src/reactpy_django/exceptions.py
@@ -0,0 +1,6 @@
+class ComponentParamError(TypeError):
+ ...
+
+
+class ComponentDoesNotExistError(AttributeError):
+ ...
diff --git a/src/reactpy_django/hooks.py b/src/reactpy_django/hooks.py
index 00e22bd9..e9f80908 100644
--- a/src/reactpy_django/hooks.py
+++ b/src/reactpy_django/hooks.py
@@ -157,9 +157,7 @@ async def execute_query() -> None:
set_data(None)
set_loading(False)
set_error(e)
- _logger.exception(
- f"Failed to execute query: {generate_obj_name(query) or query}"
- )
+ _logger.exception(f"Failed to execute query: {generate_obj_name(query)}")
return
# Query was successful
@@ -252,7 +250,7 @@ async def execute_mutation(exec_args, exec_kwargs) -> None:
set_loading(False)
set_error(e)
_logger.exception(
- f"Failed to execute mutation: {generate_obj_name(mutation) or mutation}"
+ f"Failed to execute mutation: {generate_obj_name(mutation)}"
)
# Mutation was successful
diff --git a/src/reactpy_django/templates/reactpy/component.html b/src/reactpy_django/templates/reactpy/component.html
index 08ab566d..7dae08eb 100644
--- a/src/reactpy_django/templates/reactpy/component.html
+++ b/src/reactpy_django/templates/reactpy/component.html
@@ -1,4 +1,9 @@
{% load static %}
+{% if reactpy_failure %}
+{% if reactpy_debug_mode %}
+{% firstof reactpy_error "UnknownError" %}: "{% firstof reactpy_dotted_path "UnknownPath" %}"
+{% endif %}
+{% else %}
+{% endif %}
diff --git a/src/reactpy_django/templatetags/reactpy.py b/src/reactpy_django/templatetags/reactpy.py
index b508d0e9..d1ce87e5 100644
--- a/src/reactpy_django/templatetags/reactpy.py
+++ b/src/reactpy_django/templatetags/reactpy.py
@@ -1,3 +1,4 @@
+from logging import getLogger
from uuid import uuid4
import dill as pickle
@@ -7,15 +8,22 @@
from reactpy_django import models
from reactpy_django.config import (
REACTPY_DATABASE,
+ REACTPY_DEBUG_MODE,
REACTPY_RECONNECT_MAX,
REACTPY_WEBSOCKET_URL,
)
+from reactpy_django.exceptions import ComponentDoesNotExistError, ComponentParamError
from reactpy_django.types import ComponentParamData
-from reactpy_django.utils import _register_component, func_has_params
+from reactpy_django.utils import (
+ _register_component,
+ check_component_args,
+ func_has_args,
+)
REACTPY_WEB_MODULES_URL = reverse("reactpy:web_modules", args=["x"])[:-1][1:]
register = template.Library()
+_logger = getLogger(__name__)
@register.inclusion_tag("reactpy/component.html")
@@ -39,24 +47,45 @@ def component(dotted_path: str, *args, **kwargs):