1
1
from __future__ import annotations
2
2
3
+ import asyncio
3
4
from pathlib import Path
4
5
from typing import TYPE_CHECKING , Any , Callable , Union , cast
5
6
from uuid import uuid4
18
19
transform_value_prop_on_input_element ,
19
20
)
20
21
from reactpy_django .forms .utils import convert_boolean_fields , convert_multiple_choice_fields
21
- from reactpy_django .types import FormEvent
22
+ from reactpy_django .types import AsyncFormEvent , FormEventData , SyncFormEvent
22
23
23
24
if TYPE_CHECKING :
24
25
from collections .abc import Sequence
31
32
)
32
33
33
34
35
+ # TODO: Create types for AsyncFormEvent and FormEvent
34
36
@component
35
37
def _django_form (
36
38
form : type [Form | ModelForm ],
37
- on_success : Callable [[ FormEvent ], None ] | None ,
38
- on_error : Callable [[ FormEvent ], None ] | None ,
39
- on_submit : Callable [[ FormEvent ], None ] | None ,
40
- on_change : Callable [[ FormEvent ], None ] | None ,
39
+ on_success : AsyncFormEvent | SyncFormEvent | None ,
40
+ on_error : AsyncFormEvent | SyncFormEvent | None ,
41
+ on_receive_data : AsyncFormEvent | SyncFormEvent | None ,
42
+ on_change : AsyncFormEvent | SyncFormEvent | None ,
41
43
auto_save : bool ,
42
44
extra_props : dict ,
43
45
extra_transforms : Sequence [Callable [[VdomDict ], Any ]],
@@ -64,13 +66,12 @@ def _django_form(
64
66
"Do NOT initialize your form by calling it (ex. `MyForm()`)."
65
67
)
66
68
raise TypeError (msg )
67
- if "id" in extra_props :
68
- msg = "The `extra_props` argument cannot contain an `id` key."
69
- raise ValueError (msg )
70
69
71
70
# Try to initialize the form with the provided data
72
71
initialized_form = form (data = submitted_data )
73
- form_event = FormEvent (form = initialized_form , data = submitted_data or {}, set_data = set_submitted_data )
72
+ form_event = FormEventData (
73
+ form = initialized_form , submitted_data = submitted_data or {}, set_submitted_data = set_submitted_data
74
+ )
74
75
75
76
# Validate and render the form
76
77
@hooks .use_effect
@@ -80,9 +81,15 @@ async def render_form():
80
81
await database_sync_to_async (initialized_form .full_clean )()
81
82
success = not initialized_form .errors .as_data ()
82
83
if success and on_success :
83
- on_success (form_event )
84
+ if asyncio .iscoroutinefunction (on_success ):
85
+ await on_success (form_event )
86
+ else :
87
+ on_success (form_event )
84
88
if not success and on_error :
85
- on_error (form_event )
89
+ if asyncio .iscoroutinefunction (on_error ):
90
+ await on_error (form_event )
91
+ else :
92
+ on_error (form_event )
86
93
if success and auto_save and isinstance (initialized_form , ModelForm ):
87
94
await database_sync_to_async (initialized_form .save )()
88
95
set_submitted_data (None )
@@ -93,28 +100,43 @@ async def render_form():
93
100
if new_form != rendered_form :
94
101
set_rendered_form (new_form )
95
102
96
- def on_submit_callback (new_data : dict [str , Any ]):
103
+ async def on_submit_callback (new_data : dict [str , Any ]):
97
104
"""Callback function provided directly to the client side listener. This is responsible for transmitting
98
105
the submitted form data to the server for processing."""
99
106
convert_multiple_choice_fields (new_data , initialized_form )
100
107
convert_boolean_fields (new_data , initialized_form )
101
108
102
- if on_submit :
103
- on_submit (FormEvent (form = initialized_form , data = new_data , set_data = set_submitted_data ))
109
+ if on_receive_data :
110
+ new_form_event = FormEventData (
111
+ form = initialized_form , submitted_data = new_data , set_submitted_data = set_submitted_data
112
+ )
113
+ if asyncio .iscoroutinefunction (on_receive_data ):
114
+ await on_receive_data (new_form_event )
115
+ else :
116
+ on_receive_data (new_form_event )
104
117
105
118
if submitted_data != new_data :
106
119
set_submitted_data (new_data )
107
120
121
+ async def _on_change (_event ):
122
+ """Event that exist solely to allow the user to detect form changes."""
123
+ if on_change :
124
+ if asyncio .iscoroutinefunction (on_change ):
125
+ await on_change (form_event )
126
+ else :
127
+ on_change (form_event )
128
+
108
129
if not rendered_form :
109
130
return None
110
131
111
132
return html .form (
112
- {
133
+ extra_props
134
+ | {
113
135
"id" : f"reactpy-{ uuid } " ,
136
+ # Intercept the form submission to prevent the browser from navigating
114
137
"onSubmit" : event (lambda _ : None , prevent_default = True ),
115
- "onChange" : on_change (form_event ) if on_change else lambda _ : None ,
116
- }
117
- | extra_props ,
138
+ "onChange" : _on_change ,
139
+ },
118
140
DjangoForm ({"onSubmitCallback" : on_submit_callback , "formId" : f"reactpy-{ uuid } " }),
119
141
* top_children ,
120
142
utils .html_to_vdom (
0 commit comments