diff --git a/CHANGELOG.md b/CHANGELOG.md index 80b9d021..40f29365 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,10 @@ Using the following categories, list your changes in this order: - Warning W018 (`Suspicious position of 'reactpy_django' in INSTALLED_APPS`) has been added. +### Changed + +- The default postprocessor can now disabled by setting `REACTPY_DEFAULT_QUERY_POSTPROCESSOR` to `None`. + ## [3.5.0] - 2023-08-26 ### Added diff --git a/docs/src/features/settings.md b/docs/src/features/settings.md index 00662976..3917d766 100644 --- a/docs/src/features/settings.md +++ b/docs/src/features/settings.md @@ -26,7 +26,7 @@ These are ReactPy-Django's default settings values. You can modify these values | `REACTPY_DATABASE` | `#!python "default"` | `#!python "my-reactpy-database"` | Database used to store ReactPy session data. ReactPy requires a multiprocessing-safe and thread-safe database.
If configuring `REACTPY_DATABASE`, it is mandatory to use our database router like such:
`#!python DATABASE_ROUTERS = ["reactpy_django.database.Router", ...]` | | `REACTPY_SESSION_MAX_AGE` | `#!python 259200` | `#!python 0`, `#!python 60`, `#!python 96000` | Maximum seconds to store ReactPy session data, such as `args` and `kwargs` passed into your component template tag.
Use `#!python 0` to not store any session data. | | `REACTPY_URL_PREFIX` | `#!python "reactpy/"` | `#!python "rp/"`, `#!python "render/reactpy/"` | The prefix to be used for all ReactPy websocket and HTTP URLs. | -| `REACTPY_DEFAULT_QUERY_POSTPROCESSOR` | `#!python "reactpy_django.utils.django_query_postprocessor"` | `#!python "example_project.my_query_postprocessor"` | Dotted path to the default `reactpy_django.hooks.use_query` postprocessor function. | +| `REACTPY_DEFAULT_QUERY_POSTPROCESSOR` | `#!python "reactpy_django.utils.django_query_postprocessor"` | `#!python None`, `#!python "example_project.my_query_postprocessor"` | Dotted path to the default `reactpy_django.hooks.use_query` postprocessor function. Postprocessor functions can be async or sync, and the parameters must contain the arg `#!python data`. Set `REACTPY_DEFAULT_QUERY_POSTPROCESSOR` to `#!python None` to globally disable the default postprocessor. | | `REACTPY_AUTH_BACKEND` | `#!python "django.contrib.auth.backends.ModelBackend"` | `#!python "example_project.auth.MyModelBackend"` | 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` setting and...
3. Your Django user model does not define a `backend` attribute. | | `REACTPY_BACKHAUL_THREAD` | `#!python False` | `#!python True` | Whether to render ReactPy components in a dedicated thread. This allows the webserver to process web traffic while during ReactPy rendering.
Vastly improves throughput with web servers such as `hypercorn` and `uvicorn`. | | `REACTPY_DEFAULT_HOSTS` | `#!python None` | `#!python ["localhost:8000", "localhost:8001", "localhost:8002/subdir" ]` | The default host(s) that can render your ReactPy components. ReactPy will use these hosts in a round-robin fashion, allowing for easy distributed computing.
You can use the `host` argument in your [template tag](../features/template-tag.md#component) as a manual override. | diff --git a/src/reactpy_django/checks.py b/src/reactpy_django/checks.py index e7d320d6..adc437d0 100644 --- a/src/reactpy_django/checks.py +++ b/src/reactpy_django/checks.py @@ -330,12 +330,12 @@ def reactpy_errors(app_configs, **kwargs): ) ) if not isinstance( - getattr(settings, "REACTPY_DEFAULT_QUERY_POSTPROCESSOR", ""), str + getattr(settings, "REACTPY_DEFAULT_QUERY_POSTPROCESSOR", ""), (str, type(None)) ): errors.append( Error( "Invalid type for REACTPY_DEFAULT_QUERY_POSTPROCESSOR.", - hint="REACTPY_DEFAULT_QUERY_POSTPROCESSOR should be a string.", + hint="REACTPY_DEFAULT_QUERY_POSTPROCESSOR should be a string or None.", obj=settings.REACTPY_DEFAULT_QUERY_POSTPROCESSOR, id="reactpy_django.E007", ) diff --git a/src/reactpy_django/config.py b/src/reactpy_django/config.py index d811f7dc..dc350e2a 100644 --- a/src/reactpy_django/config.py +++ b/src/reactpy_django/config.py @@ -53,15 +53,20 @@ _default_query_postprocessor = getattr( settings, "REACTPY_DEFAULT_QUERY_POSTPROCESSOR", - None, + "UNSET", ) -REACTPY_DEFAULT_QUERY_POSTPROCESSOR: AsyncPostprocessor | SyncPostprocessor | None = ( - import_dotted_path( - _default_query_postprocessor - if isinstance(_default_query_postprocessor, str) - else "reactpy_django.utils.django_query_postprocessor", +REACTPY_DEFAULT_QUERY_POSTPROCESSOR: AsyncPostprocessor | SyncPostprocessor | None +if _default_query_postprocessor is None: + REACTPY_DEFAULT_QUERY_POSTPROCESSOR = None +else: + REACTPY_DEFAULT_QUERY_POSTPROCESSOR = import_dotted_path( + "reactpy_django.utils.django_query_postprocessor" + if ( + _default_query_postprocessor == "UNSET" + or not isinstance(_default_query_postprocessor, str) + ) + else _default_query_postprocessor ) -) REACTPY_AUTH_BACKEND: str | None = getattr( settings, "REACTPY_AUTH_BACKEND", diff --git a/src/reactpy_django/types.py b/src/reactpy_django/types.py index ac6205e0..233fe432 100644 --- a/src/reactpy_django/types.py +++ b/src/reactpy_django/types.py @@ -105,9 +105,9 @@ class QueryOptions: are optional `postprocessor_kwargs` (see below). This postprocessor function must return the modified `data`. - If `None`, the default postprocessor is used. + If unset, REACTPY_DEFAULT_QUERY_POSTPROCESSOR is used. - This default Django query postprocessor prevents Django's lazy query execution, and + ReactPy's default django_query_postprocessor prevents Django's lazy query execution, and additionally can be configured via `postprocessor_kwargs` to recursively fetch `many_to_many` and `many_to_one` fields.""" diff --git a/tests/test_app/components.py b/tests/test_app/components.py index d018cd96..ec53c031 100644 --- a/tests/test_app/components.py +++ b/tests/test_app/components.py @@ -8,6 +8,7 @@ from django.shortcuts import render from reactpy import component, hooks, html, web from reactpy_django.components import view_to_component +from reactpy_django.types import QueryOptions from test_app.models import ( AsyncForiegnChild, @@ -602,3 +603,17 @@ def custom_host(number=0): }, f"Server Port: {port}", ) + + +@component +def broken_postprocessor_query(): + relational_parent = reactpy_django.hooks.use_query( + QueryOptions(postprocessor=None), get_relational_parent_query + ) + + if not relational_parent.data: + return + + mtm = relational_parent.data.many_to_many.all() + + return html.div(f"This should have failed! Something went wrong: {mtm}") diff --git a/tests/test_app/templates/base.html b/tests/test_app/templates/base.html index 303e99dd..03dd3ba3 100644 --- a/tests/test_app/templates/base.html +++ b/tests/test_app/templates/base.html @@ -91,6 +91,9 @@

ReactPy Test Page


{% component "test_app.components.hello_world" host="https://example.com/" %}

+
+ {% component "test_app.components.broken_postprocessor_query" %}
+
diff --git a/tests/test_app/tests/test_components.py b/tests/test_app/tests/test_components.py index e78d8963..d05ab46c 100644 --- a/tests/test_app/tests/test_components.py +++ b/tests/test_app/tests/test_components.py @@ -370,3 +370,8 @@ def test_invalid_host_error(self): broken_component = self.page.locator("#invalid_host_error") broken_component.wait_for() self.assertIn("InvalidHostError:", broken_component.text_content()) + + def test_broken_postprocessor_query(self): + broken_component = self.page.locator("#broken_postprocessor_query pre") + broken_component.wait_for() + self.assertIn("SynchronousOnlyOperation:", broken_component.text_content())