add options functionality for input editors
This commit is contained in:
@@ -119,6 +119,7 @@ async def show_editor(message: Message | CallbackQuery, **kwargs):
|
||||
)
|
||||
).format(name=caption_str)
|
||||
|
||||
kwargs["entity_data"] = entity_data
|
||||
kwargs["edit_prompt"] = edit_prompt
|
||||
|
||||
if value_type not in [int, float, Decimal, str]:
|
||||
|
||||
@@ -103,6 +103,8 @@ async def render_entity_picker(
|
||||
message=message,
|
||||
)
|
||||
|
||||
entity = None
|
||||
|
||||
if issubclass(type_, BotEnum):
|
||||
items_count = len(type_.all_members)
|
||||
total_pages = calc_total_pages(items_count, page_size)
|
||||
@@ -112,7 +114,6 @@ async def render_entity_picker(
|
||||
page_size * (page - 1) : page_size * page
|
||||
]
|
||||
elif callable(field_descriptor.options):
|
||||
entity = None
|
||||
if callback_data.entity_id:
|
||||
entity = await field_descriptor.entity_descriptor.type_.get(
|
||||
session=db_session, id=callback_data.entity_id
|
||||
@@ -126,13 +127,25 @@ async def render_entity_picker(
|
||||
enum_items = list(type_.all_members.values())[
|
||||
page_size * (page - 1) : page_size * page
|
||||
]
|
||||
items = [
|
||||
{
|
||||
"text": f"{'' if not is_list else '【✔︎】 ' if item in (current_value or []) else '【 】 '}{item.localized(user.lang)}",
|
||||
"value": item.value,
|
||||
}
|
||||
for item in enum_items
|
||||
]
|
||||
items = []
|
||||
for it_row in enum_items:
|
||||
if not isinstance(it_row, list):
|
||||
it_row = [it_row]
|
||||
item_row = []
|
||||
for item in it_row:
|
||||
if isinstance(item, tuple):
|
||||
item_value, item_text = item
|
||||
else:
|
||||
item_value = item
|
||||
item_text = item.localized(user.lang)
|
||||
item_row.append(
|
||||
{
|
||||
"text": f"{'' if not is_list else '[✓] ' if item_value in (current_value or []) else '[ ] '}{item_text}",
|
||||
"value": item_value.value,
|
||||
}
|
||||
)
|
||||
items.append(item_row)
|
||||
|
||||
elif issubclass(type_, BotEntity):
|
||||
ep_form = "default"
|
||||
ep_form_params = []
|
||||
@@ -224,55 +237,60 @@ async def render_entity_picker(
|
||||
entity_items = list[BotEntity]()
|
||||
|
||||
items = [
|
||||
{
|
||||
"text": f"{
|
||||
''
|
||||
if not is_list
|
||||
else '【✔︎】 '
|
||||
if item in (current_value or [])
|
||||
else '【 】 '
|
||||
}{
|
||||
await get_callable_str(
|
||||
callable_str=type_.bot_entity_descriptor.item_repr,
|
||||
context=context,
|
||||
entity=item,
|
||||
)
|
||||
if type_.bot_entity_descriptor.item_repr
|
||||
else await get_callable_str(
|
||||
callable_str=type_.bot_entity_descriptor.full_name,
|
||||
context=context,
|
||||
descriptor=type_.bot_entity_descriptor,
|
||||
)
|
||||
if type_.bot_entity_descriptor.full_name
|
||||
else f'{type_.bot_entity_descriptor.name}: {str(item.id)}'
|
||||
}",
|
||||
"value": str(item.id),
|
||||
}
|
||||
[
|
||||
{
|
||||
"text": f"{
|
||||
''
|
||||
if not is_list
|
||||
else '[✓] '
|
||||
if item in (current_value or [])
|
||||
else '[ ] '
|
||||
}{
|
||||
await get_callable_str(
|
||||
callable_str=type_.bot_entity_descriptor.item_repr,
|
||||
context=context,
|
||||
entity=item,
|
||||
)
|
||||
if type_.bot_entity_descriptor.item_repr
|
||||
else await get_callable_str(
|
||||
callable_str=type_.bot_entity_descriptor.full_name,
|
||||
context=context,
|
||||
descriptor=type_.bot_entity_descriptor,
|
||||
)
|
||||
if type_.bot_entity_descriptor.full_name
|
||||
else f'{type_.bot_entity_descriptor.name}: {str(item.id)}'
|
||||
}",
|
||||
"value": str(item.id),
|
||||
}
|
||||
]
|
||||
for item in entity_items
|
||||
]
|
||||
|
||||
keyboard_builder = InlineKeyboardBuilder()
|
||||
|
||||
for item in items:
|
||||
keyboard_builder.row(
|
||||
InlineKeyboardButton(
|
||||
text=item["text"],
|
||||
callback_data=ContextData(
|
||||
command=(
|
||||
CallbackCommand.ENTITY_PICKER_TOGGLE_ITEM
|
||||
if is_list
|
||||
else CallbackCommand.FIELD_EDITOR_CALLBACK
|
||||
),
|
||||
context=callback_data.context,
|
||||
entity_name=callback_data.entity_name,
|
||||
entity_id=callback_data.entity_id,
|
||||
field_name=callback_data.field_name,
|
||||
form_params=callback_data.form_params,
|
||||
user_command=callback_data.user_command,
|
||||
data=f"{page}&{item['value']}" if is_list else item["value"],
|
||||
).pack(),
|
||||
for item_row in items:
|
||||
btn_row = []
|
||||
for item in item_row:
|
||||
btn_row.append(
|
||||
InlineKeyboardButton(
|
||||
text=item["text"],
|
||||
callback_data=ContextData(
|
||||
command=(
|
||||
CallbackCommand.ENTITY_PICKER_TOGGLE_ITEM
|
||||
if is_list
|
||||
else CallbackCommand.FIELD_EDITOR_CALLBACK
|
||||
),
|
||||
context=callback_data.context,
|
||||
entity_name=callback_data.entity_name,
|
||||
entity_id=callback_data.entity_id,
|
||||
field_name=callback_data.field_name,
|
||||
form_params=callback_data.form_params,
|
||||
user_command=callback_data.user_command,
|
||||
data=f"{page}&{item['value']}" if is_list else item["value"],
|
||||
).pack(),
|
||||
)
|
||||
)
|
||||
)
|
||||
keyboard_builder.row(*btn_row)
|
||||
|
||||
if form_list and form_list.pagination:
|
||||
add_pagination_controls(
|
||||
@@ -321,6 +339,7 @@ async def render_entity_picker(
|
||||
state_data=state_data,
|
||||
user=user,
|
||||
context=context,
|
||||
entity=entity,
|
||||
)
|
||||
|
||||
await state.set_data(state_data)
|
||||
|
||||
@@ -261,7 +261,9 @@ async def field_editor(message: Message | CallbackQuery, **kwargs):
|
||||
async def delete_message_callback(query: CallbackQuery, **kwargs):
|
||||
state: FSMContext = kwargs["state"]
|
||||
state_data = await state.get_data()
|
||||
clear_state(state_data=state_data)
|
||||
context_data: ContextData = kwargs["callback_data"]
|
||||
clear_nav = context_data.data == "clear_nav"
|
||||
clear_state(state_data=state_data, clear_nav=clear_nav)
|
||||
await state.set_data(state_data)
|
||||
|
||||
await query.message.delete()
|
||||
|
||||
@@ -67,18 +67,23 @@ async def _validate_value(
|
||||
)
|
||||
async def field_editor_callback(message: Message | CallbackQuery, **kwargs):
|
||||
app: "QBotApp" = kwargs["app"]
|
||||
state: FSMContext = kwargs["state"]
|
||||
callback_data: ContextData = kwargs.get("callback_data", None)
|
||||
|
||||
state: FSMContext = kwargs["state"]
|
||||
state_data = await state.get_data()
|
||||
kwargs["state_data"] = state_data
|
||||
|
||||
if isinstance(message, Message):
|
||||
callback_data: ContextData = kwargs.get("callback_data", None)
|
||||
context_data = state_data.get("context_data")
|
||||
if context_data:
|
||||
callback_data = ContextData.unpack(context_data)
|
||||
value = message.text
|
||||
|
||||
field_descriptor = get_field_descriptor(app, callback_data)
|
||||
|
||||
if not field_descriptor.options_custom_value:
|
||||
return
|
||||
|
||||
value = message.text
|
||||
type_base = field_descriptor.type_base
|
||||
|
||||
if type_base in [int, float, Decimal]:
|
||||
@@ -132,21 +137,23 @@ async def field_editor_callback(message: Message | CallbackQuery, **kwargs):
|
||||
current_value = state_data.get("current_value")
|
||||
|
||||
state_data.update({"value": value})
|
||||
entity_descriptor = get_entity_descriptor(app, callback_data)
|
||||
# entity_descriptor = field_descriptor.entity_descriptor
|
||||
# entity_descriptor = get_entity_descriptor(app, callback_data)
|
||||
kwargs.update({"callback_data": callback_data})
|
||||
|
||||
return await show_editor(
|
||||
message=message,
|
||||
locale_index=locale_index + 1,
|
||||
field_descriptor=field_descriptor,
|
||||
entity_descriptor=entity_descriptor,
|
||||
# entity_descriptor=entity_descriptor,
|
||||
current_value=current_value,
|
||||
value=value,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
else:
|
||||
callback_data: ContextData = kwargs["callback_data"]
|
||||
field_descriptor = get_field_descriptor(app, callback_data)
|
||||
|
||||
if callback_data.data:
|
||||
if callback_data.data == "skip":
|
||||
value = None
|
||||
@@ -154,7 +161,6 @@ async def field_editor_callback(message: Message | CallbackQuery, **kwargs):
|
||||
value = callback_data.data
|
||||
else:
|
||||
value = state_data.get("value")
|
||||
field_descriptor = get_field_descriptor(app, callback_data)
|
||||
|
||||
kwargs.update(
|
||||
{
|
||||
|
||||
@@ -74,7 +74,11 @@ async def string_editor(
|
||||
|
||||
state_data.update({"context_data": context_data.pack()})
|
||||
|
||||
if _current_value:
|
||||
if (
|
||||
_current_value
|
||||
and field_descriptor.show_current_value_button
|
||||
and field_descriptor.options_custom_value
|
||||
):
|
||||
_current_value_caption = (
|
||||
f"{_current_value[:30]}..." if len(_current_value) > 30 else _current_value
|
||||
)
|
||||
@@ -102,6 +106,7 @@ async def string_editor(
|
||||
state_data=state_data,
|
||||
user=user,
|
||||
context=context,
|
||||
entity=kwargs.get("entity_data"),
|
||||
)
|
||||
|
||||
await state.set_data(state_data)
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
from aiogram.types import InlineKeyboardButton
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from inspect import iscoroutinefunction
|
||||
from typing import Any
|
||||
|
||||
from ....model.bot_entity import BotEntity
|
||||
from ....model.bot_enum import BotEnum
|
||||
from ....model.settings import Settings
|
||||
from ....model.descriptors import BotContext, EntityForm, FieldDescriptor
|
||||
from ....model.user import UserBase
|
||||
from ..context import ContextData, CallbackCommand, CommandContext
|
||||
from ....utils.navigation import get_navigation_context, pop_navigation_context
|
||||
from ....utils.main import build_field_sequence
|
||||
from ....utils.main import build_field_sequence, get_value_repr
|
||||
from ....utils.serialization import serialize
|
||||
|
||||
|
||||
async def wrap_editor(
|
||||
@@ -16,6 +21,7 @@ async def wrap_editor(
|
||||
state_data: dict,
|
||||
user: UserBase,
|
||||
context: BotContext,
|
||||
entity: BotEntity | Any = None,
|
||||
):
|
||||
if callback_data.context in [
|
||||
CommandContext.ENTITY_CREATE,
|
||||
@@ -23,9 +29,9 @@ async def wrap_editor(
|
||||
CommandContext.ENTITY_FIELD_EDIT,
|
||||
CommandContext.COMMAND_FORM,
|
||||
]:
|
||||
btns = []
|
||||
show_back = True
|
||||
show_cancel = True
|
||||
|
||||
if callback_data.context == CommandContext.COMMAND_FORM:
|
||||
field_sequence = list(field_descriptor.command.param_form.keys())
|
||||
field_index = field_sequence.index(callback_data.field_name)
|
||||
@@ -57,8 +63,55 @@ async def wrap_editor(
|
||||
else 0
|
||||
)
|
||||
|
||||
stack, context = get_navigation_context(state_data=state_data)
|
||||
context = pop_navigation_context(stack)
|
||||
stack, navigation_context = get_navigation_context(state_data=state_data)
|
||||
navigation_context = pop_navigation_context(stack)
|
||||
|
||||
if not issubclass(field_descriptor.type_base, BotEnum):
|
||||
options = []
|
||||
if field_descriptor.options:
|
||||
if isinstance(field_descriptor.options, list):
|
||||
options = field_descriptor.options
|
||||
elif callable(field_descriptor.options):
|
||||
if iscoroutinefunction(field_descriptor.options):
|
||||
options = await field_descriptor.options(entity, context)
|
||||
else:
|
||||
options = field_descriptor.options(entity, context)
|
||||
|
||||
for option_row in options:
|
||||
btns_row = []
|
||||
for option in option_row:
|
||||
if isinstance(option, tuple):
|
||||
value = option[0]
|
||||
caption = option[1]
|
||||
else:
|
||||
value = option
|
||||
caption = await get_value_repr(
|
||||
value=value,
|
||||
field_descriptor=field_descriptor,
|
||||
context=context,
|
||||
)
|
||||
|
||||
btns_row.append(
|
||||
InlineKeyboardButton(
|
||||
text=caption,
|
||||
callback_data=ContextData(
|
||||
command=CallbackCommand.FIELD_EDITOR_CALLBACK,
|
||||
context=callback_data.context,
|
||||
entity_name=callback_data.entity_name,
|
||||
entity_id=callback_data.entity_id,
|
||||
field_name=callback_data.field_name,
|
||||
form_params=callback_data.form_params,
|
||||
user_command=callback_data.user_command,
|
||||
data=serialize(
|
||||
value=value,
|
||||
field_descriptor=field_descriptor,
|
||||
),
|
||||
).pack(),
|
||||
)
|
||||
)
|
||||
keyboard_builder.row(*btns_row)
|
||||
|
||||
btns = []
|
||||
|
||||
if field_index > 0 and show_back:
|
||||
btns.append(
|
||||
@@ -102,9 +155,11 @@ async def wrap_editor(
|
||||
keyboard_builder.row(
|
||||
InlineKeyboardButton(
|
||||
text=(await Settings.get(Settings.APP_STRINGS_CANCEL_BTN)),
|
||||
callback_data=context.pack()
|
||||
if context
|
||||
else ContextData(command=CallbackCommand.DELETE_MESSAGE).pack(),
|
||||
callback_data=navigation_context.pack()
|
||||
if navigation_context
|
||||
else ContextData(
|
||||
command=CallbackCommand.DELETE_MESSAGE, data="clear_nav"
|
||||
).pack(),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -144,7 +144,7 @@ async def entity_item(
|
||||
)
|
||||
else:
|
||||
if field_descriptor.type_base is bool:
|
||||
btn_text = f"{'【✔︎】 ' if field_value else '【 】 '}{
|
||||
btn_text = f"{'[✓] ' if field_value else '[ ] '}{
|
||||
await get_callable_str(
|
||||
callable_str=field_descriptor.caption,
|
||||
context=context,
|
||||
|
||||
@@ -240,12 +240,12 @@ async def entity_list(
|
||||
filtering_fields=form_list.filtering_fields,
|
||||
)
|
||||
|
||||
context = pop_navigation_context(navigation_stack)
|
||||
if context:
|
||||
navigation_context = pop_navigation_context(navigation_stack)
|
||||
if navigation_context:
|
||||
keyboard_builder.row(
|
||||
InlineKeyboardButton(
|
||||
text=(await Settings.get(Settings.APP_STRINGS_BACK_BTN)),
|
||||
callback_data=context.pack(),
|
||||
callback_data=navigation_context.pack(),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ async def parameters_menu(
|
||||
caption = key.name
|
||||
|
||||
if key.type_ is bool:
|
||||
caption = f"{'【✔︎】' if value else '【 】'} {caption}"
|
||||
caption = f"{'[✓]' if value else '[ ]'} {caption}"
|
||||
else:
|
||||
caption = f"{caption}: {await get_value_repr(value=value, field_descriptor=key, context=context, locale=user.lang)}"
|
||||
|
||||
|
||||
@@ -103,7 +103,9 @@ class QBotApp(Generic[UserType, ConfigType], FastAPI):
|
||||
self.bot = Bot(
|
||||
token=self.config.TELEGRAM_BOT_TOKEN,
|
||||
session=session,
|
||||
default=DefaultBotProperties(parse_mode="HTML"),
|
||||
default=DefaultBotProperties(
|
||||
parse_mode="HTML", link_preview_is_disabled=True
|
||||
),
|
||||
)
|
||||
|
||||
dp = Dispatcher(storage=DbStorage())
|
||||
|
||||
@@ -125,7 +125,13 @@ class _BaseFieldDescriptor[T: "BotEntity"]:
|
||||
ep_parent_field: str | None = None
|
||||
ep_child_field: str | None = None
|
||||
dt_type: Literal["date", "datetime"] = "date"
|
||||
options: list[Any] | Callable[[T, "BotContext"], list[Any]] | None = None
|
||||
options: (
|
||||
list[list[Union[Any, tuple[Any, str]]]]
|
||||
| Callable[[T, "BotContext"], list[list[Union[Any, tuple[Any, str]]]]]
|
||||
| None
|
||||
) = None
|
||||
options_custom_value: bool = True
|
||||
show_current_value_button: bool = True
|
||||
show_skip_in_editor: Literal[False, "Auto"] = "Auto"
|
||||
default: Any = None
|
||||
default_factory: Callable[[], Any] | None = None
|
||||
|
||||
@@ -19,6 +19,7 @@ from ..model.descriptors import (
|
||||
EntityDescriptor,
|
||||
EntityPermission,
|
||||
_BaseFieldDescriptor,
|
||||
_BaseEntityDescriptor,
|
||||
Filter,
|
||||
)
|
||||
|
||||
@@ -76,11 +77,11 @@ def check_entity_permission(
|
||||
if perm_mapping[permission] in permissions:
|
||||
return True
|
||||
|
||||
ownership_filds = entity_descriptor.ownership_fields
|
||||
ownership_fields = entity_descriptor.ownership_fields
|
||||
|
||||
for role in user.roles:
|
||||
if role in ownership_filds:
|
||||
if getattr(entity, ownership_filds[role]) == user.id:
|
||||
if role in ownership_fields:
|
||||
if getattr(entity, ownership_fields[role]) == user.id:
|
||||
return True
|
||||
else:
|
||||
if permission in permissions:
|
||||
@@ -148,7 +149,7 @@ async def get_value_repr(
|
||||
|
||||
type_ = field_descriptor.type_base
|
||||
if isinstance(value, bool):
|
||||
return "【✔︎】" if value else "【 】"
|
||||
return "[✓]" if value else "[ ]"
|
||||
elif field_descriptor.is_list:
|
||||
if issubclass(type_, BotEntity):
|
||||
return f"[{
|
||||
@@ -204,8 +205,9 @@ async def get_callable_str(
|
||||
return await callable_str(descriptor, entity, context)
|
||||
else:
|
||||
param = args[next(iter(args))]
|
||||
if not isinstance(param.annotation, str) and issubclass(
|
||||
param.annotation, _BaseFieldDescriptor
|
||||
if not isinstance(param.annotation, str) and (
|
||||
issubclass(param.annotation, _BaseFieldDescriptor)
|
||||
or issubclass(param.annotation, _BaseEntityDescriptor)
|
||||
):
|
||||
return await callable_str(descriptor, context)
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user