From b40e5883791166eff96a8eb5bdb57f70dc81a381 Mon Sep 17 00:00:00 2001 From: Alexander Kalinovsky Date: Wed, 22 Jan 2025 20:04:32 +0100 Subject: [PATCH] fix: default values in subordinate entities based on static_filtering, add: button visibility callbacks in entities' forms --- bot/handlers/editors/date.py | 3 + bot/handlers/editors/main_callbacks.py | 30 ++++++ bot/handlers/forms/entity_form.py | 130 ++++++++++++++----------- bot/handlers/menu/entities.py | 29 +++--- model/bot_entity.py | 2 +- model/descriptors.py | 6 +- utils/serialization.py | 6 +- 7 files changed, 129 insertions(+), 77 deletions(-) diff --git a/bot/handlers/editors/date.py b/bot/handlers/editors/date.py index 375490d..577382e 100644 --- a/bot/handlers/editors/date.py +++ b/bot/handlers/editors/date.py @@ -54,6 +54,9 @@ async def time_picker( ): keyboard_builder = InlineKeyboardBuilder() + if not current_value: + current_value = time(0, 0) + for i in range(12): keyboard_builder.row( InlineKeyboardButton( diff --git a/bot/handlers/editors/main_callbacks.py b/bot/handlers/editors/main_callbacks.py index 1b18c79..7d9692f 100644 --- a/bot/handlers/editors/main_callbacks.py +++ b/bot/handlers/editors/main_callbacks.py @@ -167,6 +167,36 @@ async def process_field_edit_callback(message: Message | CallbackQuery, **kwargs 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 ( callback_data.context in [CommandContext.ENTITY_CREATE, CommandContext.ENTITY_EDIT] diff --git a/bot/handlers/forms/entity_form.py b/bot/handlers/forms/entity_form.py index 3916a8b..928849c 100644 --- a/bot/handlers/forms/entity_form.py +++ b/bot/handlers/forms/entity_form.py @@ -84,78 +84,94 @@ async def entity_item( callback_data.form_params or "default", entity_descriptor.default_form ) - if can_edit: - for edit_buttons_row in form.form_buttons: - btn_row = [] - for button in edit_buttons_row: - if isinstance(button, FieldEditButton): - field_name = button.field_name - btn_caption = button.caption - if field_name in entity_descriptor.fields_descriptors: - field_descriptor = entity_descriptor.fields_descriptors[ - field_name - ] - 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 - ) + for edit_buttons_row in form.form_buttons: + btn_row = [] + for button in edit_buttons_row: + + if button.visibility and not button.visibility(entity_item): + continue + + if isinstance(button, FieldEditButton) and can_edit: + + field_name = button.field_name + btn_caption = button.caption + if field_name in entity_descriptor.fields_descriptors: + field_descriptor = entity_descriptor.fields_descriptors[ + field_name + ] + 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: - if field_descriptor.type_base is bool: - btn_text = f"{'【✔︎】 ' if field_value else '【 】 '}{ + btn_text = ( + f"✏️ { get_callable_str( field_descriptor.caption, field_descriptor, entity_item, field_value, ) - if field_descriptor.caption - else field_name }" - else: - btn_text = ( - 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(), + if field_descriptor.caption + else f"✏️ {field_name}" ) - ) - - 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( InlineKeyboardButton( text=btn_text, - callback_data=( - button.context_data.pack() - if button.context_data - else ContextData( - command=CallbackCommand.USER_COMMAND, - user_command=button.command, - data=str(entity_item.id), - ).pack() - ), + 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(), ) ) - if btn_row: - keyboard_builder.row(*btn_row) + 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 + + 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 = [] if can_edit and form.show_edit_button: diff --git a/bot/handlers/menu/entities.py b/bot/handlers/menu/entities.py index 7f9939a..88af5ca 100644 --- a/bot/handlers/menu/entities.py +++ b/bot/handlers/menu/entities.py @@ -45,21 +45,24 @@ async def entities_menu( entity_metadata = app.entity_metadata 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( - InlineKeyboardButton( - text=caption, - callback_data=ContextData( - command=CallbackCommand.ENTITY_LIST, entity_name=entity.name - ).pack(), + if entity.show_in_entities_menu: + + 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( + InlineKeyboardButton( + text=caption, + callback_data=ContextData( + command=CallbackCommand.ENTITY_LIST, entity_name=entity.name + ).pack(), + ) ) - ) context = pop_navigation_context(navigation_stack) if context: diff --git a/model/bot_entity.py b/model/bot_entity.py index c11feb7..664334c 100644 --- a/model/bot_entity.py +++ b/model/bot_entity.py @@ -258,7 +258,7 @@ class BotEntity[CreateSchemaType: BaseModel, UpdateSchemaType: BaseModel]( condition = column(sfilt.field_name).isnot(None) else: condition = None - if condition: + if condition is not None: select_statement = select_statement.where(condition) return select_statement diff --git a/model/descriptors.py b/model/descriptors.py index 7e94e90..7e6d3b8 100644 --- a/model/descriptors.py +++ b/model/descriptors.py @@ -24,6 +24,7 @@ EntityFieldCaptionCallable = Callable[["EntityFieldDescriptor", Any, Any], str] @dataclass class FieldEditButton: field_name: str + visibility: Callable[[Any], bool] | None = None caption: str | LazyProxy | EntityFieldCaptionCallable | None = None @@ -31,7 +32,8 @@ class FieldEditButton: class CommandButton: command: str 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 @@ -64,7 +66,7 @@ class EntityList: item_form: str | None = None pagination: bool = True static_filters: list[Filter] | Any = None - filtering: bool = True + filtering: bool = False filtering_fields: list[str] = None order_by: str | Any | None = None diff --git a/utils/serialization.py b/utils/serialization.py index ce1d6d2..034b6ea 100644 --- a/utils/serialization.py +++ b/utils/serialization.py @@ -57,12 +57,10 @@ async def deserialize[T](session: AsyncSession, type_: type[T], value: str = Non elif type_ is datetime: if is_optional and not value: return None - if value[-3] == ":": - return datetime.strptime(value, "%Y-%m-%d %H:%M:%S") - elif value[-3] == "-": + if value[-3] == "-": return datetime.strptime(value, "%Y-%m-%d %H-%M") else: - raise ValueError("Invalid datetime format") + return datetime.fromisoformat(value) elif type_ is bool: return value == "True" elif type_ is Decimal: