Skip to content

Commit 810cafb

Browse files
committed
Fix filter reset issue. Fix date/time handling in transform helpers. Fix examples. Fix JSON textarea handling.
1 parent c024616 commit 810cafb

11 files changed

Lines changed: 338 additions & 183 deletions

File tree

docs/build.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,15 @@ def read_cls_docstring(cls):
4242

4343
def get_versions():
4444
return [
45+
{
46+
"version": "0.3.7",
47+
"changes": [
48+
"Fix filter reset issue.",
49+
"Fix date/time handling in transform helpers.",
50+
"Fix examples.",
51+
"Fix JSON textarea handling.",
52+
],
53+
},
4554
{
4655
"version": "0.3.6",
4756
"changes": [

docs/index.html

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,10 @@ <h4 class="title">FastAdmin</h4>
196196

197197
<ul class="nav flex-column">
198198

199+
<li class="nav-item">
200+
<a class="nav-link" href="#v0_3_7">v0.3.7</a>
201+
</li>
202+
199203
<li class="nav-item">
200204
<a class="nav-link" href="#v0_3_6">v0.3.6</a>
201205
</li>
@@ -331,7 +335,7 @@ <h1>FastAdmin | Documentation</h1>
331335
<div class="row">
332336
<div class="col-sm-6 col-lg-4">
333337
<ul class="list-unstyled">
334-
<li><strong>Version:</strong> 0.3.6</li>
338+
<li><strong>Version:</strong> 0.3.7</li>
335339
<li>
336340
<strong>Author:</strong>
337341
<a href="mailto:vsdudakov@gmail.com" target="_blank">
@@ -348,7 +352,7 @@ <h1>FastAdmin | Documentation</h1>
348352
</li>
349353
<li>
350354
<strong>Updated:</strong>
351-
24 February 2026
355+
27 February 2026
352356
</li>
353357
</ul>
354358
</div>
@@ -3357,6 +3361,85 @@ <h2>Changelog</h2>
33573361

33583362

33593363

3364+
<section id="v0_3_7">
3365+
<h3>v0.3.7</h3>
3366+
3367+
3368+
3369+
<p class="text-4">
3370+
Fix filter reset issue.
3371+
</p>
3372+
3373+
3374+
3375+
3376+
3377+
3378+
3379+
3380+
3381+
3382+
3383+
3384+
3385+
3386+
3387+
<p class="text-4">
3388+
Fix date/time handling in transform helpers.
3389+
</p>
3390+
3391+
3392+
3393+
3394+
3395+
3396+
3397+
3398+
3399+
3400+
3401+
3402+
3403+
3404+
3405+
<p class="text-4">
3406+
Fix examples.
3407+
</p>
3408+
3409+
3410+
3411+
3412+
3413+
3414+
3415+
3416+
3417+
3418+
3419+
3420+
3421+
3422+
3423+
<p class="text-4">
3424+
Fix JSON textarea handling.
3425+
</p>
3426+
3427+
3428+
3429+
3430+
3431+
3432+
3433+
3434+
3435+
3436+
3437+
3438+
3439+
3440+
</section>
3441+
3442+
33603443
<section id="v0_3_6">
33613444
<h3>v0.3.6</h3>
33623445

examples/fastapi_tortoiseorm/example.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,18 @@ class BaseEventModelAdmin(TortoiseModelAdmin):
7575
@register(Event)
7676
class EventModelAdmin(TortoiseModelAdmin):
7777
actions = ("make_is_active", "make_is_not_active")
78-
list_display = ("id", "tournament_name", "name_with_price", "rating", "event_type", "is_active", "started")
79-
list_filter = ("event_type", "is_active")
78+
list_display = (
79+
"id",
80+
"tournament_name",
81+
"name_with_price",
82+
"rating",
83+
"event_type",
84+
"is_active",
85+
"started",
86+
"start_time",
87+
"date",
88+
)
89+
list_filter = ("event_type", "is_active", "start_time", "date", "event_type")
8090
search_fields = ("name", "tournament__name")
8191
list_select_related = ("tournament",)
8292

@@ -119,6 +129,8 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
119129
db_url="sqlite://:memory:",
120130
modules={"models": ["models"]},
121131
generate_schemas=True,
132+
use_tz=False,
133+
timezone="UTC",
122134
):
123135
await create_superuser()
124136
yield

fastadmin/static/index.min.js

Lines changed: 164 additions & 164 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/src/components/form-container/index.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,11 @@ export const FormContainer: React.FC<IFormContainer> = ({
138138
? [
139139
{
140140
validator: async (_: any, value: string) => {
141-
if (!isJson(value)) {
141+
if (
142+
value !== undefined &&
143+
value !== null &&
144+
!isJson(value)
145+
) {
142146
throw new Error(_t("Invalid JSON") as string);
143147
}
144148
},

frontend/src/components/json-textarea/index.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@ export const JsonTextArea: React.FC<IJsonTextAreaProps> = ({
1717
...props
1818
}) => {
1919
const { token } = useToken();
20-
const jsonValue = !isString(value)
21-
? JSON.stringify(value, null, "\t")
22-
: value;
20+
const jsonValue =
21+
value !== undefined && value !== null && !isString(value)
22+
? JSON.stringify(value, null, "\t")
23+
: value;
2324
const rowsCount = jsonValue?.split(/\r\n|\r|\n/)?.length;
2425
return (
2526
<Input.TextArea

frontend/src/containers/list/index.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,18 @@ export const List: React.FC = () => {
125125
useCallback(
126126
(fieldName: string, value: any) => {
127127
// onApplyFilter
128-
setFilters({ ...filters, [fieldName]: value });
128+
if (
129+
(Array.isArray(value) && value.length === 0) ||
130+
value === null ||
131+
value === undefined
132+
) {
133+
// reset filter
134+
const rest = { ...filters };
135+
delete rest[fieldName];
136+
setFilters(rest);
137+
} else {
138+
setFilters({ ...filters, [fieldName]: value });
139+
}
129140
setPage(defaultPage);
130141
setPageSize(defaultPageSize);
131142
},

frontend/src/helpers/transform.test.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,16 @@ describe("transform", () => {
170170
name__icontains: "foo",
171171
});
172172
});
173+
it("handles empty arrays for __in filters", () => {
174+
expect(transformFiltersToServer({ status: [] })).toEqual({
175+
status__in: "",
176+
});
177+
});
178+
it("handles empty string values as icontains", () => {
179+
expect(transformFiltersToServer({ status: "" })).toEqual({
180+
status__icontains: "",
181+
});
182+
});
173183
});
174184

175185
describe("transformValueFromServer", () => {

frontend/src/helpers/transform.tsx

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,20 @@ export const isTime = (v: string): boolean => {
1111
return timeLikeRegex.test(v);
1212
};
1313

14+
export const isDate = (v: string): boolean => {
15+
// Accept common backend date shapes, including:
16+
// YYYY-MM-DD
17+
const dateLikeRegex = /^\d{4}-\d{2}-\d{2}$/;
18+
return dateLikeRegex.test(v);
19+
};
20+
1421
export const isDateTime = (v: string): boolean => {
1522
// Accept common backend date/datetime shapes, including:
16-
// YYYY-MM-DD, YYYY-MM-DDTHH:mm:ss(.ffffff), YYYY-MM-DD HH:mm(:ss)
23+
// YYYY-MM-DDTHH:mm:ss(.ffffff), YYYY-MM-DD HH:mm(:ss)
1724
// with optional timezone suffix.
18-
const dateLikeRegex =
25+
const dateTimeLikeRegex =
1926
/^\d{4}-\d{2}-\d{2}(?:[T\s]\d{2}:\d{2}(?::\d{2}(?:\.\d{1,6})?)?(?:Z|[+-]\d{2}:?\d{2})?)?$/;
20-
return dateLikeRegex.test(v);
27+
return dateTimeLikeRegex.test(v);
2128
};
2229

2330
export const isNumeric = (v: string): boolean => {
@@ -87,13 +94,23 @@ export const transformFiltersToServer = (data: any) => {
8794
const filters = transformDataToServer(data);
8895
const filtersData: Record<string, string | string[]> = {};
8996
for (const [k, v] of Object.entries(filters)) {
90-
if (isArray(v) && v.length === 2 && v.every(isDateTime)) {
97+
if (
98+
isArray(v) &&
99+
v.length === 2 &&
100+
v.every((item: any) => isDateTime(item) || isDate(item))
101+
) {
91102
filtersData[`${k}__gte`] = v[0];
92103
filtersData[`${k}__lte`] = v[1];
93104
} else if (isArray(v)) {
94105
// Serialize __in as comma-separated so one query param works (status__in=active,inactive)
95106
filtersData[`${k}__in`] = v.join(",");
96-
} else if (isDateTime(v) || isTime(v) || isNumeric(v) || isBoolean(v)) {
107+
} else if (
108+
isDateTime(v) ||
109+
isDate(v) ||
110+
isTime(v) ||
111+
isNumeric(v) ||
112+
isBoolean(v)
113+
) {
97114
filtersData[k] = v as string;
98115
} else {
99116
filtersData[`${k}__icontains`] = String(v);
@@ -112,12 +129,15 @@ export const transformValueFromServer = (value: any): any => {
112129
if (isBoolean(value)) {
113130
return value !== "false" && !!value;
114131
}
115-
if (isDateTime(value)) {
132+
if (isDate(value)) {
116133
return dayjs(value);
117134
}
118135
if (isTime(value)) {
119136
return dayjs(`1970-01-01T${value}`);
120137
}
138+
if (isDateTime(value)) {
139+
return dayjs(value);
140+
}
121141
return value;
122142
};
123143

@@ -163,11 +183,16 @@ export const transformColumnValueFromServer = (
163183
if (isBoolean(value)) {
164184
return <Checkbox checked={value} />;
165185
}
166-
if (isDateTime(value)) {
167-
return dayjs(value).format(dateTimeFormat);
186+
if (isDate(value)) {
187+
// remove time from dateTimeFormat ss optional
188+
const dateFormat = dateTimeFormat?.replace(/ HH:mm(:ss)?(?:.SSS)?/, "");
189+
return dayjs(value).format(dateFormat);
168190
}
169191
if (isTime(value)) {
170192
return value;
171193
}
194+
if (isDateTime(value)) {
195+
return dayjs(value).format(dateTimeFormat);
196+
}
172197
return value;
173198
};

frontend/src/helpers/widgets.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export const getWidgetCls = (
4444
case EFieldWidgetType.PasswordInput:
4545
return [PasswordInput, { parentId: id }];
4646
case EFieldWidgetType.TextArea:
47-
return [Input.TextArea, {}];
47+
return [Input.TextArea, { autoSize: { minRows: 4, maxRows: 16 } }];
4848
case EFieldWidgetType.RichTextArea:
4949
return [TextEditor, {}];
5050
case EFieldWidgetType.JsonTextArea:

0 commit comments

Comments
 (0)