Skip to content

Commit 0036601

Browse files
committed
Fix form constraints, take 4
1 parent 75b4328 commit 0036601

File tree

6 files changed

+60
-10
lines changed

6 files changed

+60
-10
lines changed

demo/forms.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,9 @@ class BigModel(BaseModel):
156156
human: bool | None = Field(
157157
None, title='Is human', description='Are you human?', json_schema_extra={'mode': 'switch'}
158158
)
159-
number: int | None = Field(None, title='Number', ge=0, le=10, multiple_of=2, description='This is a number should be 0-10 and step with 2')
159+
number: int | None = Field(
160+
None, title='Number', ge=0, le=10, multiple_of=2, description='This is a number should be 0-10 and step with 2'
161+
)
160162
size: SizeModel
161163

162164
@field_validator('name')

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ interface FormFieldInputProps extends BaseFormFieldProps {
3939
/** @TJS-type ["integer", "number"] */
4040
le?: number
4141
/** @TJS-type ["integer", "number"] */
42+
gt?: number
43+
/** @TJS-type ["integer", "number"] */
44+
lt?: number
45+
/** @TJS-type ["integer", "number"] */
4246
multipleOf?: number
4347
placeholder?: string
4448
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ class FormFieldInput(BaseFormField):
3434
min_length: _t.Union[int, None] = pydantic.Field(default=None, serialization_alias='minLength')
3535
ge: _t.Union[int, float, None] = None
3636
le: _t.Union[int, float, None] = None
37+
gt: _t.Union[int, float, None] = None
38+
lt: _t.Union[int, float, None] = None
3739
multiple_of: _t.Union[int, float, None] = pydantic.Field(default=None, serialization_alias='multipleOf')
3840
type: _t.Literal['FormFieldInput'] = 'FormFieldInput'
3941

src/python-fastui/fastui/json_schema.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ class JsonSchemaString(JsonSchemaBase):
4949
type: _ta.Required[_t.Literal['string']]
5050
default: str
5151
format: _t.Literal['date', 'date-time', 'time', 'email', 'uri', 'uuid', 'password']
52+
maxLength: int
53+
minLength: int
5254

5355

5456
class JsonSchemaStringEnum(JsonSchemaBase, total=False):
@@ -86,8 +88,6 @@ class JsonSchemaInt(JsonSchemaBase, total=False):
8688
maximum: int
8789
exclusiveMaximum: int
8890
multipleOf: int
89-
maxLength: int
90-
minLength: int
9191

9292

9393
class JsonSchemaNumber(JsonSchemaBase, total=False):
@@ -195,6 +195,8 @@ def json_schema_field_to_field(
195195
min_length=schema.get('minLength'),
196196
ge=schema.get('minimum'),
197197
le=schema.get('maximum'),
198+
gt=schema.get('exclusiveMinimum'),
199+
lt=schema.get('exclusiveMaximum'),
198200
multiple_of=schema.get('multipleOf'),
199201
)
200202

src/python-fastui/tests/react-fastui-json-schema.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,9 @@
575575
"ge": {
576576
"type": ["integer", "number"]
577577
},
578+
"gt": {
579+
"type": ["integer", "number"]
580+
},
578581
"htmlType": {
579582
"enum": ["date", "datetime-local", "email", "hidden", "number", "password", "text", "time", "url"],
580583
"type": "string"
@@ -588,6 +591,9 @@
588591
"locked": {
589592
"type": "boolean"
590593
},
594+
"lt": {
595+
"type": ["integer", "number"]
596+
},
591597
"maxLength": {
592598
"type": "integer"
593599
},

src/python-fastui/tests/test_forms.py

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@
1212

1313

1414
class SimpleForm(BaseModel):
15+
name: str
16+
size: int = 4
17+
18+
19+
class FormWithConstraints(BaseModel):
1520
name: str = Field(..., max_length=10, min_length=2, description='This field is required, it must have length 2-10')
1621
size: int = Field(4, ge=0, le=10, multiple_of=2, description='size with range 0-10 and step with 2')
1722

@@ -44,9 +49,6 @@ def test_simple_form_fields():
4449
'locked': False,
4550
'htmlType': 'text',
4651
'type': 'FormFieldInput',
47-
'maxLength': 10,
48-
'minLength': 2,
49-
'description': 'This field is required, it must have length 2-10',
5052
},
5153
{
5254
'name': 'size',
@@ -56,10 +58,6 @@ def test_simple_form_fields():
5658
'locked': False,
5759
'htmlType': 'number',
5860
'type': 'FormFieldInput',
59-
'ge': 0,
60-
'le': 10,
61-
'multipleOf': 2,
62-
'description': 'size with range 0-10 and step with 2',
6361
},
6462
],
6563
}
@@ -96,6 +94,42 @@ def test_inline_form_fields():
9694
}
9795

9896

97+
def test_form_with_constraints_fields():
98+
m = components.ModelForm[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+
99133
async def test_simple_form_submit():
100134
form_dep = fastui_form(SimpleForm)
101135

0 commit comments

Comments
 (0)