Skip to content

Commit d74e65d

Browse files
committed
Fix form constraints, take 5
1 parent 4605e6d commit d74e65d

File tree

6 files changed

+79
-3
lines changed

6 files changed

+79
-3
lines changed

demo/forms.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,10 @@ class SizeModel(BaseModel):
141141

142142
class BigModel(BaseModel):
143143
name: str | None = Field(
144-
None, description='This field is not required, it must start with a capital letter if provided'
144+
None,
145+
max_length=10,
146+
min_length=2,
147+
description='This field is not required, it must start with a capital letter if provided, and have length 2-10',
145148
)
146149
info: Annotated[str | None, Textarea(rows=5)] = Field(None, description='Optional free text information about you.')
147150
profile_pic: Annotated[UploadFile, FormFile(accept='image/*', max_size=16_000)] = Field(
@@ -154,6 +157,9 @@ class BigModel(BaseModel):
154157
human: bool | None = Field(
155158
None, title='Is human', description='Are you human?', json_schema_extra={'mode': 'switch'}
156159
)
160+
number: int | None = Field(
161+
None, title='Number', ge=0, le=10, multiple_of=2, description='This is a number should be 0-10 and step with 2'
162+
)
157163
size: SizeModel
158164

159165
position: tuple[

src/npm-fastui/src/components/FormField.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ interface FormFieldInputProps extends FormFieldInput {
2424
}
2525

2626
export const FormFieldInputComp: FC<FormFieldInputProps> = (props) => {
27-
const { name, placeholder, required, htmlType, locked, autocomplete } = props
27+
const { name, placeholder, required, htmlType, locked, autocomplete, maxLength, minLength, ge, le, multipleOf } =
28+
props
2829

2930
return (
3031
<div className={useClassName(props)}>
@@ -40,6 +41,11 @@ export const FormFieldInputComp: FC<FormFieldInputProps> = (props) => {
4041
placeholder={placeholder}
4142
autoComplete={autocomplete}
4243
aria-describedby={descId(props)}
44+
maxLength={maxLength}
45+
minLength={minLength}
46+
min={ge}
47+
max={le}
48+
step={multipleOf}
4349
/>
4450
<ErrorDescription {...props} />
4551
</div>

src/npm-fastui/src/models.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,13 @@ export interface FormFieldInput {
353353
initial?: string | number
354354
placeholder?: string
355355
autocomplete?: string
356+
maxLength?: number
357+
minLength?: number
358+
ge?: number | number
359+
le?: number | number
360+
gt?: number | number
361+
lt?: number | number
362+
multipleOf?: number | number
356363
type: 'FormFieldInput'
357364
}
358365
export interface FormFieldTextarea {

src/python-fastui/fastui/components/forms.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,13 @@ class FormFieldInput(BaseFormField):
3232
initial: _t.Union[str, float, None] = None
3333
placeholder: _t.Union[str, None] = None
3434
autocomplete: _t.Union[str, None] = None
35+
max_length: _t.Union[int, None] = pydantic.Field(default=None, serialization_alias='maxLength')
36+
min_length: _t.Union[int, None] = pydantic.Field(default=None, serialization_alias='minLength')
37+
ge: _t.Union[int, float, None] = None
38+
le: _t.Union[int, float, None] = None
39+
gt: _t.Union[int, float, None] = None
40+
lt: _t.Union[int, float, None] = None
41+
multiple_of: _t.Union[int, float, None] = pydantic.Field(default=None, serialization_alias='multipleOf')
3542
type: _t.Literal['FormFieldInput'] = 'FormFieldInput'
3643

3744

src/python-fastui/fastui/json_schema.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ class JsonSchemaString(JsonSchemaBase):
4747
type: _ta.Required[_t.Literal['string']]
4848
default: str
4949
format: _t.Literal['date', 'date-time', 'time', 'email', 'uri', 'uuid', 'password']
50+
maxLength: int
51+
minLength: int
5052

5153

5254
class JsonSchemaStringEnum(JsonSchemaBase, total=False):
@@ -197,6 +199,13 @@ def json_schema_field_to_field(
197199
initial=schema.get('default'),
198200
autocomplete=schema.get('autocomplete'),
199201
description=schema.get('description'),
202+
max_length=schema.get('maxLength'),
203+
min_length=schema.get('minLength'),
204+
ge=schema.get('minimum'),
205+
le=schema.get('maximum'),
206+
gt=schema.get('exclusiveMinimum'),
207+
lt=schema.get('exclusiveMaximum'),
208+
multiple_of=schema.get('multipleOf'),
200209
)
201210

202211

src/python-fastui/tests/test_forms.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from fastapi import HTTPException
77
from fastui import components
88
from fastui.forms import FormFile, Textarea, fastui_form
9-
from pydantic import BaseModel
9+
from pydantic import BaseModel, Field
1010
from starlette.datastructures import FormData, Headers, UploadFile
1111
from typing_extensions import Annotated
1212

@@ -16,6 +16,11 @@ class SimpleForm(BaseModel):
1616
size: int = 4
1717

1818

19+
class FormWithConstraints(BaseModel):
20+
name: str = Field(..., max_length=10, min_length=2, description='This field is required, it must have length 2-10')
21+
size: int = Field(4, ge=0, le=10, multiple_of=2, description='size with range 0-10 and step with 2')
22+
23+
1924
class FakeRequest:
2025
"""
2126
TODO replace this with httpx or similar maybe, perhaps this is sufficient
@@ -89,6 +94,42 @@ def test_inline_form_fields():
8994
}
9095

9196

97+
def test_form_with_constraints_fields():
98+
m = components.ModelForm(model=FormWithConstraints, submit_url='/foobar/')
99+
100+
assert m.model_dump(by_alias=True, exclude_none=True) == {
101+
'submitUrl': '/foobar/',
102+
'method': 'POST',
103+
'type': 'ModelForm',
104+
'formFields': [
105+
{
106+
'name': 'name',
107+
'title': ['Name'],
108+
'required': True,
109+
'locked': False,
110+
'htmlType': 'text',
111+
'type': 'FormFieldInput',
112+
'description': 'This field is required, it must have length 2-10',
113+
'maxLength': 10,
114+
'minLength': 2,
115+
},
116+
{
117+
'name': 'size',
118+
'title': ['Size'],
119+
'initial': 4,
120+
'required': False,
121+
'locked': False,
122+
'htmlType': 'number',
123+
'type': 'FormFieldInput',
124+
'description': 'size with range 0-10 and step with 2',
125+
'le': 10,
126+
'ge': 0,
127+
'multipleOf': 2,
128+
},
129+
],
130+
}
131+
132+
92133
async def test_simple_form_submit():
93134
form_dep = fastui_form(SimpleForm)
94135

0 commit comments

Comments
 (0)