44import operator
55import sys
66from collections .abc import Callable , Coroutine , Iterator , Sequence
7- from types import MappingProxyType
7+ from types import MappingProxyType as MPT
88from typing import Any , Literal , Optional , TypeVar , Union
99
1010import sqlalchemy as sa
3030
3131logger = logging .getLogger (__name__ )
3232
33- FIELD_TYPES = MappingProxyType ({
34- sa .Integer : ("NumberField" , "NumberInput" ),
35- sa .Text : ("TextField" , "TextInput" ),
36- sa .Float : ("NumberField" , "NumberInput" ),
37- sa .Date : ("DateField" , "DateInput" ),
38- sa .DateTime : ("DateField" , "DateTimeInput" ),
39- sa .Boolean : ("BooleanField" , "BooleanInput" ),
40- sa .String : ("TextField" , "TextInput" )
33+ FIELD_TYPES : MPT [type [sa .types .TypeEngine [Any ]], tuple [str , str , MPT [str , bool ]]] = MPT ({
34+ sa .Boolean : ("BooleanField" , "BooleanInput" , MPT ({})),
35+ sa .Date : ("DateField" , "DateInput" , MPT ({"showDate" : True , "showTime" : False })),
36+ sa .DateTime : ("DateField" , "DateTimeInput" , MPT ({"showDate" : True , "showTime" : True })),
37+ sa .Enum : ("SelectField" , "SelectInput" , MPT ({})),
38+ sa .Integer : ("NumberField" , "NumberInput" , MPT ({})),
39+ sa .Numeric : ("NumberField" , "NumberInput" , MPT ({})),
40+ sa .String : ("TextField" , "TextInput" , MPT ({})),
41+ sa .Time : ("DateField" , "TimeInput" , MPT ({"showDate" : False , "showTime" : True })),
42+ sa .Uuid : ("TextField" , "TextInput" , MPT ({})), # TODO: validators
43+ # TODO: Set fields for below types.
44+ # sa.sql.sqltypes._AbstractInterval: (),
45+ # sa.types._Binary: (),
46+ # sa.types.PickleType: (),
47+
48+ # sa.ARRAY: (),
49+ # sa.JSON: (),
50+
51+ # sa.dialects.postgresql.AbstractRange: (),
52+ # sa.dialects.postgresql.BIT: (),
53+ # sa.dialects.postgresql.CIDR: (),
54+ # sa.dialects.postgresql.HSTORE: (),
55+ # sa.dialects.postgresql.INET: (),
56+ # sa.dialects.postgresql.MACADDR: (),
57+ # sa.dialects.postgresql.MACADDR8: (),
58+ # sa.dialects.postgresql.MONEY: (),
59+ # sa.dialects.postgresql.OID: (),
60+ # sa.dialects.postgresql.REGCONFIG: (),
61+ # sa.dialects.postgresql.REGCLASS: (),
62+ # sa.dialects.postgresql.TSQUERY: (),
63+ # sa.dialects.postgresql.TSVECTOR: (),
64+ # sa.dialects.mysql.BIT: (),
65+ # sa.dialects.mysql.YEAR: (),
66+ # sa.dialects.oracle.ROWID: (),
67+ # sa.dialects.mssql.MONEY: (),
68+ # sa.dialects.mssql.SMALLMONEY: (),
69+ # sa.dialects.mssql.SQL_VARIANT: (),
4170})
4271
4372
73+ def get_components (t : sa .types .TypeEngine [object ]) -> tuple [str , str , dict [str , bool ]]:
74+ for key , (field , inp , props ) in FIELD_TYPES .items ():
75+ if isinstance (t , key ):
76+ return (field , inp , props .copy ())
77+
78+ return ("TextField" , "TextInput" , {})
79+
80+
4481def handle_errors (
4582 f : Callable [_P , Coroutine [None , None , _T ]]
4683) -> Callable [_P , Coroutine [None , None , _T ]]:
@@ -129,16 +166,20 @@ def __init__(self, db: AsyncEngine, model_or_table: Union[sa.Table, type[Declara
129166 self .name = table .name
130167 self .fields = {}
131168 self .inputs = {}
169+ self .omit_fields = set ()
132170 for c in table .c .values ():
133171 if c .foreign_keys :
134172 field = "ReferenceField"
135173 inp = "ReferenceInput"
136174 key = next (iter (c .foreign_keys )) # TODO: Test composite foreign keys.
137- props : dict [str , object ] = {"reference" : key .column .table .name ,
138- "source" : c .name , "target" : key .column .name }
175+ props : dict [str , Any ] = {"reference" : key .column .table .name ,
176+ "source" : c .name , "target" : key .column .name }
139177 else :
140- field , inp = FIELD_TYPES .get (type (c .type ), ("TextField" , "TextInput" ))
141- props = {}
178+ field , inp , props = get_components (c .type )
179+
180+ if isinstance (c .type , sa .Enum ):
181+ props ["choices" ] = tuple ({"id" : e .value , "name" : e .name }
182+ for e in c .type .python_type )
142183
143184 if isinstance (c .default , sa .ColumnDefault ):
144185 props ["placeholder" ] = c .default .arg
@@ -161,24 +202,29 @@ def __init__(self, db: AsyncEngine, model_or_table: Union[sa.Table, type[Declara
161202 local , remote = relationship .local_remote_pairs [0 ]
162203
163204 props = {"reference" : relationship .entity .persist_selectable .name ,
164- "label" : name .title (), "source" : local .name , "target" : remote .name }
205+ "label" : name .title (), "source" : local .name ,
206+ "target" : remote .name , "sortable" : False }
165207 if local .foreign_keys :
166208 t = "ReferenceField"
209+ props ["link" ] = "show"
167210 elif relationship .uselist :
168211 t = "ReferenceManyField"
169212 else :
170213 t = "ReferenceOneField"
214+ props ["link" ] = "show"
171215
172216 children = {}
173217 for c in relationship .target .c .values ():
174218 if c is remote : # Skip the foreign key
175219 continue
176- field , inp = FIELD_TYPES . get ( type ( c .type ), ( "TextField" , "TextInput" ) )
177- children [c .name ] = {"type" : field , "props" : {} }
220+ field , inp , c_props = get_components ( c .type )
221+ children [c .name ] = {"type" : field , "props" : c_props }
178222 container = "Datagrid" if t == "ReferenceManyField" else "DatagridSingle"
179- props ["children" ] = {"_" : {"type" : container , "props" : {"children" : children }}}
223+ props ["children" ] = {"_" : {"type" : container , "props" : {
224+ "children" : children , "rowClick" : "show" }}}
180225
181226 self .fields [name ] = {"type" : t , "props" : props }
227+ self .omit_fields .add (name )
182228
183229 self ._db = db
184230 self ._table = table
0 commit comments