55from uuid import uuid4
66
77from channels .db import database_sync_to_async
8- from django .forms import Form
8+ from django .forms import Form , ModelForm
99from reactpy import component , hooks , html , utils
1010from reactpy .core .events import event
1111from reactpy .web import export , module_from_file
3131
3232@component
3333def _django_form (
34- form : type [Form ],
34+ form : type [Form | ModelForm ],
3535 extra_props : dict ,
3636 on_success : Callable [[FormEvent ], None ] | None ,
3737 on_error : Callable [[FormEvent ], None ] | None ,
3838 on_submit : Callable [[FormEvent ], None ] | None ,
3939 on_change : Callable [[FormEvent ], None ] | None ,
40+ auto_save : bool ,
4041 form_template : str | None ,
4142 top_children : Sequence ,
4243 bottom_children : Sequence ,
@@ -46,7 +47,6 @@ def _django_form(
4647 # Perhaps pre-rendering is robust enough already handle this scenario?
4748 # Additionally, "URL" mode would limit the user to one form per page.
4849 # TODO: Test this with django-colorfield, django-ace, django-crispy-forms
49- # TODO: Add auto-save option for database-backed forms
5050 uuid_ref = hooks .use_ref (uuid4 ().hex .replace ("-" , "" ))
5151 top_children_count = hooks .use_ref (len (top_children ))
5252 bottom_children_count = hooks .use_ref (len (bottom_children ))
@@ -59,20 +59,17 @@ def _django_form(
5959 msg = "Dynamically changing the number of top or bottom children is not allowed."
6060 raise ValueError (msg )
6161
62+ # Ensure the provided form is a Django Form
63+ if not isinstance (form , (type (Form ), type (ModelForm ))):
64+ msg = (
65+ "The provided form must be an uninitialized Django Form. "
66+ "Do NOT initialize your form by calling it (ex. `MyForm()`)."
67+ )
68+ raise TypeError (msg )
69+
6270 # Try to initialize the form with the provided data
63- try :
64- initialized_form = form (data = submitted_data )
65- except Exception as e :
66- if not isinstance (form , type (Form )):
67- msg = (
68- "The provided form must be an uninitialized Django Form. "
69- "Do NOT initialize your form by calling it (ex. `MyForm()`)."
70- )
71- raise TypeError (msg ) from e
72- raise
73-
74- # Set up the form event object
75- form_event = FormEvent (form = initialized_form , data = submitted_data or {})
71+ initialized_form = form (data = submitted_data )
72+ form_event = FormEvent (form = initialized_form , data = submitted_data or {}, set_data = set_submitted_data )
7673
7774 # Validate and render the form
7875 @hooks .use_effect
@@ -85,6 +82,9 @@ async def render_form():
8582 on_success (form_event )
8683 if not success and on_error :
8784 on_error (form_event )
85+ if success and auto_save and isinstance (initialized_form , ModelForm ):
86+ await database_sync_to_async (initialized_form .save )()
87+ set_submitted_data (None )
8888
8989 set_rendered_form (await database_sync_to_async (initialized_form .render )(form_template ))
9090
@@ -99,7 +99,7 @@ def on_submit_callback(new_data: dict[str, Any]):
9999 convert_boolean_fields (new_data , initialized_form )
100100
101101 if on_submit :
102- on_submit (FormEvent (form = initialized_form , data = new_data ))
102+ on_submit (FormEvent (form = initialized_form , data = new_data , set_data = set_submitted_data ))
103103
104104 # TODO: The `use_state`` hook really should be de-duplicating this by itself. Needs upstream fix.
105105 if submitted_data != new_data :
0 commit comments