mirror of https://github.com/tiangolo/fastapi.git
Reverted the changes in docs unrelated to the main idea of PR
This commit is contained in:
parent
978351dbe6
commit
2ceaa96981
|
|
@ -73,60 +73,6 @@ They will receive an error response telling them that the field `extra` is not a
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Default Fields
|
|
||||||
|
|
||||||
Form-encoded data has some quirks that can make working with pydantic models counterintuitive.
|
|
||||||
|
|
||||||
Say, for example, you were generating an HTML form from a model,
|
|
||||||
and that model had a boolean field in it that you wanted to display as a checkbox
|
|
||||||
with a default `True` value:
|
|
||||||
|
|
||||||
{* ../../docs_src/request_form_models/tutorial003_an_py39.py hl[11,10:23] *}
|
|
||||||
|
|
||||||
This works as expected when the checkbox remains checked,
|
|
||||||
the form encoded data in the request looks like this:
|
|
||||||
|
|
||||||
```formencoded
|
|
||||||
checkbox=on
|
|
||||||
```
|
|
||||||
|
|
||||||
and the JSON response is also correct:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{"checkbox":true}
|
|
||||||
```
|
|
||||||
|
|
||||||
When the checkbox is *unchecked*, though, something strange happens.
|
|
||||||
The submitted form data is *empty*,
|
|
||||||
and the returned JSON data still shows `checkbox` still being `true`!
|
|
||||||
|
|
||||||
This is because checkboxes in HTML forms don't work exactly like the boolean inputs we expect,
|
|
||||||
when a checkbox is checked, if there is no `value` attribute, the value will be `"on"`,
|
|
||||||
and [the field will be omitted altogether if unchecked](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input/checkbox).
|
|
||||||
|
|
||||||
When dealing with form models with defaults,
|
|
||||||
we need to take special care to handle cases where the field being *unset* has a specific meaning.
|
|
||||||
|
|
||||||
We also don't want to just treat any time the value is unset as ``False`` -
|
|
||||||
that would defeat the purpose of the default!
|
|
||||||
We want to specifically correct the behavior when it is used in the context of a *form.*
|
|
||||||
|
|
||||||
In some cases, we can resolve the problem by changing or removing the default,
|
|
||||||
but we don't always have that option -
|
|
||||||
particularly when the model is used in other places than the form
|
|
||||||
|
|
||||||
The recommended approach is to duplicate your model:
|
|
||||||
|
|
||||||
/// note
|
|
||||||
|
|
||||||
Take care to ensure that your duplicate models don't diverge,
|
|
||||||
e.g. if you are using sqlmodel,
|
|
||||||
where you may end up with `MyModel`, `MyModelCreate`, and `MyModelCreateForm`.
|
|
||||||
|
|
||||||
///
|
|
||||||
|
|
||||||
{* ../../docs_src/request_form_models/tutorial004_an_py39.py hl[7,13:25] *}
|
|
||||||
|
|
||||||
## Summary { #summary }
|
## Summary { #summary }
|
||||||
|
|
||||||
You can use Pydantic models to declare form fields in FastAPI. 😎
|
You can use Pydantic models to declare form fields in FastAPI. 😎
|
||||||
|
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
from fastapi import FastAPI, Form, Request
|
|
||||||
from fastapi.responses import HTMLResponse
|
|
||||||
from fastapi.templating import Jinja2Templates
|
|
||||||
from jinja2 import DictLoader, Environment
|
|
||||||
from pydantic import BaseModel
|
|
||||||
|
|
||||||
|
|
||||||
class MyModel(BaseModel):
|
|
||||||
checkbox: bool = True
|
|
||||||
|
|
||||||
|
|
||||||
form_template = """
|
|
||||||
<form action="/form" method="POST">
|
|
||||||
{% for field_name, field in model.model_fields.items() %}
|
|
||||||
<p>
|
|
||||||
<label for="{{ field_name }}">{{ field_name }}</label>
|
|
||||||
{% if field.annotation.__name__ == "bool" %}
|
|
||||||
<input type="checkbox" name="{{field_name}}"
|
|
||||||
{% if field.default %}
|
|
||||||
checked="checked"
|
|
||||||
{% endif %}
|
|
||||||
>
|
|
||||||
{% else %}
|
|
||||||
<input name="{{ field_name }}">
|
|
||||||
{% endif %}
|
|
||||||
</p>
|
|
||||||
{% endfor %}
|
|
||||||
<button type="submit">Submit</button>
|
|
||||||
</form>
|
|
||||||
"""
|
|
||||||
loader = DictLoader({"form.html": form_template})
|
|
||||||
templates = Jinja2Templates(env=Environment(loader=loader))
|
|
||||||
|
|
||||||
app = FastAPI()
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/form", response_class=HTMLResponse)
|
|
||||||
async def show_form(request: Request):
|
|
||||||
return templates.TemplateResponse(
|
|
||||||
request=request, name="form.html", context={"model": MyModel}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/form")
|
|
||||||
async def submit_form(data: MyModel = Form()) -> MyModel:
|
|
||||||
return data
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
from typing import Annotated
|
|
||||||
|
|
||||||
from fastapi import FastAPI, Form, Request
|
|
||||||
from fastapi.responses import HTMLResponse
|
|
||||||
from fastapi.templating import Jinja2Templates
|
|
||||||
from jinja2 import DictLoader, Environment
|
|
||||||
from pydantic import BaseModel
|
|
||||||
|
|
||||||
|
|
||||||
class MyModel(BaseModel):
|
|
||||||
checkbox: bool = True
|
|
||||||
|
|
||||||
|
|
||||||
form_template = """
|
|
||||||
<form action="/form" method="POST">
|
|
||||||
{% for field_name, field in model.model_fields.items() %}
|
|
||||||
<p>
|
|
||||||
<label for="{{ field_name }}">{{ field_name }}</label>
|
|
||||||
{% if field.annotation.__name__ == "bool" %}
|
|
||||||
<input type="checkbox" name="{{field_name}}"
|
|
||||||
{% if field.default %}
|
|
||||||
checked="checked"
|
|
||||||
{% endif %}
|
|
||||||
>
|
|
||||||
{% else %}
|
|
||||||
<input name="{{ field_name }}">
|
|
||||||
{% endif %}
|
|
||||||
</p>
|
|
||||||
{% endfor %}
|
|
||||||
<button type="submit">Submit</button>
|
|
||||||
</form>
|
|
||||||
"""
|
|
||||||
loader = DictLoader({"form.html": form_template})
|
|
||||||
templates = Jinja2Templates(env=Environment(loader=loader))
|
|
||||||
|
|
||||||
app = FastAPI()
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/form", response_class=HTMLResponse)
|
|
||||||
async def show_form(request: Request):
|
|
||||||
return templates.TemplateResponse(
|
|
||||||
request=request, name="form.html", context={"model": MyModel}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/form")
|
|
||||||
async def submit_form(data: Annotated[MyModel, Form()]) -> MyModel:
|
|
||||||
return data
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
from typing import cast
|
|
||||||
|
|
||||||
from fastapi import FastAPI, Form, Request
|
|
||||||
from fastapi.responses import HTMLResponse
|
|
||||||
from fastapi.templating import Jinja2Templates
|
|
||||||
from jinja2 import DictLoader, Environment
|
|
||||||
from pydantic import BaseModel, model_validator
|
|
||||||
|
|
||||||
|
|
||||||
class MyModel(BaseModel):
|
|
||||||
checkbox: bool = True
|
|
||||||
|
|
||||||
|
|
||||||
class MyModelForm(MyModel):
|
|
||||||
@model_validator(mode="before")
|
|
||||||
def handle_defaults(cls, value: dict) -> dict:
|
|
||||||
if "checkbox" not in value:
|
|
||||||
value["checkbox"] = False
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
form_template = """
|
|
||||||
<form action="/form" method="POST">
|
|
||||||
{% for field_name, field in model.model_fields.items() %}
|
|
||||||
<p>
|
|
||||||
<label for="{{ field_name }}">{{ field_name }}</label>
|
|
||||||
{% if field.annotation.__name__ == "bool" %}
|
|
||||||
<input type="checkbox" name="{{field_name}}"
|
|
||||||
{% if field.default %}
|
|
||||||
checked="checked"
|
|
||||||
{% endif %}
|
|
||||||
>
|
|
||||||
{% else %}
|
|
||||||
<input name="{{ field_name }}">
|
|
||||||
{% endif %}
|
|
||||||
</p>
|
|
||||||
{% endfor %}
|
|
||||||
<button type="submit">Submit</button>
|
|
||||||
</form>
|
|
||||||
"""
|
|
||||||
loader = DictLoader({"form.html": form_template})
|
|
||||||
templates = Jinja2Templates(env=Environment(loader=loader))
|
|
||||||
|
|
||||||
app = FastAPI()
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/form", response_class=HTMLResponse)
|
|
||||||
async def show_form(request: Request):
|
|
||||||
return templates.TemplateResponse(
|
|
||||||
request=request, name="form.html", context={"model": MyModel}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/form")
|
|
||||||
async def submit_form(data: MyModelForm = Form()) -> MyModel:
|
|
||||||
data = cast(MyModel, data)
|
|
||||||
return data
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
from typing import Annotated, cast
|
|
||||||
|
|
||||||
from fastapi import FastAPI, Form, Request
|
|
||||||
from fastapi.responses import HTMLResponse
|
|
||||||
from fastapi.templating import Jinja2Templates
|
|
||||||
from jinja2 import DictLoader, Environment
|
|
||||||
from pydantic import BaseModel, ValidationInfo, model_validator
|
|
||||||
|
|
||||||
|
|
||||||
class MyModel(BaseModel):
|
|
||||||
checkbox: bool = True
|
|
||||||
|
|
||||||
|
|
||||||
class MyModelForm(MyModel):
|
|
||||||
@model_validator(mode="before")
|
|
||||||
def handle_defaults(cls, value: dict, info: ValidationInfo) -> dict:
|
|
||||||
if "checkbox" not in value:
|
|
||||||
value["checkbox"] = False
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
form_template = """
|
|
||||||
<form action="/form" method="POST">
|
|
||||||
{% for field_name, field in model.model_fields.items() %}
|
|
||||||
<p>
|
|
||||||
<label for="{{ field_name }}">{{ field_name }}</label>
|
|
||||||
{% if field.annotation.__name__ == "bool" %}
|
|
||||||
<input type="checkbox" name="{{field_name}}"
|
|
||||||
{% if field.default %}
|
|
||||||
checked="checked"
|
|
||||||
{% endif %}
|
|
||||||
>
|
|
||||||
{% else %}
|
|
||||||
<input name="{{ field_name }}">
|
|
||||||
{% endif %}
|
|
||||||
</p>
|
|
||||||
{% endfor %}
|
|
||||||
<button type="submit">Submit</button>
|
|
||||||
</form>
|
|
||||||
"""
|
|
||||||
loader = DictLoader({"form.html": form_template})
|
|
||||||
templates = Jinja2Templates(env=Environment(loader=loader))
|
|
||||||
|
|
||||||
app = FastAPI()
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/form", response_class=HTMLResponse)
|
|
||||||
async def show_form(request: Request):
|
|
||||||
return templates.TemplateResponse(
|
|
||||||
request=request, name="form.html", context={"model": MyModel}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/form")
|
|
||||||
async def submit_form(data: Annotated[MyModelForm, Form()]) -> MyModel:
|
|
||||||
data = cast(MyModel, data)
|
|
||||||
return data
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
from typing import cast
|
|
||||||
|
|
||||||
from fastapi import FastAPI, Form, Request
|
|
||||||
from fastapi.responses import HTMLResponse
|
|
||||||
from fastapi.templating import Jinja2Templates
|
|
||||||
from jinja2 import DictLoader, Environment
|
|
||||||
from pydantic import BaseModel, root_validator
|
|
||||||
|
|
||||||
|
|
||||||
class MyModel(BaseModel):
|
|
||||||
checkbox: bool = True
|
|
||||||
|
|
||||||
|
|
||||||
class MyModelForm(BaseModel):
|
|
||||||
@root_validator(pre=True)
|
|
||||||
def handle_defaults(cls, value: dict) -> dict:
|
|
||||||
if "checkbox" not in value:
|
|
||||||
value["checkbox"] = False
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
form_template = """
|
|
||||||
<form action="/form" method="POST">
|
|
||||||
{% for field_name, field in model.model_fields.items() %}
|
|
||||||
<p>
|
|
||||||
<label for="{{ field_name }}">{{ field_name }}</label>
|
|
||||||
{% if field.annotation.__name__ == "bool" %}
|
|
||||||
<input type="checkbox" name="{{field_name}}"
|
|
||||||
{% if field.default %}
|
|
||||||
checked="checked"
|
|
||||||
{% endif %}
|
|
||||||
>
|
|
||||||
{% else %}
|
|
||||||
<input name="{{ field_name }}">
|
|
||||||
{% endif %}
|
|
||||||
</p>
|
|
||||||
{% endfor %}
|
|
||||||
<button type="submit">Submit</button>
|
|
||||||
</form>
|
|
||||||
"""
|
|
||||||
loader = DictLoader({"form.html": form_template})
|
|
||||||
templates = Jinja2Templates(env=Environment(loader=loader))
|
|
||||||
|
|
||||||
app = FastAPI()
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/form", response_class=HTMLResponse)
|
|
||||||
async def show_form(request: Request):
|
|
||||||
return templates.TemplateResponse(
|
|
||||||
request=request, name="form.html", context={"model": MyModel}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/form")
|
|
||||||
async def submit_form(data: MyModelForm = Form()) -> MyModel:
|
|
||||||
data = cast(MyModel, data)
|
|
||||||
return data
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
from typing import Annotated, cast
|
|
||||||
|
|
||||||
from fastapi import FastAPI, Form, Request
|
|
||||||
from fastapi.responses import HTMLResponse
|
|
||||||
from fastapi.templating import Jinja2Templates
|
|
||||||
from jinja2 import DictLoader, Environment
|
|
||||||
from pydantic import BaseModel, root_validator
|
|
||||||
|
|
||||||
|
|
||||||
class MyModel(BaseModel):
|
|
||||||
checkbox: bool = True
|
|
||||||
|
|
||||||
|
|
||||||
class MyModelForm(MyModel):
|
|
||||||
@root_validator(pre=True)
|
|
||||||
def handle_defaults(cls, value: dict) -> dict:
|
|
||||||
if "checkbox" not in value:
|
|
||||||
value["checkbox"] = False
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
form_template = """
|
|
||||||
<form action="/form" method="POST">
|
|
||||||
{% for field_name, field in model.model_fields.items() %}
|
|
||||||
<p>
|
|
||||||
<label for="{{ field_name }}">{{ field_name }}</label>
|
|
||||||
{% if field.annotation.__name__ == "bool" %}
|
|
||||||
<input type="checkbox" name="{{field_name}}"
|
|
||||||
{% if field.default %}
|
|
||||||
checked="checked"
|
|
||||||
{% endif %}
|
|
||||||
>
|
|
||||||
{% else %}
|
|
||||||
<input name="{{ field_name }}">
|
|
||||||
{% endif %}
|
|
||||||
</p>
|
|
||||||
{% endfor %}
|
|
||||||
<button type="submit">Submit</button>
|
|
||||||
</form>
|
|
||||||
"""
|
|
||||||
loader = DictLoader({"form.html": form_template})
|
|
||||||
templates = Jinja2Templates(env=Environment(loader=loader))
|
|
||||||
|
|
||||||
app = FastAPI()
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/form", response_class=HTMLResponse)
|
|
||||||
async def show_form(request: Request):
|
|
||||||
return templates.TemplateResponse(
|
|
||||||
request=request, name="form.html", context={"model": MyModel}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/form")
|
|
||||||
async def submit_form(data: Annotated[MyModelForm, Form()]) -> MyModel:
|
|
||||||
data = cast(MyModel, data)
|
|
||||||
return data
|
|
||||||
Loading…
Reference in New Issue