diff --git a/docs/en/docs/tutorial/request-form-models.md b/docs/en/docs/tutorial/request-form-models.md index 900cd1afc..68bdf198e 100644 --- a/docs/en/docs/tutorial/request-form-models.md +++ b/docs/en/docs/tutorial/request-form-models.md @@ -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 } You can use Pydantic models to declare form fields in FastAPI. 😎 diff --git a/docs_src/request_form_models/tutorial003.py b/docs_src/request_form_models/tutorial003.py deleted file mode 100644 index b0cbd6ff3..000000000 --- a/docs_src/request_form_models/tutorial003.py +++ /dev/null @@ -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 = """ -
-{% for field_name, field in model.model_fields.items() %} -

- - {% if field.annotation.__name__ == "bool" %} - - {% else %} - - {% endif %} -

-{% endfor %} - -
-""" -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 diff --git a/docs_src/request_form_models/tutorial003_an_py39.py b/docs_src/request_form_models/tutorial003_an_py39.py deleted file mode 100644 index 2c7f6a4b7..000000000 --- a/docs_src/request_form_models/tutorial003_an_py39.py +++ /dev/null @@ -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 = """ -
-{% for field_name, field in model.model_fields.items() %} -

- - {% if field.annotation.__name__ == "bool" %} - - {% else %} - - {% endif %} -

-{% endfor %} - -
-""" -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 diff --git a/docs_src/request_form_models/tutorial004.py b/docs_src/request_form_models/tutorial004.py deleted file mode 100644 index 57ce0ccf8..000000000 --- a/docs_src/request_form_models/tutorial004.py +++ /dev/null @@ -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 = """ -
-{% for field_name, field in model.model_fields.items() %} -

- - {% if field.annotation.__name__ == "bool" %} - - {% else %} - - {% endif %} -

-{% endfor %} - -
-""" -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 diff --git a/docs_src/request_form_models/tutorial004_an_py39.py b/docs_src/request_form_models/tutorial004_an_py39.py deleted file mode 100644 index 42a65ece2..000000000 --- a/docs_src/request_form_models/tutorial004_an_py39.py +++ /dev/null @@ -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 = """ -
-{% for field_name, field in model.model_fields.items() %} -

- - {% if field.annotation.__name__ == "bool" %} - - {% else %} - - {% endif %} -

-{% endfor %} - -
-""" -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 diff --git a/docs_src/request_form_models/tutorial004_pv1.py b/docs_src/request_form_models/tutorial004_pv1.py deleted file mode 100644 index 69f28aab7..000000000 --- a/docs_src/request_form_models/tutorial004_pv1.py +++ /dev/null @@ -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 = """ -
-{% for field_name, field in model.model_fields.items() %} -

- - {% if field.annotation.__name__ == "bool" %} - - {% else %} - - {% endif %} -

-{% endfor %} - -
-""" -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 diff --git a/docs_src/request_form_models/tutorial004_pv1_an_py39.py b/docs_src/request_form_models/tutorial004_pv1_an_py39.py deleted file mode 100644 index 28bbc7b32..000000000 --- a/docs_src/request_form_models/tutorial004_pv1_an_py39.py +++ /dev/null @@ -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 = """ -
-{% for field_name, field in model.model_fields.items() %} -

- - {% if field.annotation.__name__ == "bool" %} - - {% else %} - - {% endif %} -

-{% endfor %} - -
-""" -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