Skip to content

Commit cd191ce

Browse files
authored
fix #272 (#295)
1 parent c5930dd commit cd191ce

3 files changed

Lines changed: 75 additions & 64 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ Don't forget to remove deprecated code on each major release!
4949
- Removed `reactpy_django/html.py` (`pyscript` VDOM constructor). Use `html.py_script` instead.
5050
- Removed `nest_asyncio` dependency and its initialization on startup.
5151

52+
### Fixed
53+
54+
- Resolved bug where `django_form` events would sometimes not occur.
55+
5256
### [5.2.1] - 2025-01-10
5357

5458
### Changed

src/reactpy_django/forms/components.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
from asyncio import iscoroutinefunction
34
from logging import getLogger
45
from pathlib import Path
56
from typing import TYPE_CHECKING, Any, Callable, Union, cast
@@ -65,6 +66,15 @@ def _django_form(
6566
form=initialized_form, submitted_data=submitted_data or {}, set_submitted_data=set_submitted_data
6667
)
6768

69+
async def _call_event_callback(callback, event_data):
70+
"""Call a form event callback, running sync callbacks directly
71+
(not through database_sync_to_async) to avoid issues with ReactPy state
72+
setters that require an active event loop."""
73+
if iscoroutinefunction(callback):
74+
await callback(event_data)
75+
else:
76+
callback(event_data)
77+
6878
# Validate and render the form
6979
@hooks.use_async_effect(dependencies=[str(submitted_data)])
7080
async def render_form():
@@ -76,9 +86,9 @@ async def render_form():
7686
await ensure_async(initialized_form.full_clean, thread_sensitive=thread_sensitive)()
7787
success = not initialized_form.errors.as_data()
7888
if success and on_success:
79-
await ensure_async(on_success, thread_sensitive=thread_sensitive)(form_event)
89+
await _call_event_callback(on_success, form_event)
8090
if not success and on_error:
81-
await ensure_async(on_error, thread_sensitive=thread_sensitive)(form_event)
91+
await _call_event_callback(on_error, form_event)
8292
if success and auto_save and isinstance(initialized_form, ModelForm):
8393
await ensure_async(initialized_form.save)()
8494
set_submitted_data(None)
@@ -101,15 +111,15 @@ async def on_submit_callback(new_data: dict[str, Any]):
101111
new_form_event = FormEventData(
102112
form=initialized_form, submitted_data=new_data, set_submitted_data=set_submitted_data
103113
)
104-
await ensure_async(on_receive_data, thread_sensitive=thread_sensitive)(new_form_event)
114+
await _call_event_callback(on_receive_data, new_form_event)
105115

106116
if submitted_data != new_data:
107117
set_submitted_data(new_data)
108118

109119
async def _on_change(_event):
110120
"""Event that exist solely to allow the user to detect form changes."""
111121
if on_change:
112-
await ensure_async(on_change, thread_sensitive=thread_sensitive)(form_event)
122+
await _call_event_callback(on_change, form_event)
113123

114124
if not rendered_form:
115125
return None

tests/test_app/tests/test_components.py

Lines changed: 57 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -859,63 +859,60 @@ def test_form_orm_model(self):
859859
finally:
860860
os.environ.pop("DJANGO_ALLOW_ASYNC_UNSAFE")
861861

