fix: default values in subordinate entities based on static_filtering, add: button visibility callbacks in entities' forms

This commit is contained in:
Alexander Kalinovsky
2025-01-22 20:04:32 +01:00
parent 9dd0708a5b
commit b40e588379
7 changed files with 129 additions and 77 deletions

View File

@@ -54,6 +54,9 @@ async def time_picker(
): ):
keyboard_builder = InlineKeyboardBuilder() keyboard_builder = InlineKeyboardBuilder()
if not current_value:
current_value = time(0, 0)
for i in range(12): for i in range(12):
keyboard_builder.row( keyboard_builder.row(
InlineKeyboardButton( InlineKeyboardButton(

View File

@@ -167,6 +167,36 @@ async def process_field_edit_callback(message: Message | CallbackQuery, **kwargs
entity_data = state_data.get("entity_data", {}) entity_data = state_data.get("entity_data", {})
if callback_data.context == CommandContext.ENTITY_CREATE:
stack = state_data.get("navigation_stack", [])
prev_callback_data = ContextData.unpack(stack[-1]) if stack else None
if (
prev_callback_data
and prev_callback_data.command == CallbackCommand.ENTITY_LIST
and prev_callback_data.entity_name == entity_descriptor.name
):
prev_form_name = (
prev_callback_data.form_params.split("&")[0]
if prev_callback_data.form_params
else "default"
)
prev_form_params = (
prev_callback_data.form_params.split("&")[1:]
if prev_callback_data.form_params
else []
)
prev_form_list = entity_descriptor.lists.get(
prev_form_name or "default", entity_descriptor.default_list
)
for filt in prev_form_list.static_filters:
if filt.value_type == "const":
entity_data[filt.field_name] = filt.value
elif len(prev_form_params) > filt.param_index:
entity_data[filt.field_name] = prev_form_params[
filt.param_index
]
if ( if (
callback_data.context callback_data.context
in [CommandContext.ENTITY_CREATE, CommandContext.ENTITY_EDIT] in [CommandContext.ENTITY_CREATE, CommandContext.ENTITY_EDIT]

View File

@@ -84,78 +84,94 @@ async def entity_item(
callback_data.form_params or "default", entity_descriptor.default_form callback_data.form_params or "default", entity_descriptor.default_form
) )
if can_edit: for edit_buttons_row in form.form_buttons:
for edit_buttons_row in form.form_buttons: btn_row = []
btn_row = [] for button in edit_buttons_row:
for button in edit_buttons_row:
if isinstance(button, FieldEditButton): if button.visibility and not button.visibility(entity_item):
field_name = button.field_name continue
btn_caption = button.caption
if field_name in entity_descriptor.fields_descriptors: if isinstance(button, FieldEditButton) and can_edit:
field_descriptor = entity_descriptor.fields_descriptors[
field_name field_name = button.field_name
] btn_caption = button.caption
field_value = getattr(entity_item, field_descriptor.field_name) if field_name in entity_descriptor.fields_descriptors:
if btn_caption: field_descriptor = entity_descriptor.fields_descriptors[
btn_text = get_callable_str( field_name
btn_caption, field_descriptor, entity_item, field_value ]
) field_value = getattr(entity_item, field_descriptor.field_name)
if btn_caption:
btn_text = get_callable_str(
btn_caption, field_descriptor, entity_item, field_value
)
else:
if field_descriptor.type_base is bool:
btn_text = f"{'【✔︎】 ' if field_value else '【 】 '}{
get_callable_str(
field_descriptor.caption,
field_descriptor,
entity_item,
field_value,
)
if field_descriptor.caption
else field_name
}"
else: else:
if field_descriptor.type_base is bool: btn_text = (
btn_text = f"{'【✔︎】 ' if field_value else '【 】 '}{ f"✏️ {
get_callable_str( get_callable_str(
field_descriptor.caption, field_descriptor.caption,
field_descriptor, field_descriptor,
entity_item, entity_item,
field_value, field_value,
) )
if field_descriptor.caption
else field_name
}" }"
else: if field_descriptor.caption
btn_text = ( else f"✏️ {field_name}"
f"✏️ {get_callable_str(field_descriptor.caption, field_descriptor, entity_item, field_value)}"
if field_descriptor.caption
else f"✏️ {field_name}"
)
btn_row.append(
InlineKeyboardButton(
text=btn_text,
callback_data=ContextData(
command=CallbackCommand.FIELD_EDITOR,
context=CommandContext.ENTITY_FIELD_EDIT,
entity_name=entity_descriptor.name,
entity_id=str(entity_item.id),
field_name=field_name,
).pack(),
) )
)
elif isinstance(button, CommandButton):
btn_caption = button.caption
if btn_caption:
btn_text = get_callable_str(
btn_caption, entity_descriptor, entity_item
)
else:
btn_text = button.command
btn_row.append( btn_row.append(
InlineKeyboardButton( InlineKeyboardButton(
text=btn_text, text=btn_text,
callback_data=( callback_data=ContextData(
button.context_data.pack() command=CallbackCommand.FIELD_EDITOR,
if button.context_data context=CommandContext.ENTITY_FIELD_EDIT,
else ContextData( entity_name=entity_descriptor.name,
command=CallbackCommand.USER_COMMAND, entity_id=str(entity_item.id),
user_command=button.command, field_name=field_name,
data=str(entity_item.id), ).pack(),
).pack()
),
) )
) )
if btn_row: elif isinstance(button, CommandButton):
keyboard_builder.row(*btn_row)
btn_caption = button.caption
if btn_caption:
btn_text = get_callable_str(
btn_caption, entity_descriptor, entity_item
)
else:
btn_text = button.command
if isinstance(button.context_data, ContextData):
btn_cdata = button.context_data
elif callable(button.context_data):
btn_cdata = button.context_data(callback_data, entity_item)
else:
btn_cdata = ContextData(
command=CallbackCommand.USER_COMMAND,
user_command=button.command,
data=str(entity_item.id),
)
btn_row.append(
InlineKeyboardButton(
text=btn_text,
callback_data=btn_cdata.pack(),
)
)
if btn_row:
keyboard_builder.row(*btn_row)
edit_delete_row = [] edit_delete_row = []
if can_edit and form.show_edit_button: if can_edit and form.show_edit_button:

View File

@@ -45,21 +45,24 @@ async def entities_menu(
entity_metadata = app.entity_metadata entity_metadata = app.entity_metadata
for entity in entity_metadata.entity_descriptors.values(): for entity in entity_metadata.entity_descriptors.values():
if entity.full_name_plural.__class__ == EntityCaptionCallable:
caption = entity.full_name_plural(entity) or entity.name
elif entity.full_name_plural.__class__ == LazyProxy:
caption = f"{f'{entity.icon} ' if entity.icon else ''}{entity.full_name_plural.value or entity.name}"
else:
caption = f"{f'{entity.icon} ' if entity.icon else ''}{entity.full_name_plural or entity.name}"
keyboard_builder.row( if entity.show_in_entities_menu:
InlineKeyboardButton(
text=caption, if entity.full_name_plural.__class__ == EntityCaptionCallable:
callback_data=ContextData( caption = entity.full_name_plural(entity) or entity.name
command=CallbackCommand.ENTITY_LIST, entity_name=entity.name elif entity.full_name_plural.__class__ == LazyProxy:
).pack(), caption = f"{f'{entity.icon} ' if entity.icon else ''}{entity.full_name_plural.value or entity.name}"
else:
caption = f"{f'{entity.icon} ' if entity.icon else ''}{entity.full_name_plural or entity.name}"
keyboard_builder.row(
InlineKeyboardButton(
text=caption,
callback_data=ContextData(
command=CallbackCommand.ENTITY_LIST, entity_name=entity.name
).pack(),
)
) )
)
context = pop_navigation_context(navigation_stack) context = pop_navigation_context(navigation_stack)
if context: if context:

View File

@@ -258,7 +258,7 @@ class BotEntity[CreateSchemaType: BaseModel, UpdateSchemaType: BaseModel](
condition = column(sfilt.field_name).isnot(None) condition = column(sfilt.field_name).isnot(None)
else: else:
condition = None condition = None
if condition: if condition is not None:
select_statement = select_statement.where(condition) select_statement = select_statement.where(condition)
return select_statement return select_statement

View File

@@ -24,6 +24,7 @@ EntityFieldCaptionCallable = Callable[["EntityFieldDescriptor", Any, Any], str]
@dataclass @dataclass
class FieldEditButton: class FieldEditButton:
field_name: str field_name: str
visibility: Callable[[Any], bool] | None = None
caption: str | LazyProxy | EntityFieldCaptionCallable | None = None caption: str | LazyProxy | EntityFieldCaptionCallable | None = None
@@ -31,7 +32,8 @@ class FieldEditButton:
class CommandButton: class CommandButton:
command: str command: str
caption: str | LazyProxy | EntityItemCaptionCallable | None = None caption: str | LazyProxy | EntityItemCaptionCallable | None = None
context_data: ContextData | None = None visibility: Callable[[Any], bool] | None = None
context_data: ContextData | Callable[[ContextData, Any], ContextData] | None = None
@dataclass @dataclass
@@ -64,7 +66,7 @@ class EntityList:
item_form: str | None = None item_form: str | None = None
pagination: bool = True pagination: bool = True
static_filters: list[Filter] | Any = None static_filters: list[Filter] | Any = None
filtering: bool = True filtering: bool = False
filtering_fields: list[str] = None filtering_fields: list[str] = None
order_by: str | Any | None = None order_by: str | Any | None = None

View File

@@ -57,12 +57,10 @@ async def deserialize[T](session: AsyncSession, type_: type[T], value: str = Non
elif type_ is datetime: elif type_ is datetime:
if is_optional and not value: if is_optional and not value:
return None return None
if value[-3] == ":": if value[-3] == "-":
return datetime.strptime(value, "%Y-%m-%d %H:%M:%S")
elif value[-3] == "-":
return datetime.strptime(value, "%Y-%m-%d %H-%M") return datetime.strptime(value, "%Y-%m-%d %H-%M")
else: else:
raise ValueError("Invalid datetime format") return datetime.fromisoformat(value)
elif type_ is bool: elif type_ is bool:
return value == "True" return value == "True"
elif type_ is Decimal: elif type_ is Decimal: