Skip to content

Commit

Permalink
Fix warnings (#833)
Browse files Browse the repository at this point in the history
  • Loading branch information
Dreamsorcerer authored Dec 24, 2023
1 parent 5120306 commit 11a85ce
Show file tree
Hide file tree
Showing 5 changed files with 52 additions and 45 deletions.
1 change: 1 addition & 0 deletions admin-js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"@testing-library/react": "^14.1.2",
"@testing-library/user-event": "^14.5.1",
"@ungap/structured-clone": "^1.2",
"jest-fail-on-console": "^3.1.2",
"react-scripts": "5.0.1"
},
"scripts": {
Expand Down
7 changes: 4 additions & 3 deletions admin-js/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ function evaluate(obj) {

let {children, ...props} = obj["props"];
props = Object.fromEntries(Object.entries(props).map(([k, v]) => [k, evaluate(v)]));
props["key"] = props["source"];
if (children)
return <C {...props}>{evaluate(children)}</C>;
return <C {...props} />;
Expand Down Expand Up @@ -205,7 +206,7 @@ function createFields(fields, name, permissions) {
const withRecordProps = Object.fromEntries(withRecordPropNames.map(
(k) => [k, evaluate(state["props"][k])]));
// Show icon if user doesn't have permission to view this field (based on filters).
components.push(<WithRecord source={field} {...withRecordProps} render={
components.push(<WithRecord source={field} key={c.key} {...withRecordProps} render={
(record) => hasPermission(`${name}.${field}.view`, permissions, record) ? c : <VisibilityOffIcon />
} />);
}
Expand All @@ -232,7 +233,7 @@ function createInputs(resource, name, perm_type, permissions) {
for (let v of fvalues)
choices.push({"id": v, "name": v});
components.push(
<SelectInput source={field} choices={choices} defaultValue={nullable < 0 && fvalues[0]}
<SelectInput source={field} key={field} choices={choices} defaultValue={nullable < 0 && fvalues[0]}
validate={nullable < 0 && required()} disabled={disabled} />);
} else {
if (perm_type === "view") {
Expand All @@ -242,7 +243,7 @@ function createInputs(resource, name, perm_type, permissions) {
const c = evaluate(state);
if (perm_type === "edit")
// Don't render if filters disallow editing this field.
components.push(<WithRecord source={field} render={
components.push(<WithRecord source={field} key={field} render={
(record) => hasPermission(`${name}.${field}.${perm_type}`, permissions, record) && c
} />);
else
Expand Down
18 changes: 9 additions & 9 deletions admin-js/tests/setupTests.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const http = require("http");
const {spawn} = require("child_process");
import "whatwg-fetch"; // https://github.com/jsdom/jsdom/issues/1724
import "@testing-library/jest-dom";
import failOnConsole from "jest-fail-on-console";
import {configure, render} from "@testing-library/react";
import * as structuredClone from "@ungap/structured-clone";

Expand All @@ -26,20 +27,19 @@ window.matchMedia = (query) => ({
// Ignore not implemented errors
window.scrollTo = jest.fn();

// Suppress act() warnings, because there's too many async changes happening.
const realError = console.error;
console.error = (...args) => {
if (typeof args[0] === "string" && args[0].includes("inside a test was not wrapped in act(...)."))
return;
realError(...args);
};
failOnConsole({
silenceMessage: (msg) => {
// Suppress act() warnings, because there's too many async changes happening.
return msg.includes("inside a test was not wrapped in act(...).");
}
});

beforeAll(async() => {
if (!global.pythonProcessPath)
return;

if (global.__coverage__)
pythonProcess = spawn("coverage", ["run", "--source=examples/", global.pythonProcessPath], {"cwd": ".."});
pythonProcess = spawn("coverage", ["run", "--source=examples/,aiohttp_admin/", global.pythonProcessPath], {"cwd": ".."});
else
pythonProcess = spawn("python3", ["-u", global.pythonProcessPath], {"cwd": ".."});

Expand All @@ -48,7 +48,7 @@ beforeAll(async() => {
await new Promise(resolve => setTimeout(resolve, 2500));

await new Promise(resolve => {
http.get("http://localhost:8080/admin", resp => {
http.get("http://localhost:8080/admin", {"timeout": 2000}, resp => {
if (resp.statusCode !== 200)
throw new Error("Request failed");

Expand Down
65 changes: 36 additions & 29 deletions aiohttp_admin/backends/sqlalchemy.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,18 @@

logger = logging.getLogger(__name__)

FIELD_TYPES: MPT[type[sa.types.TypeEngine[Any]], tuple[str, str, MPT[str, bool]]] = MPT({
sa.Boolean: ("BooleanField", "BooleanInput", MPT({})),
sa.Date: ("DateField", "DateInput", MPT({"showDate": True, "showTime": False})),
sa.DateTime: ("DateField", "DateTimeInput", MPT({"showDate": True, "showTime": True})),
sa.Enum: ("SelectField", "SelectInput", MPT({})),
sa.Integer: ("NumberField", "NumberInput", MPT({})),
sa.Numeric: ("NumberField", "NumberInput", MPT({})),
sa.String: ("TextField", "TextInput", MPT({})),
sa.Time: ("TimeField", "TimeInput", MPT({})),
sa.Uuid: ("TextField", "TextInput", MPT({})), # TODO: validators
_FieldTypesValues = tuple[str, str, MPT[str, bool], MPT[str, bool]]
FIELD_TYPES: MPT[type[sa.types.TypeEngine[Any]], _FieldTypesValues] = MPT({
sa.Boolean: ("BooleanField", "BooleanInput", MPT({}), MPT({})),
sa.Date: ("DateField", "DateInput", MPT({"showDate": True, "showTime": False}), MPT({})),
sa.DateTime: ("DateField", "DateTimeInput",
MPT({"showDate": True, "showTime": True}), MPT({})),
sa.Enum: ("SelectField", "SelectInput", MPT({}), MPT({})),
sa.Integer: ("NumberField", "NumberInput", MPT({}), MPT({})),
sa.Numeric: ("NumberField", "NumberInput", MPT({}), MPT({})),
sa.String: ("TextField", "TextInput", MPT({}), MPT({})),
sa.Time: ("TimeField", "TimeInput", MPT({}), MPT({})),
sa.Uuid: ("TextField", "TextInput", MPT({}), MPT({})), # TODO: validators
# TODO: Set fields for below types.
# sa.sql.sqltypes._AbstractInterval: (),
# sa.types._Binary: (),
Expand Down Expand Up @@ -70,12 +72,15 @@
})


def get_components(t: sa.types.TypeEngine[object]) -> tuple[str, str, dict[str, bool]]:
for key, (field, inp, props) in FIELD_TYPES.items():
_Components = tuple[str, str, dict[str, bool], dict[str, bool]]


def get_components(t: sa.types.TypeEngine[object]) -> _Components:
for key, (field, inp, field_props, input_props) in FIELD_TYPES.items():
if isinstance(t, key):
return (field, inp, props.copy())
return (field, inp, field_props.copy(), input_props.copy())

return ("TextField", "TextInput", {})
return ("TextField", "TextInput", {}, {})


def handle_errors(
Expand Down Expand Up @@ -176,15 +181,16 @@ def __init__(self, db: AsyncEngine, model_or_table: _ModelOrTable):
inp = "ReferenceInput"
key = next(iter(c.foreign_keys)) # TODO: Test composite foreign keys.
self._foreign_rows.add(c.name)
props: dict[str, Any] = {"reference": key.column.table.name,
"target": key.column.name}
field_props: dict[str, Any] = {"reference": key.column.table.name,
"target": key.column.name}
inp_props = field_props.copy()
else:
field, inp, props = get_components(c.type)
field, inp, field_props, inp_props = get_components(c.type)

if inp == "BooleanInput" and c.nullable:
inp = "NullableBooleanInput"

props["source"] = c.name
props: dict[str, Any] = {"source": c.name}
if isinstance(c.type, sa.Enum):
props["choices"] = tuple({"id": e.value, "name": e.name}
for e in c.type.python_type)
Expand All @@ -193,27 +199,28 @@ def __init__(self, db: AsyncEngine, model_or_table: _ModelOrTable):
if length is None or length > 31:
props["fullWidth"] = True
if length is None or length > 127:
props["multiline"] = True
inp_props["multiline"] = True

if isinstance(c.default, sa.ColumnDefault):
props["placeholder"] = c.default.arg

if c.comment:
props["helperText"] = c.comment

self.fields[c.name] = comp(field, props)
field_props.update(props)
self.fields[c.name] = comp(field, field_props)
if c.computed is None:
# TODO: Allow custom props (e.g. disabled, multiline, rows etc.)
props = props.copy()
inp_props.update(props)
show = c is not table.autoincrement_column
props["validate"] = self._get_validators(table, c)
inp_props["validate"] = self._get_validators(table, c)
if inp == "NumberInput":
for v in props["validate"]:
for v in inp_props["validate"]:
if v["name"] == "minValue":
props["min"] = v["args"][0]
inp_props["min"] = v["args"][0]
elif v["name"] == "maxValue":
props["max"] = v["args"][0]
self.inputs[c.name] = comp(inp, props) # type: ignore[assignment]
inp_props["max"] = v["args"][0]
self.inputs[c.name] = comp(inp, inp_props) # type: ignore[assignment]
self.inputs[c.name]["show_create"] = show
field_type: Any = c.type.python_type
if c.nullable:
Expand Down Expand Up @@ -251,9 +258,9 @@ def __init__(self, db: AsyncEngine, model_or_table: _ModelOrTable):
for kc in relationship.target.c.values():
if kc is remote: # Skip the foreign key
continue
field, inp, c_props = get_components(kc.type)
c_props["source"] = kc.name
children.append(comp(field, c_props))
field, _inp, c_fprops, _inp_props = get_components(kc.type)
c_fprops["source"] = kc.name
children.append(comp(field, c_fprops))
container = "Datagrid" if t == "ReferenceManyField" else "DatagridSingle"
datagrid = comp(container, {"children": children, "rowClick": "show"})
if t == "ReferenceManyField":
Expand Down
6 changes: 2 additions & 4 deletions tests/test_backends_sqlalchemy.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,7 @@ class TestModel(base): # type: ignore[misc,valid-type]
assert r.name == "dummy"
assert r.primary_key == "id"
assert r.fields == {"id": comp("NumberField", {"source": "id"}),
"num": comp("TextField", {"source": "num", "fullWidth": True,
"multiline": True})}
"num": comp("TextField", {"source": "num", "fullWidth": True})}
# Autoincremented PK should not be in create form
assert r.inputs == {
"id": comp("NumberInput", {"source": "id", "validate": [func("required", ())]})
Expand Down Expand Up @@ -86,8 +85,7 @@ class TestModel(base): # type: ignore[misc,valid-type]

r = SAResource(mock_engine, TestModel)
assert r.fields["num"]["props"] == {
"source": "num", "fullWidth": True, "multiline": True, "placeholder": "Bar",
"helperText": "Foo"}
"source": "num", "fullWidth": True, "placeholder": "Bar", "helperText": "Foo"}
assert r.inputs["num"]["props"] == {
"source": "num", "fullWidth": True, "multiline": True, "placeholder": "Bar",
"helperText": "Foo", "validate": [func("maxLength", (128,))]}
Expand Down

0 comments on commit 11a85ce

Please sign in to comment.