1
1
from __future__ import annotations
2
2
3
3
from pathlib import Path
4
- from typing import TYPE_CHECKING , Any , Callable
4
+ from typing import TYPE_CHECKING , Any , Callable , Union , cast
5
5
from uuid import uuid4
6
6
7
+ from channels .db import database_sync_to_async
7
8
from django .forms import Form
8
9
from reactpy import component , hooks , html , utils
9
10
from reactpy .core .events import event
@@ -50,6 +51,7 @@ def _django_form(
50
51
top_children_count = hooks .use_ref (len (top_children ))
51
52
bottom_children_count = hooks .use_ref (len (bottom_children ))
52
53
submitted_data , set_submitted_data = hooks .use_state ({} or None )
54
+ rendered_form , set_rendered_form = hooks .use_state (cast (Union [str , None ], None ))
53
55
uuid = uuid_ref .current
54
56
55
57
# Don't allow the count of top and bottom children to change
@@ -69,39 +71,50 @@ def _django_form(
69
71
raise TypeError (msg ) from e
70
72
raise
71
73
72
- # Run the form validation, if data was provided
73
- if submitted_data :
74
- initialized_form .full_clean ()
75
- success = not initialized_form .errors .as_data ()
76
- form_event = FormEvent (form = initialized_form , data = submitted_data or {})
77
- if success and on_success :
78
- on_success (form_event )
79
- if not success and on_error :
80
- on_error (form_event )
74
+ # Set up the form event object
75
+ form_event = FormEvent (form = initialized_form , data = submitted_data or {})
76
+
77
+ # Validate and render the form
78
+ @hooks .use_effect
79
+ async def render_form ():
80
+ """Forms must be rendered in an async loop to allow database fields to execute."""
81
+ if submitted_data :
82
+ await database_sync_to_async (initialized_form .full_clean )()
83
+ success = not initialized_form .errors .as_data ()
84
+ if success and on_success :
85
+ on_success (form_event )
86
+ if not success and on_error :
87
+ on_error (form_event )
88
+
89
+ set_rendered_form (await database_sync_to_async (initialized_form .render )(form_template ))
81
90
82
91
def _on_change (_event ):
83
92
if on_change :
84
- on_change (FormEvent ( form = initialized_form , data = submitted_data or {}) )
93
+ on_change (form_event )
85
94
86
95
def on_submit_callback (new_data : dict [str , Any ]):
87
96
"""Callback function provided directly to the client side listener. This is responsible for transmitting
88
97
the submitted form data to the server for processing."""
89
98
convert_multiple_choice_fields (new_data , initialized_form )
90
99
convert_boolean_fields (new_data , initialized_form )
91
100
101
+ if on_submit :
102
+ on_submit (FormEvent (form = initialized_form , data = new_data ))
103
+
92
104
# TODO: The `use_state`` hook really should be de-duplicating this by itself. Needs upstream fix.
93
105
if submitted_data != new_data :
94
- if on_submit :
95
- on_submit (FormEvent (form = initialized_form , data = new_data ))
96
106
set_submitted_data (new_data )
97
107
108
+ if not rendered_form :
109
+ return None
110
+
98
111
return html .form (
99
112
{"id" : f"reactpy-{ uuid } " , "onSubmit" : event (lambda _ : None , prevent_default = True ), "onChange" : _on_change }
100
113
| extra_props ,
101
114
DjangoForm ({"onSubmitCallback" : on_submit_callback , "formId" : f"reactpy-{ uuid } " }),
102
115
* top_children ,
103
116
utils .html_to_vdom (
104
- initialized_form . render ( form_template ) ,
117
+ rendered_form ,
105
118
convert_html_props_to_reactjs ,
106
119
convert_textarea_children_to_prop ,
107
120
set_value_prop_on_select_element ,
0 commit comments