862-
# FIXME: Re-enable once we fix the following:
863-
# https://github.com/reactive-python/reactpy-django/issues/272
864-
865-
# @navigate_to_page("/form/sync_event/")
866-
# def test_form_sync_events(self):
867-
# self.page.wait_for_selector("form")
868-
869-
# # Check initial state
870-
# self.page.wait_for_selector("#success[data-value='false']")
871-
# self.page.wait_for_selector("#error[data-value='false']")
872-
# self.page.wait_for_selector("#receive_data[data-value='false']")
873-
# self.page.wait_for_selector("#change[data-value='false']")
874-
875-
# # Submit empty the form
876-
# self.page.wait_for_selector("input[type=submit]").click(delay=DELAY)
877-
878-
# # The empty form was submitted, should result in an error
879-
# self.page.wait_for_selector("#success[data-value='false']")
880-
# self.page.wait_for_selector("#error[data-value='true']")
881-
# self.page.wait_for_selector("#receive_data[data-value='true']")
882-
# self.page.wait_for_selector("#change[data-value='false']")
883-
884-
# # Fill out the form and re-submit
885-
# self.page.wait_for_selector("#id_char_field").type("test", delay=DELAY)
886-
# self.page.wait_for_selector("input[type=submit]").click(delay=DELAY)
887-
888-
# # Form should have been successfully submitted
889-
# self.page.wait_for_selector("#success[data-value='true']")
890-
# self.page.wait_for_selector("#error[data-value='true']")
891-
# self.page.wait_for_selector("#receive_data[data-value='true']")
892-
# self.page.wait_for_selector("#change[data-value='true']")
893-
894-
# @navigate_to_page("/form/async_event/")
895-
# def test_form_async_events(self):
896-
# self.page.wait_for_selector("form")
897-
898-
# # Check initial state
899-
# self.page.wait_for_selector("#success[data-value='false']")
900-
# self.page.wait_for_selector("#error[data-value='false']")
901-
# self.page.wait_for_selector("#receive_data[data-value='false']")
902-
# self.page.wait_for_selector("#change[data-value='false']")
903-
904-
# # Submit empty the form
905-
# self.page.wait_for_selector("input[type=submit]").click(delay=DELAY)
906-
907-
# # The empty form was submitted, should result in an error
908-
# self.page.wait_for_selector("#success[data-value='false']")
909-
# self.page.wait_for_selector("#error[data-value='true']")
910-
# self.page.wait_for_selector("#receive_data[data-value='true']")
911-
# self.page.wait_for_selector("#change[data-value='false']")
912-
913-
# # Fill out the form and re-submit
914-
# self.page.wait_for_selector("#id_char_field").type("test", delay=DELAY)
915-
# self.page.wait_for_selector("input[type=submit]").click(delay=DELAY)
916-
917-
# # Form should have been successfully submitted
918-
# self.page.wait_for_selector("#success[data-value='true']")
919-
# self.page.wait_for_selector("#error[data-value='true']")
920-
# self.page.wait_for_selector("#receive_data[data-value='true']")
921-
# self.page.wait_for_selector("#change[data-value='true']")
862+
@navigate_to_page("/form/sync_event/")
863+
def test_form_sync_events(self):
864+
self.page.wait_for_selector("form")
865+
866+
# Check initial state
867+
self.page.wait_for_selector("#success[data-value='false']")
868+
self.page.wait_for_selector("#error[data-value='false']")
869+
self.page.wait_for_selector("#receive_data[data-value='false']")
870+
self.page.wait_for_selector("#change[data-value='false']")
871+
872+
# Submit empty the form
873+
self.page.wait_for_selector("input[type=submit]").click(delay=DELAY)
874+
875+
# The empty form was submitted, should result in an error
876+
self.page.wait_for_selector("#success[data-value='false']")
877+
self.page.wait_for_selector("#error[data-value='true']")
878+
self.page.wait_for_selector("#receive_data[data-value='true']")
879+
self.page.wait_for_selector("#change[data-value='false']")
880+
881+
# Fill out the form and re-submit
882+
self.page.wait_for_selector("#id_char_field").type("test", delay=DELAY)
883+
self.page.wait_for_selector("input[type=submit]").click(delay=DELAY)
884+
885+
# Form should have been successfully submitted
886+
self.page.wait_for_selector("#success[data-value='true']")
887+
self.page.wait_for_selector("#error[data-value='true']")
888+
self.page.wait_for_selector("#receive_data[data-value='true']")
889+
self.page.wait_for_selector("#change[data-value='true']")
890+
891+
@navigate_to_page("/form/async_event/")
892+
def test_form_async_events(self):
893+
self.page.wait_for_selector("form")
894+
895+
# Check initial state
896+
self.page.wait_for_selector("#success[data-value='false']")
897+
self.page.wait_for_selector("#error[data-value='false']")
898+
self.page.wait_for_selector("#receive_data[data-value='false']")
899+
self.page.wait_for_selector("#change[data-value='false']")
900+
901+
# Submit empty the form
902+
self.page.wait_for_selector("input[type=submit]").click(delay=DELAY)
903+
904+
# The empty form was submitted, should result in an error
905+
self.page.wait_for_selector("#success[data-value='false']")
906+
self.page.wait_for_selector("#error[data-value='true']")
907+
self.page.wait_for_selector("#receive_data[data-value='true']")
908+
self.page.wait_for_selector("#change[data-value='false']")
909+
910+
# Fill out the form and re-submit
911+
self.page.wait_for_selector("#id_char_field").type("test", delay=DELAY)
912+
self.page.wait_for_selector("input[type=submit]").click(delay=DELAY)
913+
914+
# Form should have been successfully submitted
915+
self.page.wait_for_selector("#success[data-value='true']")
916+
self.page.wait_for_selector("#error[data-value='true']")
917+
self.page.wait_for_selector("#receive_data[data-value='true']")
918+
self.page.wait_for_selector("#change[data-value='true']")

0 commit comments

Comments
 (0)