Repository: vitalik/django-ninja (python)

Issue:
Documentation Error on POSTing data
Hello, I have a POST endpoint, with this params:
```python
@router.post('/v1/update')
def update(request, id: int=Form(...), descricao: str=Form(None), id_reporte: int=Form(None), id_tipo: int=Form(None), id_usuario: int=Form(None), is_active: bool=Form(None)):
    try:
        table = Table.objects.filter(id=id, is_deleted=False).first()
        if descricao:
            table.descricao = descricao
        if id_reporte:
            id_reporte = None if id_reporte == 'null' else id_reporte
            table.estrutura_reporte_id = id_reporte
        if id_tipo:
            table.tipo_id = id_tipo
        if id_usuario:
            table.usuario_responsavel_id = id_usuario
        if is_active is not None:
            is_active = 1 if is_active else 0
            table.is_active = is_active
        table.save()
    except Exception:
        raise HttpError(400, 'Bad Request')
...
```


The only required parameter is the **id**, but every time I try to make a request on swagger I receive this error, even if I flag the arguments as **Send Empty Value**:

![image](https://user-images.githubusercontent.com/40257388/116609780-2971f280-a90b-11eb-84fa-d24eaf8f7b4b.png)
![image](https://user-images.githubusercontent.com/40257388/116609856-3f7fb300-a90b-11eb-9ddc-0bfcea479ed1.png)

These are the CURL headers being sent on swagger 
```sh
 -H  "Content-Type: application/x-www-form-urlencoded" -d "id=2&descricao=&id_reporte=&id_tipo=&id_usuario=&is_active="
 ```


Pull request:
Document how to handle empty form fields.
Closes #128, since other issues there, were already dealt with via #226

Patch:
diff --git a/docs/docs/help.md b/docs/docs/help.md
index 5f6f0e0..d5e3a26 100644
--- a/docs/docs/help.md
+++ b/docs/docs/help.md
@@ -8,7 +8,7 @@ Simply give django-ninja a <a href="https://github.com/vitalik/django-ninja" tar
 
 or share this URL on social media: 
 ```
-http://django-ninja.rest-framework.com
+https://django-ninja.rest-framework.com
 ```
 
 ## Do you want to help us?
@@ -17,12 +17,12 @@ Pull requests are always welcome.
 
 You can inspect our docs for typos and spelling mistakes, and create pull requests or <a href="https://github.com/vitalik/django-ninja/issues" target="_blank">open an issue</a>.
 
-If you have any suggestions to improve **Django Ninja**, please create them as <a href="https://github.com/vitalik/django-ninja/issues" target="_blank">issues</a> on Github.
+If you have any suggestions to improve **Django Ninja**, please create them as <a href="https://github.com/vitalik/django-ninja/issues" target="_blank">issues</a> on GitHub.
 
 
 ## Do you need help?
 
-Do not hesitate.  Go to <a href="https://github.com/vitalik/django-ninja/issues" target="_blank">github issues</a> and describe your question or problem.  We'll attempt to address them quickly.
+Do not hesitate.  Go to <a href="https://github.com/vitalik/django-ninja/issues" target="_blank">GitHub issues</a> and describe your question or problem.  We'll attempt to address them quickly.
 
 Join the chat at our <a href="https://discord.gg/dgE4SNUDTB" target="_blank">Discord</a> server.
 
diff --git a/docs/docs/tutorial/authentication.md b/docs/docs/tutorial/authentication.md
index ba74690..b2d40ea 100644
--- a/docs/docs/tutorial/authentication.md
+++ b/docs/docs/tutorial/authentication.md
@@ -98,7 +98,7 @@ GET /something HTTP/1.1
 Cookie: X-API-KEY=abcdef12345
 ```
 
-Django Ninja comes with built in classes to help you handle these cases.
+**Django Ninja** comes with built-in classes to help you handle these cases.
 
 
 #### in Query
@@ -167,7 +167,8 @@ router = Router(auth=BasicAuth())
 
 ## Custom exceptions
 
-Raising an exception that has a exception handler will return the response from that handler in the same way a operation would:
+Raising an exception that has an exception handler will return the response from that handler in
+the same way an operation would:
 
 ```Python hl_lines="1 4"
 {!./src/tutorial/authentication/bearer02.py!}
diff --git a/docs/docs/tutorial/body.md b/docs/docs/tutorial/body.md
index 6acb094..489a1c9 100644
--- a/docs/docs/tutorial/body.md
+++ b/docs/docs/tutorial/body.md
@@ -68,8 +68,10 @@ With just that Python type declaration, **Django Ninja** will:
 * Validate the data.
     * If the data is invalid, it will return a nice and meaningful error, indicating exactly where and what the incorrect data was.
 * Give you the received data in the parameter `item`.
-    * Because you declared it in the function to be of type `Item`, you will also have all the editor support (completion, etc.) for all the attributes and their types.
-* Generate <a href="http://json-schema.org" class="external-link" target="_blank">JSON Schema</a> definitions for your models, and you can also use them anywhere else you like if it makes sense for your project.
+    * Because you declared it in the function to be of type `Item`, you will also have all the editor support
+      (completion, etc.) for all the attributes and their types.
+* Generate <a href="https://json-schema.org" class="external-link" target="_blank">JSON Schema</a> definitions for
+  your models, and you can also use them anywhere else you like if it makes sense for your project.
 * Those schemas will be part of the generated OpenAPI schema, and used by the automatic documentation <abbr title="User Interfaces">UI's</abbr>.
 
 ## Automatic docs
diff --git a/docs/docs/tutorial/config-pydantic.md b/docs/docs/tutorial/config-pydantic.md
index 47d62f0..6f90309 100644
--- a/docs/docs/tutorial/config-pydantic.md
+++ b/docs/docs/tutorial/config-pydantic.md
@@ -1,6 +1,6 @@
 # Overriding Pydantic Config
 
-There are many cutomizations available for a **Django Ninja `Schema`**, via the schema's
+There are many customizations available for a **Django Ninja `Schema`**, via the schema's
 [Pydantic `Config` class](https://pydantic-docs.helpmanual.io/usage/model_config/). 
 
 !!! info
@@ -35,8 +35,8 @@ class CamelModelSchema(Schema):
 
 ## Custom Config from Django Model
 
-When using [`create_schema`](/tutorial/django-pydantic), the resulting schema can be used to
-build another class with a custom config like:
+When using [`create_schema`](../django-pydantic-create-schema/#create_schema), the resulting
+schema can be used to build another class with a custom config like:
 
 ```Python hl_lines="10"
 from django.contrib.auth.models import User
diff --git a/docs/docs/tutorial/csrf.md b/docs/docs/tutorial/csrf.md
index 6e591de..8858ffe 100644
--- a/docs/docs/tutorial/csrf.md
+++ b/docs/docs/tutorial/csrf.md
@@ -1,6 +1,6 @@
 # CSRF
 
-By default, Django Ninja has CSRF turned **OFF** for all operations.
+By default, **Django Ninja** has CSRF turned **OFF** for all operations.
 To turn it on you need to use the `csrf` argument of the NinjaAPI class:
 
 
@@ -15,7 +15,7 @@ api = NinjaAPI(csrf=True)
 <span style="color: red;">Warning</span>: It is not secure to use API's with cookie-based authentication! (like `CookieKey`, or `django_auth`)
 
 
-Django Ninja will prevent you from doing this. So, if you do this:
+**Django Ninja** will prevent you from doing this. So, if you do this:
 
 
 ```Python hl_lines="4"
@@ -35,4 +35,4 @@ from ninja.security import django_auth
 
 api = NinjaAPI(auth=django_auth, csrf=True)
 
-```
\ No newline at end of file
+```
diff --git a/docs/docs/tutorial/django-pydantic-create-schema.md b/docs/docs/tutorial/django-pydantic-create-schema.md
index a8fa1e7..dd2720d 100644
--- a/docs/docs/tutorial/django-pydantic-create-schema.md
+++ b/docs/docs/tutorial/django-pydantic-create-schema.md
@@ -1,7 +1,11 @@
-# create_schema
+# Using create_schema
 
-Under the hood ModelSchema uses `create_schema` function. This is more advanced (and less safe) method - please use it carefully
+Under the hood, [`ModelSchema`](../django-pydantic/#modelschema) uses the `create_schema` function.
+This is a more advanced (and less safe) method - please use it carefully.
 
+## `create_schema`
+
+**Django Ninja** comes with a helper function `create_schema`:
 
 ```Python
 def create_schema(
@@ -44,7 +48,7 @@ UserSchema = create_schema(User)
     <br>
     **Always** use `fields` or `exclude` arguments to explicitly define list of attributes.
 
-### using `fields`
+### Using `fields`
 
 ```Python hl_lines="1"
 UserSchema = create_schema(User, fields=['id', 'username'])
@@ -57,7 +61,7 @@ UserSchema = create_schema(User, fields=['id', 'username'])
 
 ```
 
-### using `exclude`
+### Using `exclude`
 
 ```Python hl_lines="1 2"
 UserSchema = create_schema(User, exclude=[
@@ -76,7 +80,7 @@ UserSchema = create_schema(User, exclude=[
 #    date_joined: datetime
 ```
 
-### using `depth`
+### Using `depth`
 
 The `depth` argument allows you to introspect the Django model into the Related fields(ForeignKey, OneToOne, ManyToMany).
 
diff --git a/docs/docs/tutorial/django-pydantic.md b/docs/docs/tutorial/django-pydantic.md
index adac99b..dde7906 100644
--- a/docs/docs/tutorial/django-pydantic.md
+++ b/docs/docs/tutorial/django-pydantic.md
@@ -30,7 +30,7 @@ class UserSchema(ModelSchema):
 
 ### Excluding model fields
 
-To use all fields **except** few  you can use `model_exclude` configuration:
+To use all fields **except** a few, you can use `model_exclude` configuration:
 
 ```Python hl_lines="4"
 class UserSchema(ModelSchema):
@@ -53,7 +53,7 @@ class UserSchema(ModelSchema):
 
 ### Overriding fields
 
-To change default annotation for some field or add new one just use annotated attributes as usual. 
+To change default annotation for some field, or to add a new field, just use annotated attributes as usual. 
 
 ```Python hl_lines="1 2 3 4 8"
 class GroupSchema(ModelSchema):
diff --git a/docs/docs/tutorial/errors.md b/docs/docs/tutorial/errors.md
index a49dc77..007fa52 100644
--- a/docs/docs/tutorial/errors.md
+++ b/docs/docs/tutorial/errors.md
@@ -1,6 +1,6 @@
 # Handling errors
 
-Django Ninja allows you to install custom exception handlers to deal with how you return responses when errors or handled exceptions occur.
+**Django Ninja** allows you to install custom exception handlers to deal with how you return responses when errors or handled exceptions occur.
 
 ## Custom exception handlers
 
@@ -52,7 +52,7 @@ function must return http response
 
 ## Override the default exception handlers
 
-By default Django Ninja initialized the following exception handlers:
+By default, **Django Ninja** initialized the following exception handlers:
 
 
 #### `ninja.errors.ValidationError`
diff --git a/docs/docs/tutorial/form-params.md b/docs/docs/tutorial/form-params.md
index 64db9e6..c352a70 100644
--- a/docs/docs/tutorial/form-params.md
+++ b/docs/docs/tutorial/form-params.md
@@ -1,8 +1,9 @@
 # Form data
 
-**Django Ninja** also allows you to parse and validate `request.POST` data (aka `application x-www-form-urlencoded` or `multipart/form-data`).
+**Django Ninja** also allows you to parse and validate `request.POST` data
+(aka `application x-www-form-urlencoded` or `multipart/form-data`).
 
-Here's a quick example:
+## Form Data as params 
 
 ```Python hl_lines="1 4"
 from ninja import NinjaAPI, Form
@@ -24,3 +25,37 @@ from ninja import Form
 username: str = Form(...)
 ```
 
+## Using a Schema
+
+In a similar manner to [Body](../body/#declare-it-as-a-parameter), you can use
+a Schema to organize your parameters.
+
+```Python hl_lines="12"
+{!./src/tutorial/form/code01.py!}
+```
+
+## Request form + path + query parameters
+
+In a similar manner to [Body](../body/#request-body-path-query-parameters), you can use
+Form data in combination with other parameter sources.
+
+You can declare query **and** path **and** form field, **and** etc... parameters at the same time.
+
+**Django Ninja** will recognize that the function parameters that match path
+parameters should be **taken from the path**, and that function parameters that
+are declared with `Form(...)` should be **taken from the request form fields**, etc.
+
+```Python hl_lines="12"
+{!./src/tutorial/form/code02.py!}
+```
+## Mapping Empty Form Field to Default
+
+Form fields that are optional, are often sent with an empty value. This value is
+interpreted as an empty string, and thus may fail validation for fields such as `int` or `bool`.
+
+This can be fixed, as described in the Pydantic docs, by using
+[Generic Classes as Types](https://pydantic-docs.helpmanual.io/usage/types/#generic-classes-as-types).
+
+```Python hl_lines="15 16 23-25"
+{!./src/tutorial/form/code03.py!}
+```
diff --git a/docs/docs/tutorial/index.md b/docs/docs/tutorial/index.md
index dad3326..f19af3a 100644
--- a/docs/docs/tutorial/index.md
+++ b/docs/docs/tutorial/index.md
@@ -74,7 +74,7 @@ urlpatterns = [
  - ... and more
 
 
-Django Ninja comes with a decorator for each method:
+**Django Ninja** comes with a decorator for each method:
 
 
 ```Python hl_lines="1 5 9 13 17"
@@ -106,4 +106,4 @@ If you need to handle multiple methods with a single function, you can use the `
 @api.api_operation(["POST", "PATCH"])
 def mixed(request):
     ...
-```
\ No newline at end of file
+```
diff --git a/docs/docs/tutorial/operation_params.md b/docs/docs/tutorial/operation_params.md
index d23ed4c..4f82787 100644
--- a/docs/docs/tutorial/operation_params.md
+++ b/docs/docs/tutorial/operation_params.md
@@ -91,7 +91,7 @@ def create_order(request, order: Order):
 
 The OpenAPI `operationId` is an optional unique string used to identify an operation. If provided, these IDs must be unique among all operations described in your API.
 
-By default, Django Ninja sets it to `module name` + `function name`.
+By default, **Django Ninja** sets it to `module name` + `function name`.
 
 If you want to set it individually for each operation, use the `operation_id` argument:
 
diff --git a/docs/docs/tutorial/pagination.md b/docs/docs/tutorial/pagination.md
index b499a21..9a4f854 100644
--- a/docs/docs/tutorial/pagination.md
+++ b/docs/docs/tutorial/pagination.md
@@ -1,6 +1,6 @@
 # Pagination (beta)
 
-Django Ninja comes with a pagination. This allows you to split large result sets into individual pages.
+**Django Ninja** comes with a pagination. This allows you to split large result sets into individual pages.
 
 
 To apply pagination to a function - just apply `paginate` decorator:
@@ -92,7 +92,7 @@ To create a custom pagination class you should subclass `ninja.pagination.Pagina
  - The `paginate_queryset` method is passed the initial queryset and should return an iterable object that contains only the data in the requested page. This method accepts the following arguments:
     - `items`: a queryset (or iterable) returned by the api function
     - `request`: django http request object
-    - `**params`: kwargs that will contain all the arguments that decorated function recieved (to access pagination input get `params["pagination"]` - it will be a validated instance of your `Input` class) 
+    - `**params`: kwargs that will contain all the arguments that decorated function received (to access pagination input get `params["pagination"]` - it will be a validated instance of your `Input` class) 
 
 
 Example:
diff --git a/docs/docs/tutorial/path-params.md b/docs/docs/tutorial/path-params.md
index 5beb039..e73f218 100644
--- a/docs/docs/tutorial/path-params.md
+++ b/docs/docs/tutorial/path-params.md
@@ -30,7 +30,7 @@ If you run this in your browser with <a href="http://localhost:8000/api/items/3"
 
 !!! tip
     Notice that the value your function received (and returned) is **3**, as a Python `int` - not a string `"3"`.
-    So, with just that type declaration, Django Ninja gives you automatic request "parsing" and validation.
+    So, with just that type declaration, **Django Ninja** gives you automatic request "parsing" and validation.
 
 
 
@@ -68,7 +68,7 @@ In this case,`item_id` will be parsed as an **`int`**. If `item_id` is not a val
 match.  (e.g. if no other path matches, a *404 Not Found* will be returned)
 
 !!! tip
-    Notice that, since Django Ninja uses a default type of `str` for unannotated parameters, the value the
+    Notice that, since **Django Ninja** uses a default type of `str` for unannotated parameters, the value the
     function above received (and returned) is `"3"`, as a Python `str` - not an integer **3**, . To receive
     an `int`, simply declare `item_id` as an `int` type annotation in the function definition as normal:
 
@@ -100,9 +100,8 @@ You can also use Schema to encapsulate path parameters that depend on each other
 ```
 
 !!! note
-    Notice that here we used a `Path` source hint to let Django Ninja know that this schema will be applied to path parameters.
+    Notice that here we used a `Path` source hint to let **Django Ninja** know that this schema will be applied to path parameters.
 
 ### Documentation
 Now, when you open your browser at <a href="http://localhost:8000/api/docs" target="_blank">http://localhost:8000/api/docs</a>, you will see the automatic, interactive, API documentation.
 ![Django Ninja Swagger](../img/tutorial-path-swagger.png)
-
diff --git a/docs/docs/tutorial/query-params.md b/docs/docs/tutorial/query-params.md
index 8399e67..8871af7 100644
--- a/docs/docs/tutorial/query-params.md
+++ b/docs/docs/tutorial/query-params.md
@@ -69,7 +69,7 @@ You can declare required or optional GET parameters in the same way as declaring
 {!./src/tutorial/query/code02.py!}
 ```
 
-In this case, Django Ninja will always validate that you pass the `q` param in the GET, and the `offset` param is an optional integer.
+In this case, **Django Ninja** will always validate that you pass the `q` param in the GET, and the `offset` param is an optional integer.
 
 ### GET parameters type conversion
 
@@ -87,7 +87,8 @@ http://localhost:8000/api/example?b=true
 http://localhost:8000/api/example?b=on
 http://localhost:8000/api/example?b=yes
 ```
-or any other case variation (uppercase, first letter in uppercase, etc), your function will see the parameter `b` with a `bool` value of `True`, otherwise as `False`.
+or any other case variation (uppercase, first letter in uppercase, etc.), your function will see
+the parameter `b` with a `bool` value of `True`, otherwise as `False`.
 
 Date can be both date string and integer (unix timestamp):
 
diff --git a/docs/docs/tutorial/request-parsers.md b/docs/docs/tutorial/request-parsers.md
index c148b02..aee1fbc 100644
--- a/docs/docs/tutorial/request-parsers.md
+++ b/docs/docs/tutorial/request-parsers.md
@@ -1,6 +1,7 @@
 # Request parsers
 
-In most cases, the default content type for REST API's is JSON, but in case you need to work with other content types (like YAML, XML, CSV) or use faster JSON parsers, Django Ninja provides a `parser` configuration.
+In most cases, the default content type for REST API's is JSON, but in case you need to work with
+other content types (like YAML, XML, CSV) or use faster JSON parsers, **Django Ninja** provides a `parser` configuration.
 
 ```Python
 api = NinjaAPI(parser=MyYamlParser())
diff --git a/docs/docs/tutorial/response-renderers.md b/docs/docs/tutorial/response-renderers.md
index dd2453c..72b830e 100644
--- a/docs/docs/tutorial/response-renderers.md
+++ b/docs/docs/tutorial/response-renderers.md
@@ -1,7 +1,7 @@
 # Response renderers
 
 The most common response type for a REST API is usually JSON.
-Django Ninja also has support for defining your own custom renderers, which gives you the flexibility to design your own media types.
+**Django Ninja** also has support for defining your own custom renderers, which gives you the flexibility to design your own media types.
 
 ## Create a renderer
 
@@ -103,4 +103,4 @@ class XMLRenderer(BaseRenderer):
 
 api = NinjaAPI(renderer=XMLRenderer())
 ```
-*(Copyright note: this code is basically copied from [DRF-xml](https://jpadilla.github.io/django-rest-framework-xml/))*
\ No newline at end of file
+*(Copyright note: this code is basically copied from [DRF-xml](https://jpadilla.github.io/django-rest-framework-xml/))*
diff --git a/docs/docs/tutorial/response-schema.md b/docs/docs/tutorial/response-schema.md
index 7f1359b..fdfbc23 100644
--- a/docs/docs/tutorial/response-schema.md
+++ b/docs/docs/tutorial/response-schema.md
@@ -46,7 +46,7 @@ def create_user(request, data: UserIn):
     return user
 ```
 
-Django Ninja will use this `response` schema to:
+**Django Ninja** will use this `response` schema to:
 
  - convert the output data to declared schema
  - validate the data
@@ -186,7 +186,7 @@ You can pass to a `response` argument a dictionary where:
  - key is a response code
  - value is a schema for that code
 
-Also, when you return the result - you have to also pass a status code to tell Django Ninja which schema should be used for validation and serialization.
+Also, when you return the result - you have to also pass a status code to tell **Django Ninja** which schema should be used for validation and serialization.
 
 
 An example:
@@ -233,7 +233,7 @@ def login(request, payload: Auth):
     return 200, {'token': xxx, ...}
 ```
 
-Django Ninja comes with the following HTTP codes:
+**Django Ninja** comes with the following HTTP codes:
 
 ```Python
 from ninja.responses import codes_1xx
@@ -270,7 +270,7 @@ Sometimes you need to create a schema that has reference to itself, or tree-stru
 
 To do that you need:
 
- - set a type of you schema in quotes
+ - set a type of your schema in quotes
  - use `update_forward_refs` method to apply self referencing types
 
 ```Python hl_lines="3 6"
diff --git a/docs/docs/tutorial/versioning.md b/docs/docs/tutorial/versioning.md
index 1d89340..f31f086 100644
--- a/docs/docs/tutorial/versioning.md
+++ b/docs/docs/tutorial/versioning.md
@@ -2,7 +2,7 @@
 
 ## Different API version numbers
 
-With Django Ninja it's easy to run multiple API versions from a single Django project.
+With **Django Ninja** it's easy to run multiple API versions from a single Django project.
 
 All you have to do is create two or more NinjaAPI instances with different `version` arguments:
 
diff --git a/docs/src/tutorial/form/code01.py b/docs/src/tutorial/form/code01.py
new file mode 100644
index 0000000..79f0c38
--- /dev/null
+++ b/docs/src/tutorial/form/code01.py
@@ -0,0 +1,13 @@
+from ninja import Form, Schema
+
+
+class Item(Schema):
+    name: str
+    description: str = None
+    price: float
+    quantity: int
+
+
+@api.post("/items")
+def create(request, item: Item = Form(...)):
+    return item
diff --git a/docs/src/tutorial/form/code02.py b/docs/src/tutorial/form/code02.py
new file mode 100644
index 0000000..26761cf
--- /dev/null
+++ b/docs/src/tutorial/form/code02.py
@@ -0,0 +1,13 @@
+from ninja import Form, Schema
+
+
+class Item(Schema):
+    name: str
+    description: str = None
+    price: float
+    quantity: int
+
+
+@api.post("/items/{item_id}")
+def update(request, item_id: int, q: str, item: Item=Form(...)):
+    return {"item_id": item_id, "item": item.dict(), "q": q}
diff --git a/docs/src/tutorial/form/code03.py b/docs/src/tutorial/form/code03.py
new file mode 100644
index 0000000..55c79e8
--- /dev/null
+++ b/docs/src/tutorial/form/code03.py
@@ -0,0 +1,30 @@
+from ninja import Form, Schema
+from pydantic.fields import ModelField
+from typing import Generic, TypeVar
+
+PydanticField = TypeVar("PydanticField")
+
+
+class EmptyStrToDefault(Generic[PydanticField]):
+    @classmethod
+    def __get_validators__(cls):
+        yield cls.validate
+
+    @classmethod
+    def validate(cls, value: PydanticField, field: ModelField) -> PydanticField:
+        if value == "":
+            return field.default
+        return value
+
+
+class Item(Schema):
+    name: str
+    description: str = None
+    price: EmptyStrToDefault[float] = 0.0
+    quantity: EmptyStrToDefault[int] = 0
+    in_stock: EmptyStrToDefault[bool] = True
+
+
+@api.post("/items-blank-default")
+def update(request, item: Item=Form(...)):
+    return item.dict()


Test patch:
diff --git a/tests/test_docs/test_form.py b/tests/test_docs/test_form.py
new file mode 100644
index 0000000..0bc3597
--- /dev/null
+++ b/tests/test_docs/test_form.py
@@ -0,0 +1,53 @@
+from unittest.mock import patch
+
+from ninja import NinjaAPI
+from ninja.testing import TestClient
+
+
+def test_examples():
+
+    api = NinjaAPI()
+
+    with patch("builtins.api", api, create=True):
+        import docs.src.tutorial.form.code01  # noqa: F401
+        import docs.src.tutorial.form.code02  # noqa: F401
+
+        client = TestClient(api)
+
+        assert client.post(
+            "/items", data={"name": "Katana", "price": 299.00, "quantity": 10}
+        ).json() == {
+            "name": "Katana",
+            "description": None,
+            "price": 299.0,
+            "quantity": 10,
+        }
+
+        assert client.post(
+            "/items/1?q=test", data={"name": "Katana", "price": 299.00, "quantity": 10}
+        ).json() == {
+            "item_id": 1,
+            "q": "test",
+            "item": {
+                "name": "Katana",
+                "description": None,
+                "price": 299.0,
+                "quantity": 10,
+            },
+        }
+
+    with patch("builtins.api", api, create=True):
+        import docs.src.tutorial.form.code03  # noqa: F401
+
+        client = TestClient(api)
+
+        assert client.post(
+            "/items-blank-default",
+            data={"name": "Katana", "price": "", "quantity": "", "in_stock": ""},
+        ).json() == {
+            "name": "Katana",
+            "description": None,
+            "in_stock": True,
+            "price": 0.0,
+            "quantity": 0,
+        }