Skip to content

Commit 5596d8d

Browse files
authored
Django form component (#267)
1 parent c8b4ec0 commit 5596d8d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1664
-29
lines changed

CHANGELOG.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,15 @@ Don't forget to remove deprecated code on each major release!
1919

2020
## [Unreleased]
2121

22-
- Nothing (yet)!
22+
### Added
23+
24+
- Automatically convert Django forms to ReactPy forms via the new `reactpy_django.components.django_form` component!
2325

2426
## [5.1.1] - 2024-12-02
2527

2628
### Fixed
2729

28-
- Fixed regression in v5.1.0 where components would sometimes not output debug messages when `settings.py:DEBUG` is enabled.
30+
- Fixed regression from the previous release where components would sometimes not output debug messages when `settings.py:DEBUG` is enabled.
2931

3032
### Changed
3133

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
- [Multiple root components](https://reactive-python.github.io/reactpy-django/latest/reference/template-tag/)
3131
- [Cross-process communication/signaling](https://reactive-python.github.io/reactpy-django/latest/reference/hooks/#use-channel-layer)
3232
- [Django view to ReactPy component conversion](https://reactive-python.github.io/reactpy-django/latest/reference/components/#view-to-component)
33+
- [Django form to ReactPy component conversion](https://reactive-python.github.io/reactpy-django/latest/reference/components/#django-form)
3334
- [Django static file access](https://reactive-python.github.io/reactpy-django/latest/reference/components/#django-css)
3435
- [Django database access](https://reactive-python.github.io/reactpy-django/latest/reference/hooks/#use-query)
3536

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{% load django_bootstrap5 %}
2+
3+
<!-- Note: CSS/JS is loaded here only for demonstration purposes.
4+
You should load this CSS/JS in your HTML <head> instead. -->
5+
{% bootstrap_css %}
6+
{% bootstrap_javascript %}
7+
8+
<!-- The actual form that is rendered by ReactPy -->
9+
{% bootstrap_form form %}
10+
{% bootstrap_button button_type="submit" content="OK" %}
11+
{% bootstrap_button button_type="reset" content="Reset" %}

docs/examples/python/django_form.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from reactpy import component, html
2+
3+
from example.forms import MyForm
4+
from reactpy_django.components import django_form
5+
6+
7+
@component
8+
def basic_form():
9+
children = [html.input({"type": "submit"})]
10+
return django_form(MyForm, bottom_children=children)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from reactpy import component
2+
3+
from example.forms import MyForm
4+
from reactpy_django.components import django_form
5+
6+
7+
@component
8+
def basic_form():
9+
return django_form(MyForm, form_template="bootstrap_form.html")
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from django import forms
2+
3+
4+
class MyForm(forms.Form):
5+
username = forms.CharField(label="Username")
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from reactpy import component, hooks, html
2+
from reactpy_router import navigate
3+
4+
from example.forms import MyForm
5+
from reactpy_django.components import django_form
6+
from reactpy_django.types import FormEventData
7+
8+
9+
@component
10+
def basic_form():
11+
submitted, set_submitted = hooks.use_state(False)
12+
13+
def on_submit(event: FormEventData):
14+
"""This function will be called when the form is successfully submitted."""
15+
set_submitted(True)
16+
17+
if submitted:
18+
return navigate("/homepage")
19+
20+
children = [html.input({"type": "submit"})]
21+
return django_form(MyForm, on_success=on_submit, bottom_children=children)

docs/examples/python/example/forms.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from django import forms
2+
3+
4+
class MyForm(forms.Form): ...

docs/src/dictionary.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,4 @@ linter
4848
linters
4949
linting
5050
formatters
51+
bootstrap_form

docs/src/reference/components.md

Lines changed: 102 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ Compatible with sync or async [Function Based Views](https://docs.djangoproject.
156156

157157
??? info "Existing limitations"
158158

159-
There are currently several limitations of using `#!python view_to_component` that may be resolved in a future version.
159+
There are currently several limitations of using `#!python view_to_component` that will be [resolved in a future version](https://github.com/reactive-python/reactpy-django/issues/269).
160160

161161
- Requires manual intervention to change HTTP methods to anything other than `GET`.
162162
- ReactPy events cannot conveniently be attached to converted view HTML.
@@ -292,12 +292,12 @@ Compatible with sync or async [Function Based Views](https://docs.djangoproject.
292292

293293
??? info "Existing limitations"
294294

295-
There are currently several limitations of using `#!python view_to_iframe` that may be resolved in a future version.
295+
There are currently several limitations of using `#!python view_to_iframe` which may be [resolved in a future version](https://github.com/reactive-python/reactpy-django/issues/268).
296296

297297
- No built-in method of signalling events back to the parent component.
298-
- All provided `#!python *args` and `#!python *kwargs` must be serializable values, since they are encoded into the URL.
298+
- All provided `#!python args` and `#!python kwargs` must be serializable values, since they are encoded into the URL.
299299
- The `#!python iframe` will always load **after** the parent component.
300-
- CSS styling for `#!python iframe` elements tends to be awkward/difficult.
300+
- CSS styling for `#!python iframe` elements tends to be awkward.
301301

302302
??? question "How do I use this for Class Based Views?"
303303

@@ -381,6 +381,104 @@ Compatible with sync or async [Function Based Views](https://docs.djangoproject.
381381

382382
---
383383

384+
## Django Form
385+
386+
Automatically convert a Django form into a ReactPy component.
387+
388+
Compatible with both [standard Django forms](https://docs.djangoproject.com/en/stable/topics/forms/#building-a-form) and [ModelForms](https://docs.djangoproject.com/en/stable/topics/forms/modelforms/).
389+
390+
=== "components.py"
391+
392+
```python
393+
{% include "../../examples/python/django_form.py" %}
394+
```
395+
396+
=== "forms.py"
397+
398+
```python
399+
{% include "../../examples/python/django_form_class.py" %}
400+
```
401+
402+
??? example "See Interface"
403+
404+
<font size="4">**Parameters**</font>
405+
406+
| Name | Type | Description | Default |
407+
| --- | --- | --- | --- |
408+
| `#!python form` | `#!python type[Form | ModelForm]` | The form to convert. | N/A |
409+
| `#!python on_success` | `#!python AsyncFormEvent | SyncFormEvent | None` | A callback function that is called when the form is successfully submitted. | `#!python None` |
410+
| `#!python on_error` | `#!python AsyncFormEvent | SyncFormEvent | None` | A callback function that is called when the form submission fails. | `#!python None` |
411+
| `#!python on_receive_data` | `#!python AsyncFormEvent | SyncFormEvent | None` | A callback function that is called before newly submitted form data is rendered. | `#!python None` |
412+
| `#!python on_change` | `#!python AsyncFormEvent | SyncFormEvent | None` | A callback function that is called when a form field is modified by the user. | `#!python None` |
413+
| `#!python auto_save` | `#!python bool` | If `#!python True`, the form will automatically call `#!python save` on successful submission of a `#!python ModelForm`. This has no effect on regular `#!python Form` instances. | `#!python True` |
414+
| `#!python extra_props` | `#!python dict[str, Any] | None` | Additional properties to add to the `#!html <form>` element. | `#!python None` |
415+
| `#!python extra_transforms` | `#!python Sequence[Callable[[VdomDict], Any]] | None` | A list of functions that transforms the newly generated VDOM. The functions will be repeatedly called on each VDOM node. | `#!python None` |
416+
| `#!python form_template` | `#!python str | None` | The template to use for the form. If `#!python None`, Django's default template is used. | `#!python None` |
417+
| `#!python thread_sensitive` | `#!python bool` | Whether to run event callback functions in thread sensitive mode. This mode only applies to sync functions, and is turned on by default due to Django ORM limitations. | `#!python True` |
418+
| `#!python top_children` | `#!python Sequence[Any]` | Additional elements to add to the top of the form. | `#!python tuple` |
419+
| `#!python bottom_children` | `#!python Sequence[Any]` | Additional elements to add to the bottom of the form. | `#!python tuple` |
420+
| `#!python key` | `#!python Key | None` | A key to uniquely identify this component which is unique amongst a component's immediate siblings. | `#!python None` |
421+
422+
<font size="4">**Returns**</font>
423+
424+
| Type | Description |
425+
| --- | --- |
426+
| `#!python Component` | A ReactPy component. |
427+
428+
??? info "Existing limitations"
429+
430+
The following fields are currently incompatible with `#!python django_form`: `#!python FileField`, `#!python ImageField`, `#!python SplitDateTimeField`, and `#!python MultiValueField`.
431+
432+
Compatibility for these fields will be [added in a future version](https://github.com/reactive-python/reactpy-django/issues/270).
433+
434+
??? question "How do I style these forms with Bootstrap?"
435+
436+
You can style these forms by using a form styling library. In the example below, it is assumed that you have already installed [`django-bootstrap5`](https://pypi.org/project/django-bootstrap5/).
437+
438+
After installing a form styling library, you can then provide ReactPy a custom `#!python form_template` parameter. This parameter allows you to specify a custom HTML template to use to render this the form.
439+
440+
Note that you can also set a global default for `form_template` by using [`settings.py:REACTPY_DEFAULT_FORM_TEMPLATE`](./settings.md#reactpy_default_form_template).
441+
442+
=== "components.py"
443+
444+
```python
445+
{% include "../../examples/python/django_form_bootstrap.py" %}
446+
```
447+
448+
=== "forms.py"
449+
450+
```python
451+
{% include "../../examples/python/django_form_class.py" %}
452+
```
453+
454+
=== "bootstrap_form.html"
455+
456+
```jinja
457+
{% include "../../examples/html/django_form_bootstrap.html" %}
458+
```
459+
460+
??? question "How do I handle form success/errors?"
461+
462+
You can react to form state by providing a callback function to any of the following parameters: `#!python on_success`, `#!python on_error`, `#!python on_receive_data`, and `#!python on_change`.
463+
464+
These functions will be called when the form is submitted.
465+
466+
In the example below, we will use the `#!python on_success` parameter to change the URL upon successful submission.
467+
468+
=== "components.py"
469+
470+
```python
471+
{% include "../../examples/python/django_form_on_success.py" %}
472+
```
473+
474+
=== "forms.py"
475+
476+
```python
477+
{% include "../../examples/python/django_form_class.py" %}
478+
```
479+
480+
---
481+
384482
## Django CSS
385483

386484
Allows you to defer loading a CSS stylesheet until a component begins rendering. This stylesheet must be stored within [Django's static files](https://docs.djangoproject.com/en/stable/howto/static-files/).

0 commit comments

Comments
 (0)