New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement TableConfig class #73
Comments
@dantownsend I'm experimenting with this and this feature could be useful. Something like this can change columns we want to display in admin and that's fine for listing, @dataclass
class TableConfig:
"""
:param list_columns:
Used to specify which columns are shown on the list page. If None,
all are shown.
:param list_filters:
Used to specify which filters are shown on the list page. If None,
all are shown.
"""
table: t.Type[Table]
list_columns: t.Optional[t.List[Column]] = None
# list_filters: t.Optional[t.List[Column]] = None
def __post_init__(self):
"""
Display the columns in the admin UI and make shure
that primary key is always included for crud operations
and FK column to avoid OperationalError
"""
if self.list_columns is None:
self.table._meta.columns = self.table._meta.columns
else:
self.table._meta.columns = [
column
for column in itertools.chain(
self.table._meta.default_columns,
self.list_columns,
self.table._meta.foreign_key_columns,
)
if column in self.table._meta.columns
] but it's not good for adding end editing records because the changed table (from |
@sinisaos Yeah, it's a tricky one. Rather than excluding the columns from the Piccolo table, it might be better to use an unmodified Piccolo table. Then add an extra field to the {
"title": "DirectorIn",
"type": "object",
"properties": {},
"help_text": "The main director for a movie.",
"visible_columns": ["id", "name"]
} The UI will then work as before - we just need a check on the listing page if the column is in For that to work we'll have to modify create_pydantic_model(extra={'visible_columns': ['id', 'name']}) I thought about having an entirely new endpoint like |
@dantownsend Great. Thanks. I'll try that approach. |
@sinisaos The only downside with this approach is we're over fetching from the API. Imagine the table has 20 columns, and we just want to display 2 of them, then we're still fetching all 20. I don't think it's a big deal initially. But down the road we could modify
|
@dantownsend You're right, but anyhow we need to fetch all the columns to be able to edit and add new records. I will play with this, and if I need help or advice, I will write here. |
@dantownsend Trying your approach, I got stuck. So far I have added visible columns to def create_pydantic_model(
...
deserialize_json: bool = False,
visible_columns: t.Tuple[str] = (),
) -> t.Type[pydantic.BaseModel]
class CustomConfig(Config):
schema_extra = {
"help_text": table._meta.help_text,
"visible_columns": visible_columns, # we also can pass tuple(i._meta.name for i in table._meta.columns) to get all columns visible by default
} I also added @dataclass
class TableConfig:
"""
:param list_columns:
Used to specify which columns are shown on the list page. If None,
all are shown.
"""
table: t.Type[Table]
list_columns: t.List[Column] = field(default_factory=list)
def __post_init__(self):
"""
Display the columns in the admin UI and make shure
that primary key and FK column is always included
"""
self.list_columns = [
column
for column in itertools.chain(
self.table._meta.default_columns,
self.list_columns,
self.table._meta.foreign_key_columns,
)
]
return self.list_columns I can change the # endpoints py
piccolo_crud: PiccoloCRUD = PiccoloCRUD
for table in tables:
if isinstance(table, TableConfig):
piccolo_crud.pydantic_model = create_pydantic_model(
table.table,
model_name=f"{table.table.__name__}In",
visible_columns=tuple(
i._meta.name for i in table.list_columns
),
)
print(piccolo_crud.pydantic_model.schema_json(indent=2))
else:
piccolo_crud.pydantic_model = create_pydantic_model(
table,
model_name=f"{table.__name__}In",
visible_columns=tuple(
i._meta.name for i in table._meta.columns
),
)
print(piccolo_crud.pydantic_model.schema_json(indent=2)) I also changed the cellNames() {
const keys = []
for (const key in this.rows[0]) {
if (!key.endsWith("_readable")) {
keys.push(key)
}
}
const columnsDifference = keys.filter((column) =>
this.schema.visible_columns.includes(column)
)
return columnsDifference
}, Sorry for long comment. |
@dantownsend Ideally change for @property
def pydantic_model(self) -> t.Type[pydantic.BaseModel]:
"""
Useful for serialising inbound data from POST and PUT requests.
"""
if something:
visible_columns = visible_columns=tuple(
i._meta.name for i in table.list_columns
),
else:
visible_columns=tuple(
i._meta.name for i in table._meta.columns
),
return create_pydantic_model(
self.table, model_name=f"{self.table.__name__}In",
visible_columns=visible_columns,
) Another approach could be to add class Director(Table, help_text="The main director for a movie."):
name = Varchar(length=300, null=False)
gender = Varchar(length=1, choices=Gender)
@classmethod
def get_readable(cls):
return Readable(template="%s", columns=[cls.name])
@classmethod
def set_visible(cls):
return [cls.name] # some like this and then we can pass it to pydantic.py visible columns Then we dont need TableConfig |
@sinisaos I like the idea of the When # in AdminRouter.__init__
self.tables = [table if isinstance(TableConfig) else TableConfig(table=table) for table in tables) |
You should be able to simplify @dataclass
class TableConfig:
"""
:param list_columns:
Used to specify which columns are shown on the list page. If None,
all are shown.
"""
table: t.Type[Table]
list_columns: t.List[Column] = field(default_factory=list)
def __post_init__(self):
"""
Display the columns in the admin UI and make shure
that primary key and FK column is always included
"""
if not self.list_columns:
self.list_columns = self.table._meta.columns That should simplify things. |
@dantownsend Thanks. I'll try that during the day. |
@dantownsend In order for your example to work I had to change def create_pydantic_model(
...
deserialize_json: bool = False,
visible_columns: t.Tuple[str] = (),
) -> t.Type[pydantic.BaseModel]
class CustomConfig(Config):
schema_extra = {
"help_text": table._meta.help_text,
"visible_columns": tuple(
i._meta.name for i in table._meta.columns
),
} to pass all columns to # endpoints py
piccolo_crud: PiccoloCRUD = PiccoloCRUD
for table in tables:
if isinstance(table, TableConfig):
piccolo_crud.pydantic_model = create_pydantic_model(
table.table,
model_name=f"{table.table.__name__}In",
visible_columns=tuple(
i._meta.name for i in table.list_columns
),
)
# print(piccolo_crud.pydantic_model.schema_json(indent=2))
else:
piccolo_crud.pydantic_model = create_pydantic_model(
table,
model_name=f"{table.__name__}In",
visible_columns=tuple(
i._meta.name for i in table._meta.columns
),
)
# print(piccolo_crud.pydantic_model.schema_json(indent=2)) but it's a loop and only the last result from the loop is returned, and all the tables change to the last result schema, which is not good. |
@sinisaos I think this might be the issue: def create_pydantic_model(
...
deserialize_json: bool = False,
visible_columns: t.Tuple[str] = (),
) -> t.Type[pydantic.BaseModel]
class CustomConfig(Config):
schema_extra = {
"help_text": table._meta.help_text,
# You should have to do this - just use the visible_columns value passed into the function.
"visible_columns": tuple(
i._meta.name for i in table._meta.columns
),
} It can just be: def create_pydantic_model(
...
deserialize_json: bool = False,
visible_columns: t.Tuple[str] = (),
) -> t.Type[pydantic.BaseModel]
class CustomConfig(Config):
schema_extra = {
"help_text": table._meta.help_text,
"visible_columns": visible_columns
} Does that help? |
@dantownsend Unfortunately no. Without this class CustomConfig(Config):
schema_extra = {
"help_text": table._meta.help_text,
# You should have to do this - just use the visible_columns value passed into the function.
"visible_columns": tuple(
i._meta.name for i in table._meta.columns
),
} is empty. We need to do that or add visible columns in here. # in PiccoloCRUD class in piccolo_api.endpoints.crud
@property
def pydantic_model(self) -> t.Type[pydantic.BaseModel]:
"""
Useful for serialising inbound data from POST and PUT requests.
"""
return create_pydantic_model(
self.table,
model_name=f"{self.table.__name__}In",
visible_columns=tuple(
i._meta.name for i in self.table._meta.columns
),
) I think if we manage somehow (I don't know how) to pass TableConfig @property
def pydantic_model(self) -> t.Type[pydantic.BaseModel]:
"""
Useful for serialising inbound data from POST and PUT requests.
"""
if (some condition to get TableConfig visible_columns) :
visible_columns = visible_columns=tuple(
i._meta.name for i in table.list_columns
),
else:
visible_columns=tuple(
i._meta.name for i in table._meta.columns
),
return create_pydantic_model(
self.table, model_name=f"{self.table.__name__}In",
visible_columns=visible_columns,
) |
@sinisaos I'll have to look at it in detail - it sounds trickier than I originally thought. |
@dantownsend I know you didn't imagine it that way, but if there's no easy workaround for @dataclass
class TableMeta:
...
tags: t.List[str] = field(default_factory=list)
help_text: t.Optional[str] = None
visible_columns: t.Optional[t.List[str]] = None
...
class Table(metaclass=TableMetaclass):
_meta = TableMeta()
def __init_subclass__(
...
tags: t.List[str] = [],
help_text: t.Optional[str] = None,
visible_columns: t.Optional[t.List[str]] = None,
):
...
cls._meta = TableMeta(
...
tags=tags,
help_text=help_text,
visible_columns=visible_columns,
_db=db,
) then in ...
visible_columns = tuple(
[
[i._meta.name for i in table._meta.columns]
if table._meta.visible_columns is None
else table._meta.visible_columns
][0]
)
class CustomConfig(Config):
schema_extra = {
"help_text": table._meta.help_text,
"visible_columns": visible_columns,
}
... and then in Piccolo Admin cellNames() {
const keys = []
for (const key in this.rows[0]) {
if (!key.endsWith("_readable")) {
keys.push(key)
}
}
const columnsDifference = keys.filter((column) =>
this.schema.visible_columns.includes(column)
)
return columnsDifference
}, To use that we just add visible columns in Table construction and everything works great. class Director(
Table,
help_text="The main director for a movie.",
visible_columns=["id", "name"],
): |
@sinisaos That's a nice idea. My main reason I didn't want to add extra attributes to the Table class, is it increases the likelihood of a clash with a column name. Adding them to the Table Metaclass avoids this. However, it could be problematic because lets say you do this: class MyTable(Table, visible_columns=[MyTable.id, MyTable.name]):
... Would that cause an error? Because MyTable isn't fully defined yet. |
@dantownsend Thanks. This is just an idea if we fail to solve the problem with
If I correctly understand, I think we are safe here because it is just a string and if we enter the column name to
I have to try it and see if there is a error. |
This doesn't work because NameError is raised. We need to stick to a list of strings. Please can you leave a comment here if you can't solve the |
@dantownsend Just for reminder. Have you had success in implementing |
@sinisaos I had a look. I have a potential solution - it effects the Piccolo, Piccolo API, and Piccolo Admin repos ... I'll push them up so you can take a look. |
@sinisaos These are the relevant PRs: piccolo-orm/piccolo#313 What do you think of them as a solution? |
@dantownsend It looks great, but first I want to give it a try and then report how it works. |
@sinisaos Thanks, if you could give them a go that would be great. |
This is mostly implemented by #102 However, |
@dantownsend I will add possibility to change list of columns in filter. It's basically the same as visible columns with different modification on frontend. If I'm not able to do PR today, I will do it no later than tomorrow. |
@sinisaos You're right - the implementation should be very similar. Thanks a lot 👍 |
Currently,
create_admin
accepts a list ofTable
subclasses. This can be modified to accept eitherTable
subclasses, or instances of a new class calledTableConfig
.TableConfig
will be a dataclass which accepts a table argument, as well as a bunch of configuration options. For example:It will allow more fine grained control of the how the admin looks.
The text was updated successfully, but these errors were encountered: