add ruff format, ruff check, time_picker, project structure and imports reorganized
This commit is contained in:
@@ -8,7 +8,6 @@ logger = getLogger(__name__)
|
||||
|
||||
|
||||
class CallbackCommandFilter(Filter):
|
||||
|
||||
def __init__(self, command: CallbackCommand):
|
||||
self.command = command
|
||||
|
||||
@@ -19,11 +18,9 @@ class CallbackCommandFilter(Filter):
|
||||
if context_data:
|
||||
try:
|
||||
context_data = ContextData.unpack(context_data)
|
||||
except Exception as e:
|
||||
logger.error(f"Error unpacking context data", exc_info = True)
|
||||
except Exception:
|
||||
logger.error("Error unpacking context data", exc_info=True)
|
||||
return False
|
||||
else:
|
||||
return context_data.command == self.command
|
||||
return False
|
||||
|
||||
|
||||
@@ -1,345 +0,0 @@
|
||||
from types import NoneType, UnionType
|
||||
from aiogram import Router, F
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.types import Message, CallbackQuery, InlineKeyboardButton
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from babel.support import LazyProxy
|
||||
from inspect import signature
|
||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
from typing import Any, get_args, get_origin, TYPE_CHECKING
|
||||
import ujson as json
|
||||
|
||||
|
||||
from ..context import ContextData, CallbackCommand, CommandContext
|
||||
from ...command_context_filter import CallbackCommandFilter
|
||||
from ....model.user import UserBase
|
||||
from ....model.settings import Settings
|
||||
from ....model.bot_entity import BotEntity
|
||||
from ....model.bot_enum import BotEnum
|
||||
from ....model.view_setting import ViewSetting
|
||||
from ....utils import get_local_text, deserialize
|
||||
from ....model.descriptors import (EntityFieldDescriptor,
|
||||
EntityDescriptor,
|
||||
EntityCaptionCallable,
|
||||
EntityItemCaptionCallable,
|
||||
EntityFieldCaptionCallable)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ....main import QBotApp
|
||||
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
def get_send_message(message: Message | CallbackQuery):
|
||||
if isinstance(message, Message):
|
||||
return message.answer
|
||||
else:
|
||||
return message.message.edit_text
|
||||
|
||||
|
||||
# def get_local_text(text: str, lang: str):
|
||||
# try:
|
||||
# text_obj = json.loads(text) #@IgnoreException
|
||||
# return text_obj.get(lang, text_obj[list(text_obj.keys())[0]])
|
||||
# except:
|
||||
# return text
|
||||
|
||||
|
||||
def get_value_repr(value: Any, field_descriptor: EntityFieldDescriptor, locale: str | None = None) -> str:
|
||||
|
||||
type_ = field_descriptor.type_base
|
||||
if value is None:
|
||||
return ""
|
||||
|
||||
if isinstance(value, bool):
|
||||
return "【✔︎】" if value else "【 】"
|
||||
elif field_descriptor.is_list:
|
||||
if issubclass(type_, BotEntity):
|
||||
if locale and type_.bot_entity_descriptor.fields_descriptors["name"].localizable:
|
||||
return "[" + ", ".join([get_local_text(text = item.name, locale = locale) for item in value]) + "]"
|
||||
else:
|
||||
return "[" + ", ".join([str(item.name) for item in value]) + "]"
|
||||
elif issubclass(type_, BotEnum):
|
||||
return "[" + ", ".join(item.localized(locale) for item in value) + "]"
|
||||
elif type_ == str:
|
||||
return "[" + ", ".join([f"\"{item}\"" for item in value]) + "]"
|
||||
else:
|
||||
return "[" + ", ".join([str(item) for item in value]) + "]"
|
||||
elif issubclass(type_, BotEntity):
|
||||
if type_.bot_entity_descriptor.fields_descriptors["name"].localizable:
|
||||
return get_local_text(text = value.name, locale = locale)
|
||||
return value.name
|
||||
elif issubclass(type_, BotEnum):
|
||||
return value.localized(locale)
|
||||
elif isinstance(value, str):
|
||||
if field_descriptor and field_descriptor.localizable:
|
||||
return get_local_text(text = value, locale = locale)
|
||||
return value
|
||||
elif isinstance(value, int):
|
||||
return str(value)
|
||||
elif isinstance(value, float):
|
||||
return str(value)
|
||||
else:
|
||||
return str(value)
|
||||
|
||||
|
||||
def get_callable_str(callable_str: str | LazyProxy | EntityCaptionCallable | EntityItemCaptionCallable | EntityFieldCaptionCallable,
|
||||
descriptor: EntityFieldDescriptor | EntityDescriptor,
|
||||
entity: Any = None,
|
||||
value: Any = None) -> str:
|
||||
|
||||
if isinstance(callable_str, str):
|
||||
return callable_str
|
||||
elif isinstance(callable_str, LazyProxy):
|
||||
return callable_str.value
|
||||
elif callable(callable_str):
|
||||
args = signature(callable_str).parameters
|
||||
if len(args) == 1:
|
||||
return callable_str(descriptor)
|
||||
elif len(args) == 2:
|
||||
return callable_str(descriptor, entity)
|
||||
elif len(args) == 3:
|
||||
return callable_str(descriptor, entity, value)
|
||||
|
||||
|
||||
async def authorize_command(user: UserBase,
|
||||
callback_data: ContextData):
|
||||
|
||||
if (callback_data.command == CallbackCommand.MENU_ENTRY_PARAMETERS or
|
||||
callback_data.context == CommandContext.SETTING_EDIT):
|
||||
allowed_roles = (await Settings.get(Settings.SECURITY_PARAMETERS_ROLES))
|
||||
return any(role in user.roles for role in allowed_roles)
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def get_entity_descriptor(app: "QBotApp", callback_data: ContextData) -> EntityDescriptor | None:
|
||||
|
||||
if callback_data.entity_name:
|
||||
return app.entity_metadata.entity_descriptors[callback_data.entity_name]
|
||||
return None
|
||||
|
||||
|
||||
def get_field_descriptor(app: "QBotApp", callback_data: ContextData) -> EntityFieldDescriptor | None:
|
||||
|
||||
if callback_data.context == CommandContext.SETTING_EDIT:
|
||||
return Settings.list_params()[callback_data.field_name]
|
||||
elif callback_data.context in [CommandContext.ENTITY_CREATE, CommandContext.ENTITY_EDIT, CommandContext.ENTITY_FIELD_EDIT]:
|
||||
entity_descriptor = get_entity_descriptor(app, callback_data)
|
||||
if entity_descriptor:
|
||||
return entity_descriptor.fields_descriptors.get(callback_data.field_name)
|
||||
return None
|
||||
|
||||
|
||||
def add_pagination_controls(keyboard_builder: InlineKeyboardBuilder,
|
||||
callback_data: ContextData,
|
||||
total_pages: int,
|
||||
command: CallbackCommand,
|
||||
page: int):
|
||||
|
||||
if total_pages > 1:
|
||||
navigation_buttons = []
|
||||
ContextData(**callback_data.model_dump()).__setattr__
|
||||
if total_pages > 10:
|
||||
navigation_buttons.append(InlineKeyboardButton(text = "⏮️",
|
||||
callback_data = ContextData(
|
||||
command = command,
|
||||
context = callback_data.context,
|
||||
entity_name = callback_data.entity_name,
|
||||
entity_id = callback_data.entity_id,
|
||||
field_name = callback_data.field_name,
|
||||
data = "1" if page != 1 else "skip",
|
||||
save_state = True).pack()))
|
||||
navigation_buttons.append(InlineKeyboardButton(text = "⏪️",
|
||||
callback_data = ContextData(
|
||||
command = command,
|
||||
context = callback_data.context,
|
||||
entity_name = callback_data.entity_name,
|
||||
entity_id = callback_data.entity_id,
|
||||
field_name = callback_data.field_name,
|
||||
data = str(max(page - 10, 1)) if page > 1 else "skip",
|
||||
save_state = True).pack()))
|
||||
|
||||
navigation_buttons.append(InlineKeyboardButton(text = f"◀️",
|
||||
callback_data = ContextData(
|
||||
command = command,
|
||||
context = callback_data.context,
|
||||
entity_name = callback_data.entity_name,
|
||||
entity_id = callback_data.entity_id,
|
||||
field_name = callback_data.field_name,
|
||||
data = str(max(page - 1, 1)) if page > 1 else "skip",
|
||||
save_state = True).pack()))
|
||||
navigation_buttons.append(InlineKeyboardButton(text = f"▶️",
|
||||
callback_data = ContextData(
|
||||
command = command,
|
||||
context = callback_data.context,
|
||||
entity_name = callback_data.entity_name,
|
||||
entity_id = callback_data.entity_id,
|
||||
field_name = callback_data.field_name,
|
||||
data = str(min(page + 1, total_pages)) if page < total_pages else "skip",
|
||||
save_state = True).pack()))
|
||||
|
||||
if total_pages > 10:
|
||||
navigation_buttons.append(InlineKeyboardButton(text = "⏩️",
|
||||
callback_data = ContextData(
|
||||
command = command,
|
||||
context = callback_data.context,
|
||||
entity_name = callback_data.entity_name,
|
||||
entity_id = callback_data.entity_id,
|
||||
field_name = callback_data.field_name,
|
||||
data = str(min(page + 10, total_pages)) if page < total_pages else "skip",
|
||||
save_state = True).pack()))
|
||||
navigation_buttons.append(InlineKeyboardButton(text = "⏭️",
|
||||
callback_data = ContextData(
|
||||
command = command,
|
||||
context = callback_data.context,
|
||||
entity_name = callback_data.entity_name,
|
||||
entity_id = callback_data.entity_id,
|
||||
field_name = callback_data.field_name,
|
||||
data = str(total_pages) if page != total_pages else "skip",
|
||||
save_state = True).pack()))
|
||||
|
||||
keyboard_builder.row(*navigation_buttons)
|
||||
|
||||
|
||||
def add_filter_controls(keyboard_builder: InlineKeyboardBuilder,
|
||||
entity_descriptor: EntityDescriptor,
|
||||
filter: str = None,
|
||||
page: int = 1):
|
||||
|
||||
field_name_descriptor = entity_descriptor.fields_descriptors["name"]
|
||||
if field_name_descriptor.caption:
|
||||
caption = get_callable_str(field_name_descriptor.caption, field_name_descriptor)
|
||||
else:
|
||||
caption = field_name_descriptor.name
|
||||
|
||||
keyboard_builder.row(
|
||||
InlineKeyboardButton(
|
||||
text = f"🔎 {caption}{f": \"{filter}\"" if filter else ""}",
|
||||
callback_data = ContextData(
|
||||
command = CallbackCommand.VIEW_FILTER_EDIT,
|
||||
entity_name = entity_descriptor.name,
|
||||
data = str(page)).pack()))
|
||||
|
||||
|
||||
@router.callback_query(ContextData.filter(F.command == CallbackCommand.VIEW_FILTER_EDIT))
|
||||
async def view_filter_edit(query: CallbackQuery, **kwargs):
|
||||
|
||||
callback_data: ContextData = kwargs["callback_data"]
|
||||
state: FSMContext = kwargs["state"]
|
||||
state_data = await state.get_data()
|
||||
kwargs["state_data"] = state_data
|
||||
|
||||
args = callback_data.data.split("&")
|
||||
page = int(args[0])
|
||||
cmd = None
|
||||
if len(args) > 1:
|
||||
cmd = args[1]
|
||||
|
||||
db_session: AsyncSession = kwargs["db_session"]
|
||||
app: "QBotApp" = kwargs["app"]
|
||||
user: UserBase = kwargs["user"]
|
||||
entity_descriptor = get_entity_descriptor(app = app, callback_data = callback_data)
|
||||
|
||||
if cmd in ["cancel", "clear"]:
|
||||
|
||||
if cmd == "clear":
|
||||
await ViewSetting.set_filter(session = db_session, user_id = user.id, entity_name = entity_descriptor.class_name, filter = None)
|
||||
|
||||
context_data_bak = state_data.pop("context_data_bak", None)
|
||||
if context_data_bak:
|
||||
|
||||
state_data["context_data"] = context_data_bak
|
||||
context_data = ContextData.unpack(context_data_bak)
|
||||
|
||||
field_descriptor = get_field_descriptor(app, context_data)
|
||||
edit_prompt = state_data["edit_prompt"]
|
||||
current_value = await deserialize(session = db_session,
|
||||
type_ = field_descriptor.type_,
|
||||
value = state_data["value"])
|
||||
page = int(state_data.pop("page"))
|
||||
kwargs.pop("callback_data")
|
||||
|
||||
return await render_entity_picker(field_descriptor = field_descriptor,
|
||||
message = query,
|
||||
callback_data = context_data,
|
||||
current_value = current_value,
|
||||
edit_prompt = edit_prompt,
|
||||
page = page,
|
||||
**kwargs)
|
||||
|
||||
else:
|
||||
|
||||
state_data.pop("context_data", None)
|
||||
return await route_callback(message = query, back = False, **kwargs)
|
||||
|
||||
|
||||
#await save_navigation_context(callback_data = callback_data, state = state)
|
||||
old_context_data = state_data.get("context_data")
|
||||
await state.update_data({"context_data": callback_data.pack(),
|
||||
"context_data_bak": old_context_data,
|
||||
"page": page})
|
||||
|
||||
send_message = get_send_message(query)
|
||||
|
||||
await send_message(text = await Settings.get(Settings.APP_STRINGS_VIEW_FILTER_EDIT_PROMPT),
|
||||
reply_markup = InlineKeyboardBuilder().row(
|
||||
InlineKeyboardButton(
|
||||
text = await Settings.get(Settings.APP_STRINGS_CANCEL_BTN),
|
||||
callback_data = ContextData(
|
||||
command = CallbackCommand.VIEW_FILTER_EDIT,
|
||||
entity_name = entity_descriptor.name,
|
||||
data = f"{page}&cancel").pack()),
|
||||
InlineKeyboardButton(
|
||||
text = await Settings.get(Settings.APP_STRINGS_CLEAR_BTN),
|
||||
callback_data = ContextData(
|
||||
command = CallbackCommand.VIEW_FILTER_EDIT,
|
||||
entity_name = entity_descriptor.name,
|
||||
data = f"{page}&clear").pack())).as_markup())
|
||||
|
||||
|
||||
@router.message(CallbackCommandFilter(command = CallbackCommand.VIEW_FILTER_EDIT))
|
||||
async def view_filter_edit_input(message: Message, **kwargs):
|
||||
|
||||
state: FSMContext = kwargs["state"]
|
||||
state_data = await state.get_data()
|
||||
kwargs["state_data"] = state_data
|
||||
callback_data = ContextData.unpack(state_data["context_data"])
|
||||
db_session: AsyncSession = kwargs["db_session"]
|
||||
user: UserBase = kwargs["user"]
|
||||
app: "QBotApp" = kwargs["app"]
|
||||
entity_descriptor = get_entity_descriptor(app = app, callback_data = callback_data)
|
||||
filter = message.text
|
||||
await ViewSetting.set_filter(session = db_session, user_id = user.id, entity_name = entity_descriptor.class_name, filter = filter)
|
||||
|
||||
#state_data.pop("context_data")
|
||||
#return await route_callback(message = message, back = False, **kwargs)
|
||||
|
||||
context_data_bak = state_data.pop("context_data_bak", None)
|
||||
if context_data_bak:
|
||||
state_data["context_data"] = context_data_bak
|
||||
context_data = ContextData.unpack(context_data_bak)
|
||||
field_descriptor = get_field_descriptor(app, context_data)
|
||||
edit_prompt = state_data["edit_prompt"]
|
||||
current_value = await deserialize(session = db_session,
|
||||
type_ = field_descriptor.type_,
|
||||
value = state_data["value"])
|
||||
page = int(state_data.pop("page"))
|
||||
|
||||
return await render_entity_picker(field_descriptor = field_descriptor,
|
||||
message = message,
|
||||
callback_data = context_data,
|
||||
current_value = current_value,
|
||||
edit_prompt = edit_prompt,
|
||||
page = page,
|
||||
**kwargs)
|
||||
|
||||
else:
|
||||
|
||||
state_data.pop("context_data", None)
|
||||
return await route_callback(message = message, back = False, **kwargs)
|
||||
|
||||
|
||||
from ..navigation import route_callback, save_navigation_context, clear_state, get_navigation_context
|
||||
from ..editors.entity import render_entity_picker
|
||||
30
bot/handlers/common/filtering.py
Normal file
30
bot/handlers/common/filtering.py
Normal file
@@ -0,0 +1,30 @@
|
||||
from aiogram.types import InlineKeyboardButton
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from ....model.descriptors import EntityDescriptor
|
||||
from ....utils.main import get_callable_str
|
||||
from ..context import ContextData, CallbackCommand
|
||||
|
||||
|
||||
def add_filter_controls(
|
||||
keyboard_builder: InlineKeyboardBuilder,
|
||||
entity_descriptor: EntityDescriptor,
|
||||
filter: str = None,
|
||||
page: int = 1,
|
||||
):
|
||||
field_name_descriptor = entity_descriptor.fields_descriptors["name"]
|
||||
if field_name_descriptor.caption:
|
||||
caption = get_callable_str(field_name_descriptor.caption, field_name_descriptor)
|
||||
else:
|
||||
caption = field_name_descriptor.name
|
||||
|
||||
keyboard_builder.row(
|
||||
InlineKeyboardButton(
|
||||
text=f"🔎 {caption}{f': "{filter}"' if filter else ''}",
|
||||
callback_data=ContextData(
|
||||
command=CallbackCommand.VIEW_FILTER_EDIT,
|
||||
entity_name=entity_descriptor.name,
|
||||
data=str(page),
|
||||
).pack(),
|
||||
)
|
||||
)
|
||||
168
bot/handlers/common/filtering_callbacks.py
Normal file
168
bot/handlers/common/filtering_callbacks.py
Normal file
@@ -0,0 +1,168 @@
|
||||
from aiogram import Router, F
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.types import Message, CallbackQuery, InlineKeyboardButton
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ..context import ContextData, CallbackCommand
|
||||
from ...command_context_filter import CallbackCommandFilter
|
||||
from ....model.user import UserBase
|
||||
from ....model.settings import Settings
|
||||
from ....model.view_setting import ViewSetting
|
||||
from ....utils.main import (
|
||||
get_send_message,
|
||||
get_entity_descriptor,
|
||||
get_field_descriptor,
|
||||
)
|
||||
from ....utils.serialization import deserialize
|
||||
from ..editors.entity import render_entity_picker
|
||||
from .routing import route_callback
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ....main import QBotApp
|
||||
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
@router.callback_query(
|
||||
ContextData.filter(F.command == CallbackCommand.VIEW_FILTER_EDIT)
|
||||
)
|
||||
async def view_filter_edit(query: CallbackQuery, **kwargs):
|
||||
callback_data: ContextData = kwargs["callback_data"]
|
||||
state: FSMContext = kwargs["state"]
|
||||
state_data = await state.get_data()
|
||||
kwargs["state_data"] = state_data
|
||||
|
||||
args = callback_data.data.split("&")
|
||||
page = int(args[0])
|
||||
cmd = None
|
||||
if len(args) > 1:
|
||||
cmd = args[1]
|
||||
|
||||
db_session: AsyncSession = kwargs["db_session"]
|
||||
app: "QBotApp" = kwargs["app"]
|
||||
user: UserBase = kwargs["user"]
|
||||
entity_descriptor = get_entity_descriptor(app=app, callback_data=callback_data)
|
||||
|
||||
if cmd in ["cancel", "clear"]:
|
||||
if cmd == "clear":
|
||||
await ViewSetting.set_filter(
|
||||
session=db_session,
|
||||
user_id=user.id,
|
||||
entity_name=entity_descriptor.class_name,
|
||||
filter=None,
|
||||
)
|
||||
|
||||
context_data_bak = state_data.pop("context_data_bak", None)
|
||||
if context_data_bak:
|
||||
state_data["context_data"] = context_data_bak
|
||||
context_data = ContextData.unpack(context_data_bak)
|
||||
|
||||
field_descriptor = get_field_descriptor(app, context_data)
|
||||
edit_prompt = state_data["edit_prompt"]
|
||||
current_value = await deserialize(
|
||||
session=db_session,
|
||||
type_=field_descriptor.type_,
|
||||
value=state_data["value"],
|
||||
)
|
||||
page = int(state_data.pop("page"))
|
||||
kwargs.pop("callback_data")
|
||||
|
||||
return await render_entity_picker(
|
||||
field_descriptor=field_descriptor,
|
||||
message=query,
|
||||
callback_data=context_data,
|
||||
current_value=current_value,
|
||||
edit_prompt=edit_prompt,
|
||||
page=page,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
else:
|
||||
state_data.pop("context_data", None)
|
||||
return await route_callback(message=query, back=False, **kwargs)
|
||||
|
||||
# await save_navigation_context(callback_data = callback_data, state = state)
|
||||
old_context_data = state_data.get("context_data")
|
||||
await state.update_data(
|
||||
{
|
||||
"context_data": callback_data.pack(),
|
||||
"context_data_bak": old_context_data,
|
||||
"page": page,
|
||||
}
|
||||
)
|
||||
|
||||
send_message = get_send_message(query)
|
||||
|
||||
await send_message(
|
||||
text=await Settings.get(Settings.APP_STRINGS_VIEW_FILTER_EDIT_PROMPT),
|
||||
reply_markup=InlineKeyboardBuilder()
|
||||
.row(
|
||||
InlineKeyboardButton(
|
||||
text=await Settings.get(Settings.APP_STRINGS_CANCEL_BTN),
|
||||
callback_data=ContextData(
|
||||
command=CallbackCommand.VIEW_FILTER_EDIT,
|
||||
entity_name=entity_descriptor.name,
|
||||
data=f"{page}&cancel",
|
||||
).pack(),
|
||||
),
|
||||
InlineKeyboardButton(
|
||||
text=await Settings.get(Settings.APP_STRINGS_CLEAR_BTN),
|
||||
callback_data=ContextData(
|
||||
command=CallbackCommand.VIEW_FILTER_EDIT,
|
||||
entity_name=entity_descriptor.name,
|
||||
data=f"{page}&clear",
|
||||
).pack(),
|
||||
),
|
||||
)
|
||||
.as_markup(),
|
||||
)
|
||||
|
||||
|
||||
@router.message(CallbackCommandFilter(command=CallbackCommand.VIEW_FILTER_EDIT))
|
||||
async def view_filter_edit_input(message: Message, **kwargs):
|
||||
state: FSMContext = kwargs["state"]
|
||||
state_data = await state.get_data()
|
||||
kwargs["state_data"] = state_data
|
||||
callback_data = ContextData.unpack(state_data["context_data"])
|
||||
db_session: AsyncSession = kwargs["db_session"]
|
||||
user: UserBase = kwargs["user"]
|
||||
app: "QBotApp" = kwargs["app"]
|
||||
entity_descriptor = get_entity_descriptor(app=app, callback_data=callback_data)
|
||||
filter = message.text
|
||||
await ViewSetting.set_filter(
|
||||
session=db_session,
|
||||
user_id=user.id,
|
||||
entity_name=entity_descriptor.class_name,
|
||||
filter=filter,
|
||||
)
|
||||
|
||||
# state_data.pop("context_data")
|
||||
# return await route_callback(message = message, back = False, **kwargs)
|
||||
|
||||
context_data_bak = state_data.pop("context_data_bak", None)
|
||||
if context_data_bak:
|
||||
state_data["context_data"] = context_data_bak
|
||||
context_data = ContextData.unpack(context_data_bak)
|
||||
field_descriptor = get_field_descriptor(app, context_data)
|
||||
edit_prompt = state_data["edit_prompt"]
|
||||
current_value = await deserialize(
|
||||
session=db_session, type_=field_descriptor.type_, value=state_data["value"]
|
||||
)
|
||||
page = int(state_data.pop("page"))
|
||||
|
||||
return await render_entity_picker(
|
||||
field_descriptor=field_descriptor,
|
||||
message=message,
|
||||
callback_data=context_data,
|
||||
current_value=current_value,
|
||||
edit_prompt=edit_prompt,
|
||||
page=page,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
else:
|
||||
state_data.pop("context_data", None)
|
||||
return await route_callback(message=message, back=False, **kwargs)
|
||||
114
bot/handlers/common/pagination.py
Normal file
114
bot/handlers/common/pagination.py
Normal file
@@ -0,0 +1,114 @@
|
||||
from aiogram.types import InlineKeyboardButton
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from ..context import ContextData, CallbackCommand
|
||||
|
||||
|
||||
def add_pagination_controls(
|
||||
keyboard_builder: InlineKeyboardBuilder,
|
||||
callback_data: ContextData,
|
||||
total_pages: int,
|
||||
command: CallbackCommand,
|
||||
page: int,
|
||||
):
|
||||
if total_pages > 1:
|
||||
navigation_buttons = []
|
||||
ContextData(**callback_data.model_dump()).__setattr__
|
||||
if total_pages > 10:
|
||||
navigation_buttons.append(
|
||||
InlineKeyboardButton(
|
||||
text="⏮️",
|
||||
callback_data=ContextData(
|
||||
command=command,
|
||||
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,
|
||||
data="1" if page != 1 else "skip",
|
||||
).pack(),
|
||||
)
|
||||
)
|
||||
navigation_buttons.append(
|
||||
InlineKeyboardButton(
|
||||
text="⏪️",
|
||||
callback_data=ContextData(
|
||||
command=command,
|
||||
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,
|
||||
data=str(max(page - 10, 1)) if page > 1 else "skip",
|
||||
).pack(),
|
||||
)
|
||||
)
|
||||
|
||||
navigation_buttons.append(
|
||||
InlineKeyboardButton(
|
||||
text="◀️",
|
||||
callback_data=ContextData(
|
||||
command=command,
|
||||
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,
|
||||
data=str(max(page - 1, 1)) if page > 1 else "skip",
|
||||
).pack(),
|
||||
)
|
||||
)
|
||||
navigation_buttons.append(
|
||||
InlineKeyboardButton(
|
||||
text="▶️",
|
||||
callback_data=ContextData(
|
||||
command=command,
|
||||
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,
|
||||
data=(
|
||||
str(min(page + 1, total_pages))
|
||||
if page < total_pages
|
||||
else "skip"
|
||||
),
|
||||
).pack(),
|
||||
)
|
||||
)
|
||||
|
||||
if total_pages > 10:
|
||||
navigation_buttons.append(
|
||||
InlineKeyboardButton(
|
||||
text="⏩️",
|
||||
callback_data=ContextData(
|
||||
command=command,
|
||||
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,
|
||||
data=(
|
||||
str(min(page + 10, total_pages))
|
||||
if page < total_pages
|
||||
else "skip"
|
||||
),
|
||||
).pack(),
|
||||
)
|
||||
)
|
||||
navigation_buttons.append(
|
||||
InlineKeyboardButton(
|
||||
text="⏭️",
|
||||
callback_data=ContextData(
|
||||
command=command,
|
||||
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,
|
||||
data=str(total_pages) if page != total_pages else "skip",
|
||||
).pack(),
|
||||
)
|
||||
)
|
||||
|
||||
keyboard_builder.row(*navigation_buttons)
|
||||
48
bot/handlers/common/routing.py
Normal file
48
bot/handlers/common/routing.py
Normal file
@@ -0,0 +1,48 @@
|
||||
from aiogram.types import Message, CallbackQuery
|
||||
from ..context import CallbackCommand
|
||||
|
||||
from ..navigation import (
|
||||
get_navigation_context,
|
||||
save_navigation_context,
|
||||
pop_navigation_context,
|
||||
)
|
||||
|
||||
import qbot.bot.handlers.menu.main as menu_main
|
||||
import qbot.bot.handlers.menu.settings as menu_settings
|
||||
import qbot.bot.handlers.menu.parameters as menu_parameters
|
||||
import qbot.bot.handlers.menu.language as menu_language
|
||||
import qbot.bot.handlers.menu.entities as menu_entities
|
||||
import qbot.bot.handlers.forms.entity_list as form_list
|
||||
import qbot.bot.handlers.forms.entity_form as form_item
|
||||
import qbot.bot.handlers.editors.main as editor
|
||||
|
||||
|
||||
async def route_callback(message: Message | CallbackQuery, back: bool = True, **kwargs):
|
||||
state_data = kwargs["state_data"]
|
||||
stack, context = get_navigation_context(state_data)
|
||||
if back:
|
||||
context = pop_navigation_context(stack)
|
||||
stack = save_navigation_context(callback_data=context, state_data=state_data)
|
||||
kwargs.update({"callback_data": context, "navigation_stack": stack})
|
||||
if context:
|
||||
if context.command == CallbackCommand.MENU_ENTRY_MAIN:
|
||||
await menu_main.main_menu(message, **kwargs)
|
||||
elif context.command == CallbackCommand.MENU_ENTRY_SETTINGS:
|
||||
await menu_settings.settings_menu(message, **kwargs)
|
||||
elif context.command == CallbackCommand.MENU_ENTRY_PARAMETERS:
|
||||
await menu_parameters.parameters_menu(message, **kwargs)
|
||||
elif context.command == CallbackCommand.MENU_ENTRY_LANGUAGE:
|
||||
await menu_language.language_menu(message, **kwargs)
|
||||
elif context.command == CallbackCommand.MENU_ENTRY_ENTITIES:
|
||||
await menu_entities.entities_menu(message, **kwargs)
|
||||
elif context.command == CallbackCommand.ENTITY_LIST:
|
||||
await form_list.entity_list(message, **kwargs)
|
||||
elif context.command == CallbackCommand.ENTITY_ITEM:
|
||||
await form_item.entity_item(message, **kwargs)
|
||||
elif context.command == CallbackCommand.FIELD_EDITOR:
|
||||
await editor.field_editor(message, **kwargs)
|
||||
|
||||
else:
|
||||
raise ValueError(f"Unknown command {context.command}")
|
||||
else:
|
||||
raise ValueError("No navigation context")
|
||||
@@ -1,8 +1,8 @@
|
||||
from aiogram.filters.callback_data import CallbackData as BaseCallbackData
|
||||
from enum import StrEnum
|
||||
|
||||
class CallbackCommand(StrEnum):
|
||||
|
||||
class CallbackCommand(StrEnum):
|
||||
FIELD_EDITOR = "fe"
|
||||
FIELD_EDITOR_CALLBACK = "fc"
|
||||
ENTITY_LIST = "el"
|
||||
@@ -16,25 +16,28 @@ class CallbackCommand(StrEnum):
|
||||
SET_LANGUAGE = "ls"
|
||||
DATE_PICKER_MONTH = "dm"
|
||||
DATE_PICKER_YEAR = "dy"
|
||||
#STRING_EDITOR_LOCALE = "sl"
|
||||
TIME_PICKER = "tp"
|
||||
# STRING_EDITOR_LOCALE = "sl"
|
||||
ENTITY_PICKER_PAGE = "ep"
|
||||
ENTITY_PICKER_TOGGLE_ITEM = "et"
|
||||
VIEW_FILTER_EDIT = "vf"
|
||||
USER_COMMAND = "uc"
|
||||
|
||||
class CommandContext(StrEnum):
|
||||
|
||||
class CommandContext(StrEnum):
|
||||
SETTING_EDIT = "se"
|
||||
ENTITY_CREATE = "ec"
|
||||
ENTITY_EDIT = "ee"
|
||||
ENTITY_FIELD_EDIT = "ef"
|
||||
|
||||
class ContextData(BaseCallbackData, prefix = "cd"):
|
||||
|
||||
class ContextData(BaseCallbackData, prefix="cd"):
|
||||
command: CallbackCommand
|
||||
context: CommandContext | None = None
|
||||
entity_name: str | None = None
|
||||
entity_id: int | None = None
|
||||
field_name: str | None = None
|
||||
form_params: str | None = None
|
||||
user_command: str | None = None
|
||||
data: str | None = None
|
||||
back: bool = False
|
||||
|
||||
@@ -1,416 +0,0 @@
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
from types import NoneType, UnionType
|
||||
from typing import Union, get_args, get_origin, TYPE_CHECKING
|
||||
from aiogram import Router, F
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.types import Message, CallbackQuery
|
||||
from logging import getLogger
|
||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
import ujson as json
|
||||
|
||||
from ....model import EntityPermission
|
||||
from ....model.bot_entity import BotEntity
|
||||
from ....model.owned_bot_entity import OwnedBotEntity
|
||||
from ....model.bot_enum import BotEnum
|
||||
from ....model.language import LanguageBase
|
||||
from ....model.settings import Settings
|
||||
from ....model.user import UserBase
|
||||
from ....model.descriptors import EntityFieldDescriptor
|
||||
from ....utils import deserialize, get_user_permissions, serialize
|
||||
from ...command_context_filter import CallbackCommandFilter
|
||||
from ..context import ContextData, CallbackCommand, CommandContext
|
||||
|
||||
from ..menu.parameters import parameters_menu
|
||||
from .string import string_editor, router as string_editor_router
|
||||
from .date import date_picker, router as date_picker_router
|
||||
from .boolean import bool_editor, router as bool_editor_router
|
||||
from .entity import entity_picker, router as entity_picker_router
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ....main import QBotApp
|
||||
|
||||
|
||||
logger = getLogger(__name__)
|
||||
router = Router()
|
||||
|
||||
router.include_routers(
|
||||
string_editor_router,
|
||||
date_picker_router,
|
||||
bool_editor_router,
|
||||
entity_picker_router,
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(ContextData.filter(F.command == CallbackCommand.FIELD_EDITOR))
|
||||
async def field_editor(message: Message | CallbackQuery, **kwargs):
|
||||
|
||||
callback_data: ContextData = kwargs.get("callback_data", None)
|
||||
db_session: AsyncSession = kwargs["db_session"]
|
||||
user: UserBase = kwargs["user"]
|
||||
app: "QBotApp" = kwargs["app"]
|
||||
state: FSMContext = kwargs["state"]
|
||||
|
||||
state_data = await state.get_data()
|
||||
entity_data = state_data.get("entity_data")
|
||||
|
||||
for key in ["current_value", "value", "locale_index"]:
|
||||
if key in state_data:
|
||||
state_data.pop(key)
|
||||
|
||||
kwargs["state_data"] = state_data
|
||||
|
||||
entity_descriptor = None
|
||||
|
||||
if callback_data.context == CommandContext.SETTING_EDIT:
|
||||
field_descriptor = get_field_descriptor(app, callback_data)
|
||||
|
||||
if field_descriptor.type_ == bool:
|
||||
if await authorize_command(user = user, callback_data = callback_data):
|
||||
await Settings.set_param(field_descriptor, not await Settings.get(field_descriptor))
|
||||
else:
|
||||
return await message.answer(text = (await Settings.get(Settings.APP_STRINGS_FORBIDDEN)))
|
||||
|
||||
stack, context = get_navigation_context(state_data = state_data)
|
||||
|
||||
return await parameters_menu(message = message,
|
||||
navigation_stack = stack,
|
||||
**kwargs)
|
||||
|
||||
current_value = await Settings.get(field_descriptor, all_locales = True)
|
||||
else:
|
||||
field_descriptor = get_field_descriptor(app, callback_data)
|
||||
entity_descriptor = field_descriptor.entity_descriptor
|
||||
|
||||
current_value = None
|
||||
user_permissions = get_user_permissions(user, entity_descriptor)
|
||||
|
||||
if field_descriptor.type_base == bool and callback_data.context == CommandContext.ENTITY_FIELD_EDIT:
|
||||
|
||||
entity = await entity_descriptor.type_.get(session = db_session, id = int(callback_data.entity_id))
|
||||
|
||||
if (EntityPermission.UPDATE_ALL in user_permissions or
|
||||
(EntityPermission.UPDATE in user_permissions and
|
||||
not isinstance(entity, OwnedBotEntity)) or
|
||||
(EntityPermission.UPDATE in user_permissions and
|
||||
isinstance(entity, OwnedBotEntity) and
|
||||
entity.user_id == user.id)):
|
||||
|
||||
current_value: bool = getattr(entity, field_descriptor.field_name) or False
|
||||
setattr(entity, field_descriptor.field_name, not current_value)
|
||||
|
||||
await db_session.commit()
|
||||
|
||||
stack, context = get_navigation_context(state_data = state_data)
|
||||
|
||||
return await entity_item(query = message, navigation_stack = stack, **kwargs)
|
||||
|
||||
|
||||
if not entity_data and callback_data.context in [CommandContext.ENTITY_EDIT, CommandContext.ENTITY_FIELD_EDIT]:
|
||||
|
||||
entity = await entity_descriptor.type_.get(session = kwargs["db_session"], id = int(callback_data.entity_id))
|
||||
|
||||
if (EntityPermission.READ_ALL in user_permissions or
|
||||
(EntityPermission.READ in user_permissions and
|
||||
not isinstance(entity, OwnedBotEntity)) or
|
||||
(EntityPermission.READ in user_permissions and
|
||||
isinstance(entity, OwnedBotEntity) and
|
||||
entity.user_id == user.id)):
|
||||
|
||||
|
||||
if entity:
|
||||
entity_data = {key: serialize(getattr(entity, key), entity_descriptor.fields_descriptors[key])
|
||||
for key in (entity_descriptor.field_sequence if callback_data.context == CommandContext.ENTITY_EDIT
|
||||
else [callback_data.field_name])}
|
||||
state_data.update({"entity_data": entity_data})
|
||||
|
||||
if entity_data:
|
||||
current_value = await deserialize(session = db_session,
|
||||
type_= field_descriptor.type_,
|
||||
value = entity_data.get(callback_data.field_name))
|
||||
|
||||
kwargs.update({"field_descriptor": field_descriptor})
|
||||
|
||||
save_navigation_context(state_data = state_data, callback_data = callback_data)
|
||||
|
||||
await show_editor(message = message,
|
||||
current_value = current_value,
|
||||
**kwargs)
|
||||
|
||||
|
||||
async def show_editor(message: Message | CallbackQuery,
|
||||
**kwargs):
|
||||
|
||||
field_descriptor: EntityFieldDescriptor = kwargs["field_descriptor"]
|
||||
current_value = kwargs["current_value"]
|
||||
user: UserBase = kwargs["user"]
|
||||
callback_data: ContextData = kwargs.get("callback_data", None)
|
||||
state: FSMContext = kwargs["state"]
|
||||
state_data: dict = kwargs["state_data"]
|
||||
|
||||
value_type = field_descriptor.type_base
|
||||
|
||||
if field_descriptor.edit_prompt:
|
||||
edit_prompt = get_callable_str(field_descriptor.edit_prompt, field_descriptor, None, current_value)
|
||||
else:
|
||||
if field_descriptor.caption:
|
||||
caption_str = get_callable_str(field_descriptor.caption, field_descriptor, None, current_value)
|
||||
else:
|
||||
caption_str = field_descriptor.name
|
||||
if callback_data.context == CommandContext.ENTITY_EDIT:
|
||||
edit_prompt = (await Settings.get(Settings.APP_STRINGS_FIELD_EDIT_PROMPT_TEMPLATE_P_NAME_VALUE)).format(
|
||||
name = caption_str, value = get_value_repr(current_value, field_descriptor, user.lang))
|
||||
else:
|
||||
edit_prompt = (await Settings.get(Settings.APP_STRINGS_FIELD_CREATE_PROMPT_TEMPLATE_P_NAME)).format(
|
||||
name = caption_str)
|
||||
|
||||
kwargs["edit_prompt"] = edit_prompt
|
||||
|
||||
# type_origin = get_origin(value_type)
|
||||
|
||||
# if type_origin in [UnionType, Union]:
|
||||
# args = get_args(value_type)
|
||||
# if args[1] == NoneType:
|
||||
# value_type = args[0]
|
||||
|
||||
if value_type not in [int, float, Decimal, str]:
|
||||
state_data.update({"context_data": callback_data.pack()})
|
||||
|
||||
if value_type == str:
|
||||
await string_editor(message = message, **kwargs)
|
||||
|
||||
elif value_type == bool:
|
||||
await bool_editor(message = message, **kwargs)
|
||||
|
||||
elif value_type in [int, float, Decimal, str]:
|
||||
await string_editor(message = message, **kwargs)
|
||||
|
||||
elif value_type == datetime:
|
||||
await date_picker(message = message, **kwargs)
|
||||
|
||||
# elif type_origin == list:
|
||||
# type_args = get_args(value_type)
|
||||
# if type_args and issubclass(type_args[0], BotEntity) or issubclass(type_args[0], BotEnum):
|
||||
# await entity_picker(message = message, **kwargs)
|
||||
# else:
|
||||
# await string_editor(message = message, **kwargs)
|
||||
|
||||
elif issubclass(value_type, BotEntity) or issubclass(value_type, BotEnum):
|
||||
await entity_picker(message = message, **kwargs)
|
||||
|
||||
else:
|
||||
raise ValueError(f"Unsupported field type: {value_type}")
|
||||
|
||||
|
||||
@router.message(CallbackCommandFilter(CallbackCommand.FIELD_EDITOR_CALLBACK))
|
||||
@router.callback_query(ContextData.filter(F.command == CallbackCommand.FIELD_EDITOR_CALLBACK))
|
||||
async def field_editor_callback(message: Message | CallbackQuery, **kwargs):
|
||||
|
||||
app: "QBotApp" = kwargs["app"]
|
||||
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)
|
||||
type_base = field_descriptor.type_base
|
||||
|
||||
if type_base == str and field_descriptor.localizable:
|
||||
locale_index = int(state_data.get("locale_index"))
|
||||
|
||||
if locale_index < len(LanguageBase.all_members.values()) - 1:
|
||||
|
||||
#entity_data = state_data.get("entity_data", {})
|
||||
#current_value = entity_data.get(field_descriptor.field_name)
|
||||
current_value = state_data.get("current_value")
|
||||
value = state_data.get("value")
|
||||
if value:
|
||||
value = json.loads(value)
|
||||
else:
|
||||
value = {}
|
||||
|
||||
value[list(LanguageBase.all_members.values())[locale_index]] = message.text
|
||||
value = json.dumps(value, ensure_ascii = False)
|
||||
|
||||
state_data.update({"value": value})
|
||||
|
||||
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,
|
||||
current_value = current_value,
|
||||
value = value,
|
||||
**kwargs)
|
||||
else:
|
||||
value = state_data.get("value")
|
||||
if value:
|
||||
value = json.loads(value)
|
||||
else:
|
||||
value = {}
|
||||
value[list(LanguageBase.all_members.values())[locale_index]] = message.text
|
||||
value = json.dumps(value, ensure_ascii = False)
|
||||
|
||||
elif (type_base in [int, float, Decimal]):
|
||||
try:
|
||||
_ = type_base(value) #@IgnoreException
|
||||
except:
|
||||
return await message.answer(text = (await Settings.get(Settings.APP_STRINGS_INVALID_INPUT)))
|
||||
else:
|
||||
callback_data: ContextData = kwargs["callback_data"]
|
||||
if callback_data.data:
|
||||
if callback_data.data == "skip":
|
||||
value = None
|
||||
else:
|
||||
value = callback_data.data
|
||||
else:
|
||||
value = state_data.get("value")
|
||||
field_descriptor = get_field_descriptor(app, callback_data)
|
||||
|
||||
kwargs.update({"callback_data": callback_data,})
|
||||
|
||||
await process_field_edit_callback(message = message,
|
||||
value = value,
|
||||
field_descriptor = field_descriptor,
|
||||
**kwargs)
|
||||
|
||||
|
||||
async def process_field_edit_callback(message: Message | CallbackQuery, **kwargs):
|
||||
|
||||
user: UserBase = kwargs["user"]
|
||||
db_session: AsyncSession = kwargs["db_session"]
|
||||
callback_data: ContextData = kwargs.get("callback_data", None)
|
||||
# state: FSMContext = kwargs["state"]
|
||||
state_data: dict = kwargs["state_data"]
|
||||
value = kwargs["value"]
|
||||
field_descriptor: EntityFieldDescriptor = kwargs["field_descriptor"]
|
||||
|
||||
if callback_data.context == CommandContext.SETTING_EDIT:
|
||||
|
||||
# clear_state(state_data = state_data)
|
||||
|
||||
if callback_data.data != "cancel":
|
||||
if await authorize_command(user = user, callback_data = callback_data):
|
||||
value = await deserialize(session = db_session, type_ = field_descriptor.type_, value = value)
|
||||
await Settings.set_param(field_descriptor, value)
|
||||
else:
|
||||
return await message.answer(text = (await Settings.get(Settings.APP_STRINGS_FORBIDDEN)))
|
||||
|
||||
# stack, context = get_navigation_context(state_data = state_data)
|
||||
|
||||
return await route_callback(message = message, back = True, **kwargs)
|
||||
|
||||
# return await parameters_menu(message = message,
|
||||
# navigation_stack = stack,
|
||||
# **kwargs)
|
||||
|
||||
elif callback_data.context in [CommandContext.ENTITY_CREATE, CommandContext.ENTITY_EDIT, CommandContext.ENTITY_FIELD_EDIT]:
|
||||
|
||||
app: "QBotApp" = kwargs["app"]
|
||||
|
||||
entity_descriptor = get_entity_descriptor(app, callback_data)
|
||||
|
||||
field_sequence = entity_descriptor.field_sequence
|
||||
current_index = (field_sequence.index(callback_data.field_name)
|
||||
if callback_data.context in [CommandContext.ENTITY_CREATE, CommandContext.ENTITY_EDIT] else 0)
|
||||
|
||||
entity_data = state_data.get("entity_data", {})
|
||||
|
||||
if (callback_data.context in [CommandContext.ENTITY_CREATE, CommandContext.ENTITY_EDIT] and
|
||||
current_index < len(field_sequence) - 1):
|
||||
|
||||
entity_data[field_descriptor.field_name] = value
|
||||
state_data.update({"entity_data": entity_data})
|
||||
|
||||
next_field_name = field_sequence[current_index + 1]
|
||||
next_field_descriptor = entity_descriptor.fields_descriptors[next_field_name]
|
||||
kwargs.update({"field_descriptor": next_field_descriptor})
|
||||
callback_data.field_name = next_field_name
|
||||
|
||||
state_entity_val = entity_data.get(next_field_descriptor.field_name)
|
||||
|
||||
current_value = await deserialize(session = db_session, type_ = next_field_descriptor.type_,
|
||||
value = state_entity_val) if state_entity_val else None
|
||||
|
||||
await show_editor(message = message,
|
||||
entity_descriptor = entity_descriptor,
|
||||
current_value = current_value,
|
||||
**kwargs)
|
||||
|
||||
else:
|
||||
|
||||
entity_type: BotEntity = entity_descriptor.type_
|
||||
user_permissions = get_user_permissions(user, entity_descriptor)
|
||||
|
||||
if ((callback_data.context == CommandContext.ENTITY_CREATE and
|
||||
EntityPermission.CREATE not in user_permissions and
|
||||
EntityPermission.CREATE_ALL not in user_permissions) or
|
||||
(callback_data.context in [CommandContext.ENTITY_EDIT, CommandContext.ENTITY_FIELD_EDIT] and
|
||||
EntityPermission.UPDATE not in user_permissions and
|
||||
EntityPermission.UPDATE_ALL not in user_permissions)):
|
||||
return await message.answer(text = (await Settings.get(Settings.APP_STRINGS_FORBIDDEN)))
|
||||
|
||||
is_owned = issubclass(entity_type, OwnedBotEntity)
|
||||
|
||||
entity_data[field_descriptor.field_name] = value
|
||||
if is_owned and EntityPermission.CREATE_ALL not in user_permissions:
|
||||
entity_data["user_id"] = user.id
|
||||
|
||||
deser_entity_data = {key: await deserialize(
|
||||
session = db_session,
|
||||
type_ = entity_descriptor.fields_descriptors[key].type_,
|
||||
value = value) for key, value in entity_data.items()}
|
||||
|
||||
if callback_data.context == CommandContext.ENTITY_CREATE:
|
||||
|
||||
new_entity = await entity_type.create(session = db_session,
|
||||
obj_in = entity_type(**deser_entity_data),
|
||||
commit = True)
|
||||
|
||||
state_data["navigation_context"] = ContextData(
|
||||
command = CallbackCommand.ENTITY_ITEM,
|
||||
entity_name = entity_descriptor.name,
|
||||
entity_id = str(new_entity.id)).pack()
|
||||
|
||||
state_data.update(state_data)
|
||||
|
||||
# await save_navigation_context(state = state, callback_data = ContextData(
|
||||
# command = CallbackCommand.ENTITY_ITEM,
|
||||
# entity_name = entity_descriptor.name,
|
||||
# entity_id = str(new_entity.id)
|
||||
# ))
|
||||
|
||||
elif callback_data.context in [CommandContext.ENTITY_EDIT, CommandContext.ENTITY_FIELD_EDIT]:
|
||||
|
||||
entity_id = int(callback_data.entity_id)
|
||||
entity = await entity_type.get(session = db_session, id = entity_id)
|
||||
if not entity:
|
||||
return await message.answer(text = (await Settings.get(Settings.APP_STRINGS_NOT_FOUND)))
|
||||
|
||||
if (is_owned and entity.user_id != user.id and
|
||||
EntityPermission.UPDATE_ALL not in user_permissions):
|
||||
return await message.answer(text = (await Settings.get(Settings.APP_STRINGS_FORBIDDEN)))
|
||||
|
||||
for key, value in deser_entity_data.items():
|
||||
setattr(entity, key, value)
|
||||
|
||||
await db_session.commit()
|
||||
|
||||
clear_state(state_data = state_data)
|
||||
|
||||
await route_callback(message = message, back = True, **kwargs)
|
||||
|
||||
|
||||
from ..common import (get_value_repr, authorize_command, get_callable_str,
|
||||
get_entity_descriptor, get_field_descriptor)
|
||||
from ..navigation import get_navigation_context, route_callback, clear_state, save_navigation_context, pop_navigation_context
|
||||
from ..forms.entity_form import entity_item
|
||||
@@ -5,22 +5,23 @@ from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from babel.support import LazyProxy
|
||||
from logging import getLogger
|
||||
|
||||
from ....model.descriptors import EntityFieldDescriptor, EntityDescriptor
|
||||
from ....model.descriptors import EntityFieldDescriptor
|
||||
from ..context import ContextData, CallbackCommand
|
||||
from ..common import get_send_message
|
||||
from .common import wrap_editor
|
||||
from ....utils.main import get_send_message
|
||||
from .wrapper import wrap_editor
|
||||
|
||||
|
||||
logger = getLogger(__name__)
|
||||
router = Router()
|
||||
|
||||
|
||||
async def bool_editor(message: Message | CallbackQuery,
|
||||
edit_prompt: str,
|
||||
field_descriptor: EntityFieldDescriptor,
|
||||
callback_data: ContextData,
|
||||
**kwargs):
|
||||
|
||||
async def bool_editor(
|
||||
message: Message | CallbackQuery,
|
||||
edit_prompt: str,
|
||||
field_descriptor: EntityFieldDescriptor,
|
||||
callback_data: ContextData,
|
||||
**kwargs,
|
||||
):
|
||||
keyboard_builder = InlineKeyboardBuilder()
|
||||
|
||||
if isinstance(field_descriptor.bool_true_value, LazyProxy):
|
||||
@@ -34,41 +35,44 @@ async def bool_editor(message: Message | CallbackQuery,
|
||||
false_caption = field_descriptor.bool_false_value
|
||||
|
||||
keyboard_builder.row(
|
||||
InlineKeyboardButton(text = true_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,
|
||||
data = str(True),
|
||||
save_state = True).pack()),
|
||||
InlineKeyboardButton(text = false_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,
|
||||
data = str(False),
|
||||
save_state = True).pack())
|
||||
InlineKeyboardButton(
|
||||
text=true_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,
|
||||
data=str(True),
|
||||
).pack(),
|
||||
),
|
||||
InlineKeyboardButton(
|
||||
text=false_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,
|
||||
data=str(False),
|
||||
).pack(),
|
||||
),
|
||||
)
|
||||
|
||||
state_data = kwargs["state_data"]
|
||||
|
||||
await wrap_editor(keyboard_builder = keyboard_builder,
|
||||
field_descriptor = field_descriptor,
|
||||
callback_data = callback_data,
|
||||
state_data = state_data)
|
||||
await wrap_editor(
|
||||
keyboard_builder=keyboard_builder,
|
||||
field_descriptor=field_descriptor,
|
||||
callback_data=callback_data,
|
||||
state_data=state_data,
|
||||
)
|
||||
|
||||
state: FSMContext = kwargs["state"]
|
||||
await state.set_data(state_data)
|
||||
await state.set_data(state_data)
|
||||
|
||||
send_message = get_send_message(message)
|
||||
|
||||
await send_message(text = edit_prompt, reply_markup = keyboard_builder.as_markup())
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
await send_message(text=edit_prompt, reply_markup=keyboard_builder.as_markup())
|
||||
|
||||
@@ -1,67 +1,75 @@
|
||||
from types import NoneType, UnionType
|
||||
from typing import get_args, get_origin
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.types import InlineKeyboardButton
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from aiogram.types import Message, CallbackQuery
|
||||
from decimal import Decimal
|
||||
from datetime import datetime, time
|
||||
|
||||
from ....model.descriptors import EntityFieldDescriptor, EntityDescriptor
|
||||
from ....model.bot_entity import BotEntity
|
||||
from ....model.bot_enum import BotEnum
|
||||
from ....model.descriptors import EntityFieldDescriptor
|
||||
from ....model.settings import Settings
|
||||
from ..context import ContextData, CallbackCommand, CommandContext
|
||||
from ..navigation import get_navigation_context, pop_navigation_context
|
||||
from ....model.user import UserBase
|
||||
from ....utils.main import get_callable_str, get_value_repr
|
||||
from ..context import ContextData, CommandContext
|
||||
from .boolean import bool_editor
|
||||
from .date import date_picker, time_picker
|
||||
from .entity import entity_picker
|
||||
from .string import string_editor
|
||||
|
||||
|
||||
async def wrap_editor(keyboard_builder: InlineKeyboardBuilder,
|
||||
field_descriptor: EntityFieldDescriptor,
|
||||
callback_data: ContextData,
|
||||
state_data: dict):
|
||||
|
||||
if callback_data.context in [CommandContext.ENTITY_CREATE, CommandContext.ENTITY_EDIT, CommandContext.ENTITY_FIELD_EDIT]:
|
||||
|
||||
btns = []
|
||||
entity_descriptor = field_descriptor.entity_descriptor
|
||||
field_index = (entity_descriptor.field_sequence.index(field_descriptor.name)
|
||||
if callback_data.context in [CommandContext.ENTITY_CREATE, CommandContext.ENTITY_EDIT]
|
||||
else 0)
|
||||
|
||||
stack, context = get_navigation_context(state_data = state_data)
|
||||
context = pop_navigation_context(stack)
|
||||
async def show_editor(message: Message | CallbackQuery, **kwargs):
|
||||
field_descriptor: EntityFieldDescriptor = kwargs["field_descriptor"]
|
||||
current_value = kwargs["current_value"]
|
||||
user: UserBase = kwargs["user"]
|
||||
callback_data: ContextData = kwargs.get("callback_data", None)
|
||||
state_data: dict = kwargs["state_data"]
|
||||
|
||||
if field_index > 0:
|
||||
btns.append(InlineKeyboardButton(
|
||||
text = (await Settings.get(Settings.APP_STRINGS_BACK_BTN)),
|
||||
callback_data = ContextData(
|
||||
command = CallbackCommand.FIELD_EDITOR,
|
||||
context = callback_data.context,
|
||||
entity_name = callback_data.entity_name,
|
||||
entity_id = callback_data.entity_id,
|
||||
field_name = entity_descriptor.field_sequence[field_index - 1]).pack()))
|
||||
|
||||
if field_descriptor.is_optional:
|
||||
btns.append(InlineKeyboardButton(
|
||||
text = (await Settings.get(Settings.APP_STRINGS_SKIP_BTN)),
|
||||
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,
|
||||
data = "skip").pack()))
|
||||
|
||||
keyboard_builder.row(*btns)
|
||||
value_type = field_descriptor.type_base
|
||||
|
||||
|
||||
keyboard_builder.row(InlineKeyboardButton(
|
||||
text = (await Settings.get(Settings.APP_STRINGS_CANCEL_BTN)),
|
||||
callback_data = context.pack()))
|
||||
if field_descriptor.edit_prompt:
|
||||
edit_prompt = get_callable_str(
|
||||
field_descriptor.edit_prompt, field_descriptor, None, current_value
|
||||
)
|
||||
else:
|
||||
if field_descriptor.caption:
|
||||
caption_str = get_callable_str(
|
||||
field_descriptor.caption, field_descriptor, None, current_value
|
||||
)
|
||||
else:
|
||||
caption_str = field_descriptor.name
|
||||
if callback_data.context == CommandContext.ENTITY_EDIT:
|
||||
edit_prompt = (
|
||||
await Settings.get(
|
||||
Settings.APP_STRINGS_FIELD_EDIT_PROMPT_TEMPLATE_P_NAME_VALUE
|
||||
)
|
||||
).format(
|
||||
name=caption_str,
|
||||
value=get_value_repr(current_value, field_descriptor, user.lang),
|
||||
)
|
||||
else:
|
||||
edit_prompt = (
|
||||
await Settings.get(
|
||||
Settings.APP_STRINGS_FIELD_CREATE_PROMPT_TEMPLATE_P_NAME
|
||||
)
|
||||
).format(name=caption_str)
|
||||
|
||||
elif callback_data.context == CommandContext.SETTING_EDIT:
|
||||
kwargs["edit_prompt"] = edit_prompt
|
||||
|
||||
keyboard_builder.row(
|
||||
InlineKeyboardButton(
|
||||
text = (await Settings.get(Settings.APP_STRINGS_CANCEL_BTN)),
|
||||
callback_data = ContextData(
|
||||
command = CallbackCommand.FIELD_EDITOR_CALLBACK,
|
||||
context = callback_data.context,
|
||||
field_name = callback_data.field_name,
|
||||
data = "cancel").pack()))
|
||||
|
||||
if value_type not in [int, float, Decimal, str]:
|
||||
state_data.update({"context_data": callback_data.pack()})
|
||||
|
||||
if value_type is bool:
|
||||
await bool_editor(message=message, **kwargs)
|
||||
|
||||
elif value_type in [int, float, Decimal, str]:
|
||||
await string_editor(message=message, **kwargs)
|
||||
|
||||
elif value_type is datetime:
|
||||
await date_picker(message=message, **kwargs)
|
||||
|
||||
elif value_type is time:
|
||||
await time_picker(message=message, **kwargs)
|
||||
|
||||
elif issubclass(value_type, BotEntity) or issubclass(value_type, BotEnum):
|
||||
await entity_picker(message=message, **kwargs)
|
||||
|
||||
else:
|
||||
raise ValueError(f"Unsupported field type: {value_type}")
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime, time, timedelta
|
||||
from aiogram import Router, F
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.types import Message, CallbackQuery, InlineKeyboardButton
|
||||
@@ -6,10 +6,11 @@ from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from logging import getLogger
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ....model.descriptors import EntityFieldDescriptor, EntityDescriptor
|
||||
from ....model.descriptors import EntityFieldDescriptor
|
||||
from ....model.settings import Settings
|
||||
from ..context import ContextData, CallbackCommand
|
||||
from ..common import get_send_message, get_field_descriptor, get_entity_descriptor
|
||||
from .common import wrap_editor
|
||||
from ....utils.main import get_send_message, get_field_descriptor
|
||||
from .wrapper import wrap_editor
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ....main import QBotApp
|
||||
@@ -19,154 +20,343 @@ logger = getLogger(__name__)
|
||||
router = Router()
|
||||
|
||||
|
||||
async def date_picker(message: Message | CallbackQuery,
|
||||
field_descriptor: EntityFieldDescriptor,
|
||||
callback_data: ContextData,
|
||||
current_value: datetime,
|
||||
state: FSMContext,
|
||||
edit_prompt: str | None = None,
|
||||
**kwargs):
|
||||
|
||||
if not current_value:
|
||||
start_date = datetime.now()
|
||||
else:
|
||||
start_date = current_value
|
||||
|
||||
start_date = start_date.replace(day = 1)
|
||||
|
||||
previous_month = start_date - timedelta(days = 1)
|
||||
next_month = start_date.replace(day = 28) + timedelta(days = 4)
|
||||
|
||||
keyboard_builder = InlineKeyboardBuilder()
|
||||
keyboard_builder.row(InlineKeyboardButton(text = "◀️",
|
||||
callback_data = ContextData(
|
||||
command = CallbackCommand.DATE_PICKER_MONTH,
|
||||
context = callback_data.context,
|
||||
entity_name = callback_data.entity_name,
|
||||
entity_id = callback_data.entity_id,
|
||||
field_name = callback_data.field_name,
|
||||
data = previous_month.strftime("%Y-%m-%d"),
|
||||
save_state = True).pack()),
|
||||
InlineKeyboardButton(text = start_date.strftime("%b %Y"),
|
||||
callback_data = ContextData(
|
||||
command = CallbackCommand.DATE_PICKER_YEAR,
|
||||
context = callback_data.context,
|
||||
entity_name = callback_data.entity_name,
|
||||
entity_id = callback_data.entity_id,
|
||||
field_name = callback_data.field_name,
|
||||
data = start_date.strftime("%Y-%m-%d"),
|
||||
save_state = True).pack()),
|
||||
InlineKeyboardButton(text = "▶️",
|
||||
callback_data = ContextData(
|
||||
command = CallbackCommand.DATE_PICKER_MONTH,
|
||||
context = callback_data.context,
|
||||
entity_name = callback_data.entity_name,
|
||||
entity_id = callback_data.entity_id,
|
||||
field_name = callback_data.field_name,
|
||||
data = next_month.strftime("%Y-%m-%d"),
|
||||
save_state = True).pack()))
|
||||
|
||||
first_day = start_date - timedelta(days = start_date.weekday())
|
||||
weeks = (((start_date.replace(day = 28) + timedelta(days = 4)).replace(day = 1) - first_day).days - 1) // 7 + 1
|
||||
for week in range(weeks):
|
||||
buttons = []
|
||||
for day in range(7):
|
||||
current_day = first_day + timedelta(days = week * 7 + day)
|
||||
buttons.append(InlineKeyboardButton(text = current_day.strftime("%d"),
|
||||
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,
|
||||
data = current_day.strftime("%Y-%m-%d"),
|
||||
save_state = True).pack()))
|
||||
|
||||
keyboard_builder.row(*buttons)
|
||||
|
||||
state_data = kwargs["state_data"]
|
||||
|
||||
await wrap_editor(keyboard_builder = keyboard_builder,
|
||||
field_descriptor = field_descriptor,
|
||||
callback_data = callback_data,
|
||||
state_data = state_data)
|
||||
|
||||
await state.set_data(state_data)
|
||||
|
||||
if edit_prompt:
|
||||
send_message = get_send_message(message)
|
||||
await send_message(text = edit_prompt, reply_markup = keyboard_builder.as_markup())
|
||||
else:
|
||||
await message.edit_reply_markup(reply_markup = keyboard_builder.as_markup())
|
||||
|
||||
|
||||
@router.callback_query(ContextData.filter(F.command == CallbackCommand.DATE_PICKER_YEAR))
|
||||
async def date_picker_year(query: CallbackQuery,
|
||||
callback_data: ContextData,
|
||||
app: "QBotApp",
|
||||
state: FSMContext,
|
||||
**kwargs):
|
||||
|
||||
start_date = datetime.strptime(callback_data.data, "%Y-%m-%d")
|
||||
|
||||
state_data = await state.get_data()
|
||||
kwargs["state_data"] = state_data
|
||||
|
||||
keyboard_builder = InlineKeyboardBuilder()
|
||||
keyboard_builder.row(InlineKeyboardButton(text = "🔼",
|
||||
callback_data = ContextData(
|
||||
command = CallbackCommand.DATE_PICKER_YEAR,
|
||||
context = callback_data.context,
|
||||
entity_name = callback_data.entity_name,
|
||||
entity_id = callback_data.entity_id,
|
||||
field_name = callback_data.field_name,
|
||||
data = start_date.replace(year = start_date.year - 20).strftime("%Y-%m-%d")).pack()))
|
||||
|
||||
for r in range(4):
|
||||
buttons = []
|
||||
for c in range(5):
|
||||
current_date = start_date.replace(year = start_date.year + r * 5 + c - 10)
|
||||
buttons.append(InlineKeyboardButton(text = current_date.strftime("%Y"),
|
||||
callback_data = ContextData(
|
||||
command = CallbackCommand.DATE_PICKER_MONTH,
|
||||
context = callback_data.context,
|
||||
entity_name = callback_data.entity_name,
|
||||
entity_id = callback_data.entity_id,
|
||||
field_name = callback_data.field_name,
|
||||
data = current_date.strftime("%Y-%m-%d"),
|
||||
save_state = True).pack()))
|
||||
|
||||
keyboard_builder.row(*buttons)
|
||||
|
||||
keyboard_builder.row(InlineKeyboardButton(text = "🔽",
|
||||
callback_data = ContextData(
|
||||
command = CallbackCommand.DATE_PICKER_YEAR,
|
||||
context = callback_data.context,
|
||||
entity_name = callback_data.entity_name,
|
||||
entity_id = callback_data.entity_id,
|
||||
field_name = callback_data.field_name,
|
||||
data = start_date.replace(year = start_date.year + 20).strftime("%Y-%m-%d")).pack()))
|
||||
|
||||
field_descriptor = get_field_descriptor(app, callback_data)
|
||||
|
||||
await wrap_editor(keyboard_builder = keyboard_builder,
|
||||
field_descriptor = field_descriptor,
|
||||
callback_data = callback_data,
|
||||
state_data = state_data)
|
||||
|
||||
await query.message.edit_reply_markup(reply_markup = keyboard_builder.as_markup())
|
||||
|
||||
|
||||
@router.callback_query(ContextData.filter(F.command == CallbackCommand.DATE_PICKER_MONTH))
|
||||
async def date_picker_month(query: CallbackQuery, callback_data: ContextData, app: "QBotApp", **kwargs):
|
||||
@router.callback_query(ContextData.filter(F.command == CallbackCommand.TIME_PICKER))
|
||||
async def time_picker_callback(
|
||||
query: CallbackQuery, callback_data: ContextData, app: "QBotApp", **kwargs
|
||||
):
|
||||
if not callback_data.data:
|
||||
return
|
||||
|
||||
field_descriptor = get_field_descriptor(app, callback_data)
|
||||
state: FSMContext = kwargs["state"]
|
||||
state_data = await state.get_data()
|
||||
kwargs["state_data"] = state_data
|
||||
|
||||
await date_picker(query.message,
|
||||
field_descriptor = field_descriptor,
|
||||
callback_data = callback_data,
|
||||
current_value = datetime.strptime(callback_data.data, "%Y-%m-%d"),
|
||||
**kwargs)
|
||||
await time_picker(
|
||||
query.message,
|
||||
field_descriptor=field_descriptor,
|
||||
callback_data=callback_data,
|
||||
current_value=datetime.strptime(callback_data.data, "%Y-%m-%d %H-%M")
|
||||
if len(callback_data.data) > 10
|
||||
else time.fromisoformat(callback_data.data.replace("-", ":")),
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
||||
async def time_picker(
|
||||
message: Message | CallbackQuery,
|
||||
field_descriptor: EntityFieldDescriptor,
|
||||
callback_data: ContextData,
|
||||
current_value: datetime | time,
|
||||
state: FSMContext,
|
||||
edit_prompt: str | None = None,
|
||||
**kwargs,
|
||||
):
|
||||
keyboard_builder = InlineKeyboardBuilder()
|
||||
|
||||
for i in range(12):
|
||||
keyboard_builder.row(
|
||||
InlineKeyboardButton(
|
||||
text=(
|
||||
"▶︎ {v:02d} ◀︎" if i == (current_value.hour % 12) else "{v:02d}"
|
||||
).format(v=i if current_value.hour < 12 else i + 12),
|
||||
callback_data=ContextData(
|
||||
command=CallbackCommand.TIME_PICKER,
|
||||
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,
|
||||
data=current_value.replace(
|
||||
hour=i if current_value.hour < 12 else i + 12
|
||||
).strftime(
|
||||
"%Y-%m-%d %H-%M"
|
||||
if isinstance(current_value, datetime)
|
||||
else "%H-%M"
|
||||
)
|
||||
if i != current_value.hour % 12
|
||||
else None,
|
||||
).pack(),
|
||||
),
|
||||
InlineKeyboardButton(
|
||||
text=(
|
||||
"▶︎ {v:02d} ◀︎" if i == current_value.minute // 5 else "{v:02d}"
|
||||
).format(v=i * 5),
|
||||
callback_data=ContextData(
|
||||
command=CallbackCommand.TIME_PICKER,
|
||||
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,
|
||||
data=current_value.replace(minute=i * 5).strftime(
|
||||
"%Y-%m-%d %H-%M"
|
||||
if isinstance(current_value, datetime)
|
||||
else "%H-%M"
|
||||
)
|
||||
if i != current_value.minute // 5
|
||||
else None,
|
||||
).pack(),
|
||||
),
|
||||
)
|
||||
keyboard_builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="AM/PM",
|
||||
callback_data=ContextData(
|
||||
command=CallbackCommand.TIME_PICKER,
|
||||
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,
|
||||
data=current_value.replace(
|
||||
hour=current_value.hour + 12
|
||||
if current_value.hour < 12
|
||||
else current_value.hour - 12
|
||||
).strftime(
|
||||
"%Y-%m-%d %H-%M" if isinstance(current_value, datetime) else "%H-%M"
|
||||
),
|
||||
).pack(),
|
||||
),
|
||||
InlineKeyboardButton(
|
||||
text=await Settings.get(Settings.APP_STRINGS_DONE_BTN),
|
||||
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,
|
||||
data=current_value.strftime(
|
||||
"%Y-%m-%d %H-%M" if isinstance(current_value, datetime) else "%H-%M"
|
||||
),
|
||||
).pack(),
|
||||
),
|
||||
)
|
||||
|
||||
state_data = kwargs["state_data"]
|
||||
|
||||
await wrap_editor(
|
||||
keyboard_builder=keyboard_builder,
|
||||
field_descriptor=field_descriptor,
|
||||
callback_data=callback_data,
|
||||
state_data=state_data,
|
||||
)
|
||||
|
||||
await state.set_data(state_data)
|
||||
|
||||
if edit_prompt:
|
||||
send_message = get_send_message(message)
|
||||
await send_message(text=edit_prompt, reply_markup=keyboard_builder.as_markup())
|
||||
else:
|
||||
await message.edit_reply_markup(reply_markup=keyboard_builder.as_markup())
|
||||
|
||||
|
||||
async def date_picker(
|
||||
message: Message | CallbackQuery,
|
||||
field_descriptor: EntityFieldDescriptor,
|
||||
callback_data: ContextData,
|
||||
current_value: datetime,
|
||||
state: FSMContext,
|
||||
edit_prompt: str | None = None,
|
||||
**kwargs,
|
||||
):
|
||||
if not current_value:
|
||||
start_date = datetime.now()
|
||||
else:
|
||||
start_date = current_value
|
||||
|
||||
start_date = current_value.replace(day=1)
|
||||
|
||||
previous_month = start_date - timedelta(days=1)
|
||||
next_month = start_date.replace(day=28) + timedelta(days=4)
|
||||
|
||||
keyboard_builder = InlineKeyboardBuilder()
|
||||
keyboard_builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="◀️",
|
||||
callback_data=ContextData(
|
||||
command=CallbackCommand.DATE_PICKER_MONTH,
|
||||
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,
|
||||
data=previous_month.strftime("%Y-%m-%d %H-%M"),
|
||||
).pack(),
|
||||
),
|
||||
InlineKeyboardButton(
|
||||
text=start_date.strftime("%b %Y"),
|
||||
callback_data=ContextData(
|
||||
command=CallbackCommand.DATE_PICKER_YEAR,
|
||||
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,
|
||||
data=start_date.strftime("%Y-%m-%d %H-%M"),
|
||||
).pack(),
|
||||
),
|
||||
InlineKeyboardButton(
|
||||
text="▶️",
|
||||
callback_data=ContextData(
|
||||
command=CallbackCommand.DATE_PICKER_MONTH,
|
||||
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,
|
||||
data=next_month.strftime("%Y-%m-%d %H-%M"),
|
||||
).pack(),
|
||||
),
|
||||
)
|
||||
|
||||
first_day = start_date - timedelta(days=start_date.weekday())
|
||||
weeks = (
|
||||
(
|
||||
(start_date.replace(day=28) + timedelta(days=4)).replace(day=1) - first_day
|
||||
).days
|
||||
- 1
|
||||
) // 7 + 1
|
||||
for week in range(weeks):
|
||||
buttons = []
|
||||
for day in range(7):
|
||||
current_day = first_day + timedelta(days=week * 7 + day)
|
||||
buttons.append(
|
||||
InlineKeyboardButton(
|
||||
text=current_day.strftime("%d"),
|
||||
callback_data=ContextData(
|
||||
command=CallbackCommand.FIELD_EDITOR_CALLBACK
|
||||
if field_descriptor.dt_type == "date"
|
||||
else CallbackCommand.TIME_PICKER,
|
||||
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,
|
||||
data=current_day.strftime("%Y-%m-%d %H-%M"),
|
||||
).pack(),
|
||||
)
|
||||
)
|
||||
|
||||
keyboard_builder.row(*buttons)
|
||||
|
||||
state_data = kwargs["state_data"]
|
||||
|
||||
await wrap_editor(
|
||||
keyboard_builder=keyboard_builder,
|
||||
field_descriptor=field_descriptor,
|
||||
callback_data=callback_data,
|
||||
state_data=state_data,
|
||||
)
|
||||
|
||||
await state.set_data(state_data)
|
||||
|
||||
if edit_prompt:
|
||||
send_message = get_send_message(message)
|
||||
await send_message(text=edit_prompt, reply_markup=keyboard_builder.as_markup())
|
||||
else:
|
||||
await message.edit_reply_markup(reply_markup=keyboard_builder.as_markup())
|
||||
|
||||
|
||||
@router.callback_query(
|
||||
ContextData.filter(F.command == CallbackCommand.DATE_PICKER_YEAR)
|
||||
)
|
||||
async def date_picker_year(
|
||||
query: CallbackQuery,
|
||||
callback_data: ContextData,
|
||||
app: "QBotApp",
|
||||
state: FSMContext,
|
||||
**kwargs,
|
||||
):
|
||||
start_date = datetime.strptime(callback_data.data, "%Y-%m-%d %H-%M")
|
||||
|
||||
state_data = await state.get_data()
|
||||
kwargs["state_data"] = state_data
|
||||
|
||||
keyboard_builder = InlineKeyboardBuilder()
|
||||
keyboard_builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="🔼",
|
||||
callback_data=ContextData(
|
||||
command=CallbackCommand.DATE_PICKER_YEAR,
|
||||
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,
|
||||
data=start_date.replace(year=start_date.year - 20).strftime(
|
||||
"%Y-%m-%d %H-%M"
|
||||
),
|
||||
).pack(),
|
||||
)
|
||||
)
|
||||
|
||||
for r in range(4):
|
||||
buttons = []
|
||||
for c in range(5):
|
||||
current_date = start_date.replace(year=start_date.year + r * 5 + c - 10)
|
||||
buttons.append(
|
||||
InlineKeyboardButton(
|
||||
text=current_date.strftime("%Y"),
|
||||
callback_data=ContextData(
|
||||
command=CallbackCommand.DATE_PICKER_MONTH,
|
||||
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,
|
||||
data=current_date.strftime("%Y-%m-%d %H-%M"),
|
||||
).pack(),
|
||||
)
|
||||
)
|
||||
|
||||
keyboard_builder.row(*buttons)
|
||||
|
||||
keyboard_builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="🔽",
|
||||
callback_data=ContextData(
|
||||
command=CallbackCommand.DATE_PICKER_YEAR,
|
||||
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,
|
||||
data=start_date.replace(year=start_date.year + 20).strftime(
|
||||
"%Y-%m-%d %H-%M"
|
||||
),
|
||||
).pack(),
|
||||
)
|
||||
)
|
||||
|
||||
field_descriptor = get_field_descriptor(app, callback_data)
|
||||
|
||||
await wrap_editor(
|
||||
keyboard_builder=keyboard_builder,
|
||||
field_descriptor=field_descriptor,
|
||||
callback_data=callback_data,
|
||||
state_data=state_data,
|
||||
)
|
||||
|
||||
await query.message.edit_reply_markup(reply_markup=keyboard_builder.as_markup())
|
||||
|
||||
|
||||
@router.callback_query(
|
||||
ContextData.filter(F.command == CallbackCommand.DATE_PICKER_MONTH)
|
||||
)
|
||||
async def date_picker_month(
|
||||
query: CallbackQuery, callback_data: ContextData, app: "QBotApp", **kwargs
|
||||
):
|
||||
field_descriptor = get_field_descriptor(app, callback_data)
|
||||
state: FSMContext = kwargs["state"]
|
||||
state_data = await state.get_data()
|
||||
kwargs["state_data"] = state_data
|
||||
|
||||
await date_picker(
|
||||
query.message,
|
||||
field_descriptor=field_descriptor,
|
||||
callback_data=callback_data,
|
||||
current_value=datetime.strptime(callback_data.data, "%Y-%m-%d %H-%M"),
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
@@ -1,25 +1,32 @@
|
||||
from types import UnionType
|
||||
from aiogram import Router, F
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.types import Message, CallbackQuery, InlineKeyboardButton
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from logging import getLogger
|
||||
from sqlmodel import column
|
||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
from typing import get_args, get_origin, TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ....model.bot_entity import BotEntity
|
||||
from ....model.owned_bot_entity import OwnedBotEntity
|
||||
|
||||
# from ....model.owned_bot_entity import OwnedBotEntity
|
||||
from ....model.bot_enum import BotEnum
|
||||
from ....model.settings import Settings
|
||||
from ....model.user import UserBase
|
||||
from ....model.view_setting import ViewSetting
|
||||
from ....model.descriptors import EntityFieldDescriptor, EntityDescriptor
|
||||
from ....model.descriptors import EntityFieldDescriptor, Filter
|
||||
from ....model import EntityPermission
|
||||
from ....utils import serialize, deserialize, get_user_permissions
|
||||
from ....utils.main import (
|
||||
get_user_permissions,
|
||||
get_send_message,
|
||||
get_field_descriptor,
|
||||
get_callable_str,
|
||||
)
|
||||
from ....utils.serialization import serialize, deserialize
|
||||
from ..context import ContextData, CallbackCommand
|
||||
from ..common import (get_send_message, get_local_text, get_field_descriptor,
|
||||
get_entity_descriptor, add_pagination_controls, add_filter_controls)
|
||||
from .common import wrap_editor
|
||||
from ..common.pagination import add_pagination_controls
|
||||
from ..common.filtering import add_filter_controls
|
||||
from .wrapper import wrap_editor
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ....main import QBotApp
|
||||
@@ -28,179 +35,295 @@ logger = getLogger(__name__)
|
||||
router = Router()
|
||||
|
||||
|
||||
async def entity_picker(message: Message | CallbackQuery,
|
||||
field_descriptor: EntityFieldDescriptor,
|
||||
edit_prompt: str,
|
||||
current_value: BotEntity | BotEnum | list[BotEntity] | list[BotEnum],
|
||||
**kwargs):
|
||||
|
||||
async def entity_picker(
|
||||
message: Message | CallbackQuery,
|
||||
field_descriptor: EntityFieldDescriptor,
|
||||
edit_prompt: str,
|
||||
current_value: BotEntity | BotEnum | list[BotEntity] | list[BotEnum],
|
||||
**kwargs,
|
||||
):
|
||||
state_data: dict = kwargs["state_data"]
|
||||
|
||||
state_data.update({"current_value": serialize(current_value, field_descriptor),
|
||||
"value": serialize(current_value, field_descriptor),
|
||||
"edit_prompt": edit_prompt})
|
||||
state_data.update(
|
||||
{
|
||||
"current_value": serialize(current_value, field_descriptor),
|
||||
"value": serialize(current_value, field_descriptor),
|
||||
"edit_prompt": edit_prompt,
|
||||
}
|
||||
)
|
||||
|
||||
await render_entity_picker(field_descriptor = field_descriptor,
|
||||
message = message,
|
||||
current_value = current_value,
|
||||
edit_prompt = edit_prompt,
|
||||
**kwargs)
|
||||
await render_entity_picker(
|
||||
field_descriptor=field_descriptor,
|
||||
message=message,
|
||||
current_value=current_value,
|
||||
edit_prompt=edit_prompt,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
||||
def calc_total_pages(items_count: int, page_size: int) -> int:
|
||||
|
||||
return max(items_count // page_size + (1 if items_count % page_size else 0), 1)
|
||||
|
||||
|
||||
async def render_entity_picker(*,
|
||||
field_descriptor: EntityFieldDescriptor,
|
||||
message: Message | CallbackQuery,
|
||||
callback_data: ContextData,
|
||||
user: UserBase,
|
||||
db_session: AsyncSession,
|
||||
state: FSMContext,
|
||||
current_value: BotEntity | BotEnum | list[BotEntity] | list[BotEnum],
|
||||
edit_prompt: str,
|
||||
page: int = 1,
|
||||
**kwargs):
|
||||
|
||||
|
||||
if callback_data.command in [CallbackCommand.ENTITY_PICKER_PAGE, CallbackCommand.ENTITY_PICKER_TOGGLE_ITEM]:
|
||||
async def render_entity_picker(
|
||||
*,
|
||||
field_descriptor: EntityFieldDescriptor,
|
||||
message: Message | CallbackQuery,
|
||||
callback_data: ContextData,
|
||||
user: UserBase,
|
||||
db_session: AsyncSession,
|
||||
state: FSMContext,
|
||||
current_value: BotEntity | BotEnum | list[BotEntity] | list[BotEnum],
|
||||
edit_prompt: str,
|
||||
page: int = 1,
|
||||
**kwargs,
|
||||
):
|
||||
if callback_data.command in [
|
||||
CallbackCommand.ENTITY_PICKER_PAGE,
|
||||
CallbackCommand.ENTITY_PICKER_TOGGLE_ITEM,
|
||||
]:
|
||||
page = int(callback_data.data.split("&")[0])
|
||||
|
||||
# is_list = False
|
||||
|
||||
# type_origin = get_origin(field_descriptor.type_)
|
||||
# if type_origin == UnionType:
|
||||
# type_ = get_args(field_descriptor.type_)[0]
|
||||
|
||||
# elif type_origin == list:
|
||||
# type_ = get_args(field_descriptor.type_)[0]
|
||||
# is_list = True
|
||||
|
||||
# else:
|
||||
# type_ = field_descriptor.type_
|
||||
|
||||
type_ = field_descriptor.type_base
|
||||
is_list = field_descriptor.is_list
|
||||
|
||||
|
||||
if not issubclass(type_, BotEntity) and not issubclass(type_, BotEnum):
|
||||
raise ValueError("Unsupported type")
|
||||
|
||||
|
||||
page_size = await Settings.get(Settings.PAGE_SIZE)
|
||||
form_list = None
|
||||
|
||||
if issubclass(type_, BotEnum):
|
||||
items_count = len(type_.all_members)
|
||||
total_pages = calc_total_pages(items_count, page_size)
|
||||
page = min(page, total_pages)
|
||||
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]
|
||||
else:
|
||||
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
|
||||
]
|
||||
elif issubclass(type_, BotEntity):
|
||||
form_name = field_descriptor.ep_form or "default"
|
||||
form_list = type_.bot_entity_descriptor.lists.get(
|
||||
form_name, type_.bot_entity_descriptor.default_list
|
||||
)
|
||||
permissions = get_user_permissions(user, type_.bot_entity_descriptor)
|
||||
entity_filter = await ViewSetting.get_filter(session = db_session, user_id = user.id, entity_name = type_.bot_entity_descriptor.class_name)
|
||||
if (EntityPermission.LIST_ALL in permissions or
|
||||
(EntityPermission.LIST in permissions and
|
||||
not issubclass(type_, OwnedBotEntity))):
|
||||
items_count = await type_.get_count(session = db_session, filter = entity_filter)
|
||||
total_pages = calc_total_pages(items_count, page_size)
|
||||
page = min(page, total_pages)
|
||||
if form_list.filtering:
|
||||
entity_filter = await ViewSetting.get_filter(
|
||||
session=db_session,
|
||||
user_id=user.id,
|
||||
entity_name=type_.bot_entity_descriptor.class_name,
|
||||
)
|
||||
else:
|
||||
entity_filter = None
|
||||
list_all = EntityPermission.LIST_ALL in permissions
|
||||
|
||||
if list_all or EntityPermission.LIST in permissions:
|
||||
if (
|
||||
field_descriptor.ep_parent_field
|
||||
and field_descriptor.ep_parent_field
|
||||
and callback_data.entity_id
|
||||
):
|
||||
entity = await field_descriptor.entity_descriptor.type_.get(
|
||||
session=db_session, id=callback_data.entity_id
|
||||
)
|
||||
value = getattr(entity, field_descriptor.ep_parent_field)
|
||||
ext_filter = column(field_descriptor.ep_child_field).__eq__(value)
|
||||
|
||||
else:
|
||||
ext_filter = None
|
||||
|
||||
if form_list.pagination:
|
||||
items_count = await type_.get_count(
|
||||
session=db_session,
|
||||
static_filter=(
|
||||
[
|
||||
Filter(
|
||||
field_name=f.field_name,
|
||||
operator=f.operator,
|
||||
value_type="const",
|
||||
value=f.value,
|
||||
)
|
||||
for f in form_list.static_filters
|
||||
if f.value_type == "const"
|
||||
]
|
||||
if isinstance(form_list.static_filters, list)
|
||||
else form_list.static_filters
|
||||
),
|
||||
ext_filter=ext_filter,
|
||||
filter=entity_filter,
|
||||
filter_fields=form_list.filtering_fields,
|
||||
user=user if not list_all else None,
|
||||
)
|
||||
total_pages = calc_total_pages(items_count, page_size)
|
||||
page = min(page, total_pages)
|
||||
skip = page_size * (page - 1)
|
||||
limit = page_size
|
||||
else:
|
||||
skip = 0
|
||||
limit = None
|
||||
entity_items = await type_.get_multi(
|
||||
session = db_session, order_by = type_.name, filter = entity_filter,
|
||||
skip = page_size * (page - 1), limit = page_size)
|
||||
elif (EntityPermission.LIST in permissions and
|
||||
issubclass(type_, OwnedBotEntity)):
|
||||
items_count = await type_.get_count_by_user(session = db_session, user_id = user.id, filter = entity_filter)
|
||||
total_pages = calc_total_pages(items_count, page_size)
|
||||
page = min(page, total_pages)
|
||||
entity_items = await type_.get_multi_by_user(
|
||||
session = db_session, user_id = user.id, order_by = type_.name, filter = entity_filter,
|
||||
skip = page_size * (page - 1), limit = page_size)
|
||||
session=db_session,
|
||||
order_by=form_list.order_by,
|
||||
static_filter=(
|
||||
[
|
||||
Filter(
|
||||
field_name=f.field_name,
|
||||
operator=f.operator,
|
||||
value_type="const",
|
||||
value=f.value,
|
||||
)
|
||||
for f in form_list.static_filters
|
||||
if f.value_type == "const"
|
||||
]
|
||||
if isinstance(form_list.static_filters, list)
|
||||
else form_list.static_filters
|
||||
),
|
||||
ext_filter=ext_filter,
|
||||
filter=entity_filter,
|
||||
user=user if not list_all else None,
|
||||
skip=skip,
|
||||
limit=limit,
|
||||
)
|
||||
else:
|
||||
items_count = 0
|
||||
total_pages = 1
|
||||
page = 1
|
||||
entity_items = list[BotEntity]()
|
||||
|
||||
items = [{"text": f"{"" if not is_list else "【✔︎】 " if item in (current_value or []) else "【 】 "}{
|
||||
type_.bot_entity_descriptor.item_caption(type_.bot_entity_descriptor, item) if type_.bot_entity_descriptor.item_caption
|
||||
else get_local_text(item.name, user.lang) if type_.bot_entity_descriptor.fields_descriptors["name"].localizable else item.name}",
|
||||
"value": str(item.id)} for item in entity_items]
|
||||
|
||||
# total_pages = items_count // page_size + (1 if items_count % page_size else 0)
|
||||
|
||||
|
||||
items = [
|
||||
{
|
||||
"text": f"{
|
||||
''
|
||||
if not is_list
|
||||
else '【✔︎】 '
|
||||
if item in (current_value or [])
|
||||
else '【 】 '
|
||||
}{
|
||||
type_.bot_entity_descriptor.item_repr(
|
||||
type_.bot_entity_descriptor, item
|
||||
)
|
||||
if type_.bot_entity_descriptor.item_repr
|
||||
else get_callable_str(
|
||||
type_.bot_entity_descriptor.full_name,
|
||||
type_.bot_entity_descriptor,
|
||||
item,
|
||||
)
|
||||
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,
|
||||
data = f"{page}&{item['value']}" if is_list else item["value"],
|
||||
save_state = True).pack()))
|
||||
|
||||
add_pagination_controls(keyboard_builder = keyboard_builder,
|
||||
callback_data = callback_data,
|
||||
total_pages = total_pages,
|
||||
command = CallbackCommand.ENTITY_PICKER_PAGE,
|
||||
page = page)
|
||||
|
||||
if issubclass(type_, BotEntity):
|
||||
add_filter_controls(keyboard_builder = keyboard_builder,
|
||||
entity_descriptor = type_.bot_entity_descriptor,
|
||||
filter = entity_filter)
|
||||
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,
|
||||
data=f"{page}&{item['value']}" if is_list else item["value"],
|
||||
).pack(),
|
||||
)
|
||||
)
|
||||
|
||||
if form_list and form_list.pagination:
|
||||
add_pagination_controls(
|
||||
keyboard_builder=keyboard_builder,
|
||||
callback_data=callback_data,
|
||||
total_pages=total_pages,
|
||||
command=CallbackCommand.ENTITY_PICKER_PAGE,
|
||||
page=page,
|
||||
)
|
||||
|
||||
if (
|
||||
issubclass(type_, BotEntity)
|
||||
and form_list.filtering
|
||||
and form_list.filtering_fields
|
||||
):
|
||||
add_filter_controls(
|
||||
keyboard_builder=keyboard_builder,
|
||||
entity_descriptor=type_.bot_entity_descriptor,
|
||||
filter=entity_filter,
|
||||
)
|
||||
|
||||
if is_list:
|
||||
keyboard_builder.row(
|
||||
InlineKeyboardButton(text = await Settings.get(Settings.APP_STRINGS_DONE_BTN),
|
||||
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,
|
||||
save_state = True).pack()))
|
||||
InlineKeyboardButton(
|
||||
text=await Settings.get(Settings.APP_STRINGS_DONE_BTN),
|
||||
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,
|
||||
).pack(),
|
||||
)
|
||||
)
|
||||
|
||||
state_data = kwargs["state_data"]
|
||||
|
||||
await wrap_editor(keyboard_builder = keyboard_builder,
|
||||
field_descriptor = field_descriptor,
|
||||
callback_data = callback_data,
|
||||
state_data = state_data)
|
||||
await wrap_editor(
|
||||
keyboard_builder=keyboard_builder,
|
||||
field_descriptor=field_descriptor,
|
||||
callback_data=callback_data,
|
||||
state_data=state_data,
|
||||
)
|
||||
|
||||
await state.set_data(state_data)
|
||||
|
||||
send_message = get_send_message(message)
|
||||
|
||||
await send_message(text = edit_prompt, reply_markup = keyboard_builder.as_markup())
|
||||
await send_message(text=edit_prompt, reply_markup=keyboard_builder.as_markup())
|
||||
|
||||
|
||||
@router.callback_query(ContextData.filter(F.command == CallbackCommand.ENTITY_PICKER_PAGE))
|
||||
@router.callback_query(ContextData.filter(F.command == CallbackCommand.ENTITY_PICKER_TOGGLE_ITEM))
|
||||
async def entity_picker_callback(query: CallbackQuery,
|
||||
callback_data: ContextData,
|
||||
db_session: AsyncSession,
|
||||
app: "QBotApp",
|
||||
state: FSMContext,
|
||||
**kwargs):
|
||||
|
||||
@router.callback_query(
|
||||
ContextData.filter(F.command == CallbackCommand.ENTITY_PICKER_PAGE)
|
||||
)
|
||||
@router.callback_query(
|
||||
ContextData.filter(F.command == CallbackCommand.ENTITY_PICKER_TOGGLE_ITEM)
|
||||
)
|
||||
async def entity_picker_callback(
|
||||
query: CallbackQuery,
|
||||
callback_data: ContextData,
|
||||
db_session: AsyncSession,
|
||||
app: "QBotApp",
|
||||
state: FSMContext,
|
||||
**kwargs,
|
||||
):
|
||||
state_data = await state.get_data()
|
||||
kwargs["state_data"] = state_data
|
||||
|
||||
field_descriptor = get_field_descriptor(app = app, callback_data = callback_data)
|
||||
field_descriptor = get_field_descriptor(app=app, callback_data=callback_data)
|
||||
|
||||
current_value = await deserialize(session = db_session, type_ = field_descriptor.type_, value = state_data["current_value"])
|
||||
# current_value = await deserialize(session = db_session, type_ = field_descriptor.type_, value = state_data["current_value"])
|
||||
edit_prompt = state_data["edit_prompt"]
|
||||
value = await deserialize(session = db_session, type_ = field_descriptor.type_, value = state_data["value"])
|
||||
value = await deserialize(
|
||||
session=db_session, type_=field_descriptor.type_, value=state_data["value"]
|
||||
)
|
||||
|
||||
if callback_data.command == CallbackCommand.ENTITY_PICKER_TOGGLE_ITEM:
|
||||
page, id_value = callback_data.data.split("&")
|
||||
page = int(page)
|
||||
type_ = field_descriptor.type_base
|
||||
type_ = field_descriptor.type_base
|
||||
if issubclass(type_, BotEnum):
|
||||
item = type_(id_value)
|
||||
if item in value:
|
||||
@@ -208,7 +331,7 @@ async def entity_picker_callback(query: CallbackQuery,
|
||||
else:
|
||||
value.append(item)
|
||||
else:
|
||||
item = await type_.get(session = db_session, id = int(id_value))
|
||||
item = await type_.get(session=db_session, id=int(id_value))
|
||||
if item in value:
|
||||
value.remove(item)
|
||||
else:
|
||||
@@ -221,16 +344,16 @@ async def entity_picker_callback(query: CallbackQuery,
|
||||
page = int(callback_data.data)
|
||||
else:
|
||||
raise ValueError("Unsupported command")
|
||||
|
||||
await render_entity_picker(field_descriptor = field_descriptor,
|
||||
message = query,
|
||||
callback_data = callback_data,
|
||||
current_value = value,
|
||||
edit_prompt = edit_prompt,
|
||||
db_session = db_session,
|
||||
app = app,
|
||||
state = state,
|
||||
page = page,
|
||||
**kwargs)
|
||||
|
||||
|
||||
await render_entity_picker(
|
||||
field_descriptor=field_descriptor,
|
||||
message=query,
|
||||
callback_data=callback_data,
|
||||
current_value=value,
|
||||
edit_prompt=edit_prompt,
|
||||
db_session=db_session,
|
||||
app=app,
|
||||
state=state,
|
||||
page=page,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
156
bot/handlers/editors/main.py
Normal file
156
bot/handlers/editors/main.py
Normal file
@@ -0,0 +1,156 @@
|
||||
from typing import TYPE_CHECKING
|
||||
from aiogram import Router, F
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.types import Message, CallbackQuery
|
||||
from logging import getLogger
|
||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
|
||||
from ....model import EntityPermission
|
||||
from ....model.settings import Settings
|
||||
from ....model.user import UserBase
|
||||
from ....utils.main import (
|
||||
check_entity_permission,
|
||||
get_field_descriptor,
|
||||
)
|
||||
from ....utils.serialization import deserialize, serialize
|
||||
from ..context import ContextData, CallbackCommand, CommandContext
|
||||
from ....auth import authorize_command
|
||||
from ..navigation import (
|
||||
get_navigation_context,
|
||||
save_navigation_context,
|
||||
)
|
||||
from ..forms.entity_form import entity_item
|
||||
from .common import show_editor
|
||||
|
||||
from ..menu.parameters import parameters_menu
|
||||
from .string import router as string_editor_router
|
||||
from .date import router as date_picker_router
|
||||
from .boolean import router as bool_editor_router
|
||||
from .entity import router as entity_picker_router
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ....main import QBotApp
|
||||
|
||||
|
||||
logger = getLogger(__name__)
|
||||
router = Router()
|
||||
|
||||
|
||||
@router.callback_query(ContextData.filter(F.command == CallbackCommand.FIELD_EDITOR))
|
||||
async def field_editor(message: Message | CallbackQuery, **kwargs):
|
||||
callback_data: ContextData = kwargs.get("callback_data", None)
|
||||
db_session: AsyncSession = kwargs["db_session"]
|
||||
user: UserBase = kwargs["user"]
|
||||
app: "QBotApp" = kwargs["app"]
|
||||
state: FSMContext = kwargs["state"]
|
||||
|
||||
state_data = await state.get_data()
|
||||
entity_data = state_data.get("entity_data")
|
||||
|
||||
for key in ["current_value", "value", "locale_index"]:
|
||||
if key in state_data:
|
||||
state_data.pop(key)
|
||||
|
||||
kwargs["state_data"] = state_data
|
||||
|
||||
entity_descriptor = None
|
||||
|
||||
if callback_data.context == CommandContext.SETTING_EDIT:
|
||||
field_descriptor = get_field_descriptor(app, callback_data)
|
||||
|
||||
if field_descriptor.type_ is bool:
|
||||
if await authorize_command(user=user, callback_data=callback_data):
|
||||
await Settings.set_param(
|
||||
field_descriptor, not await Settings.get(field_descriptor)
|
||||
)
|
||||
else:
|
||||
return await message.answer(
|
||||
text=(await Settings.get(Settings.APP_STRINGS_FORBIDDEN))
|
||||
)
|
||||
|
||||
stack, context = get_navigation_context(state_data=state_data)
|
||||
|
||||
return await parameters_menu(
|
||||
message=message, navigation_stack=stack, **kwargs
|
||||
)
|
||||
|
||||
current_value = await Settings.get(field_descriptor, all_locales=True)
|
||||
else:
|
||||
field_descriptor = get_field_descriptor(app, callback_data)
|
||||
entity_descriptor = field_descriptor.entity_descriptor
|
||||
|
||||
current_value = None
|
||||
|
||||
if (
|
||||
field_descriptor.type_base is bool
|
||||
and callback_data.context == CommandContext.ENTITY_FIELD_EDIT
|
||||
):
|
||||
entity = await entity_descriptor.type_.get(
|
||||
session=db_session, id=int(callback_data.entity_id)
|
||||
)
|
||||
if check_entity_permission(
|
||||
entity=entity, user=user, permission=EntityPermission.UPDATE
|
||||
):
|
||||
current_value: bool = (
|
||||
getattr(entity, field_descriptor.field_name) or False
|
||||
)
|
||||
setattr(entity, field_descriptor.field_name, not current_value)
|
||||
|
||||
await db_session.commit()
|
||||
stack, context = get_navigation_context(state_data=state_data)
|
||||
|
||||
return await entity_item(
|
||||
query=message, navigation_stack=stack, **kwargs
|
||||
)
|
||||
|
||||
if not entity_data and callback_data.context in [
|
||||
CommandContext.ENTITY_EDIT,
|
||||
CommandContext.ENTITY_FIELD_EDIT,
|
||||
]:
|
||||
entity = await entity_descriptor.type_.get(
|
||||
session=kwargs["db_session"], id=int(callback_data.entity_id)
|
||||
)
|
||||
if check_entity_permission(
|
||||
entity=entity, user=user, permission=EntityPermission.READ
|
||||
):
|
||||
if entity:
|
||||
form_name = (
|
||||
callback_data.form_params.split("&")[0]
|
||||
if callback_data.form_params
|
||||
else "default"
|
||||
)
|
||||
form = entity_descriptor.forms.get(
|
||||
form_name, entity_descriptor.default_form
|
||||
)
|
||||
entity_data = {
|
||||
key: serialize(
|
||||
getattr(entity, key),
|
||||
entity_descriptor.fields_descriptors[key],
|
||||
)
|
||||
for key in (
|
||||
form.edit_field_sequence
|
||||
if callback_data.context == CommandContext.ENTITY_EDIT
|
||||
else [callback_data.field_name]
|
||||
)
|
||||
}
|
||||
state_data.update({"entity_data": entity_data})
|
||||
|
||||
if entity_data:
|
||||
current_value = await deserialize(
|
||||
session=db_session,
|
||||
type_=field_descriptor.type_,
|
||||
value=entity_data.get(callback_data.field_name),
|
||||
)
|
||||
|
||||
kwargs.update({"field_descriptor": field_descriptor})
|
||||
save_navigation_context(state_data=state_data, callback_data=callback_data)
|
||||
|
||||
await show_editor(message=message, current_value=current_value, **kwargs)
|
||||
|
||||
|
||||
router.include_routers(
|
||||
string_editor_router,
|
||||
date_picker_router,
|
||||
bool_editor_router,
|
||||
entity_picker_router,
|
||||
)
|
||||
284
bot/handlers/editors/main_callbacks.py
Normal file
284
bot/handlers/editors/main_callbacks.py
Normal file
@@ -0,0 +1,284 @@
|
||||
from aiogram import Router, F
|
||||
from aiogram.types import Message, CallbackQuery
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
from typing import TYPE_CHECKING
|
||||
from decimal import Decimal
|
||||
import json
|
||||
|
||||
from ..context import ContextData, CallbackCommand, CommandContext
|
||||
from ...command_context_filter import CallbackCommandFilter
|
||||
from ....model import EntityPermission
|
||||
from ....model.user import UserBase
|
||||
from ....model.settings import Settings
|
||||
from ....model.descriptors import EntityFieldDescriptor
|
||||
from ....model.language import LanguageBase
|
||||
from ....auth import authorize_command
|
||||
from ....utils.main import (
|
||||
get_user_permissions,
|
||||
check_entity_permission,
|
||||
clear_state,
|
||||
get_entity_descriptor,
|
||||
get_field_descriptor,
|
||||
)
|
||||
from ....utils.serialization import deserialize
|
||||
from ..common.routing import route_callback
|
||||
from .common import show_editor
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ....main import QBotApp
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
@router.message(CallbackCommandFilter(CallbackCommand.FIELD_EDITOR_CALLBACK))
|
||||
@router.callback_query(
|
||||
ContextData.filter(F.command == CallbackCommand.FIELD_EDITOR_CALLBACK)
|
||||
)
|
||||
async def field_editor_callback(message: Message | CallbackQuery, **kwargs):
|
||||
app: "QBotApp" = kwargs["app"]
|
||||
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)
|
||||
type_base = field_descriptor.type_base
|
||||
|
||||
if type_base is str and field_descriptor.localizable:
|
||||
locale_index = int(state_data.get("locale_index"))
|
||||
|
||||
value = state_data.get("value")
|
||||
if value:
|
||||
value = json.loads(value)
|
||||
else:
|
||||
value = {}
|
||||
|
||||
value[list(LanguageBase.all_members.keys())[locale_index]] = message.text
|
||||
value = json.dumps(value, ensure_ascii=False)
|
||||
|
||||
if locale_index < len(LanguageBase.all_members.values()) - 1:
|
||||
current_value = state_data.get("current_value")
|
||||
|
||||
state_data.update({"value": value})
|
||||
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,
|
||||
current_value=current_value,
|
||||
value=value,
|
||||
**kwargs,
|
||||
)
|
||||
# else:
|
||||
# value = state_data.get("value")
|
||||
# if value:
|
||||
# value = json.loads(value)
|
||||
# else:
|
||||
# value = {}
|
||||
# value[list(LanguageBase.all_members.keys())[locale_index]] = (
|
||||
# message.text
|
||||
# )
|
||||
# value = json.dumps(value, ensure_ascii=False)
|
||||
|
||||
elif type_base in [int, float, Decimal]:
|
||||
try:
|
||||
_ = type_base(value) # @IgnoreException
|
||||
except Exception:
|
||||
return await message.answer(
|
||||
text=(await Settings.get(Settings.APP_STRINGS_INVALID_INPUT))
|
||||
)
|
||||
else:
|
||||
callback_data: ContextData = kwargs["callback_data"]
|
||||
if callback_data.data:
|
||||
if callback_data.data == "skip":
|
||||
value = None
|
||||
else:
|
||||
value = callback_data.data
|
||||
else:
|
||||
value = state_data.get("value")
|
||||
field_descriptor = get_field_descriptor(app, callback_data)
|
||||
|
||||
kwargs.update(
|
||||
{
|
||||
"callback_data": callback_data,
|
||||
}
|
||||
)
|
||||
|
||||
await process_field_edit_callback(
|
||||
message=message, value=value, field_descriptor=field_descriptor, **kwargs
|
||||
)
|
||||
|
||||
|
||||
async def process_field_edit_callback(message: Message | CallbackQuery, **kwargs):
|
||||
user: UserBase = kwargs["user"]
|
||||
db_session: AsyncSession = kwargs["db_session"]
|
||||
callback_data: ContextData = kwargs.get("callback_data", None)
|
||||
state_data: dict = kwargs["state_data"]
|
||||
value = kwargs["value"]
|
||||
field_descriptor: EntityFieldDescriptor = kwargs["field_descriptor"]
|
||||
|
||||
if callback_data.context == CommandContext.SETTING_EDIT:
|
||||
if callback_data.data != "cancel":
|
||||
if await authorize_command(user=user, callback_data=callback_data):
|
||||
value = await deserialize(
|
||||
session=db_session, type_=field_descriptor.type_, value=value
|
||||
)
|
||||
await Settings.set_param(field_descriptor, value)
|
||||
else:
|
||||
return await message.answer(
|
||||
text=(await Settings.get(Settings.APP_STRINGS_FORBIDDEN))
|
||||
)
|
||||
|
||||
return await route_callback(message=message, back=True, **kwargs)
|
||||
|
||||
elif callback_data.context in [
|
||||
CommandContext.ENTITY_CREATE,
|
||||
CommandContext.ENTITY_EDIT,
|
||||
CommandContext.ENTITY_FIELD_EDIT,
|
||||
]:
|
||||
app: "QBotApp" = kwargs["app"]
|
||||
|
||||
entity_descriptor = get_entity_descriptor(app, callback_data)
|
||||
|
||||
form_name = (
|
||||
callback_data.form_params.split("&")[0]
|
||||
if callback_data.form_params
|
||||
else "default"
|
||||
)
|
||||
form = entity_descriptor.forms.get(form_name, entity_descriptor.default_form)
|
||||
|
||||
field_sequence = form.edit_field_sequence
|
||||
current_index = (
|
||||
field_sequence.index(callback_data.field_name)
|
||||
if callback_data.context
|
||||
in [CommandContext.ENTITY_CREATE, CommandContext.ENTITY_EDIT]
|
||||
else 0
|
||||
)
|
||||
|
||||
entity_data = state_data.get("entity_data", {})
|
||||
|
||||
if (
|
||||
callback_data.context
|
||||
in [CommandContext.ENTITY_CREATE, CommandContext.ENTITY_EDIT]
|
||||
and current_index < len(field_sequence) - 1
|
||||
):
|
||||
entity_data[field_descriptor.field_name] = value
|
||||
state_data.update({"entity_data": entity_data})
|
||||
|
||||
next_field_name = field_sequence[current_index + 1]
|
||||
next_field_descriptor = entity_descriptor.fields_descriptors[
|
||||
next_field_name
|
||||
]
|
||||
kwargs.update({"field_descriptor": next_field_descriptor})
|
||||
callback_data.field_name = next_field_name
|
||||
|
||||
state_entity_val = entity_data.get(next_field_descriptor.field_name)
|
||||
|
||||
current_value = (
|
||||
await deserialize(
|
||||
session=db_session,
|
||||
type_=next_field_descriptor.type_,
|
||||
value=state_entity_val,
|
||||
)
|
||||
if state_entity_val
|
||||
else None
|
||||
)
|
||||
|
||||
await show_editor(
|
||||
message=message,
|
||||
entity_descriptor=entity_descriptor,
|
||||
current_value=current_value,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
else:
|
||||
entity_type = entity_descriptor.type_
|
||||
|
||||
entity_data[field_descriptor.field_name] = value
|
||||
|
||||
# What if user has several roles and each role has its own ownership field? Should we allow creation even
|
||||
# if user has no CREATE_ALL permission
|
||||
|
||||
# for role in user.roles:
|
||||
# if role in entity_descriptor.ownership_fields and not EntityPermission.CREATE_ALL in user_permissions:
|
||||
# entity_data[entity_descriptor.ownership_fields[role]] = user.id
|
||||
|
||||
deser_entity_data = {
|
||||
key: await deserialize(
|
||||
session=db_session,
|
||||
type_=entity_descriptor.fields_descriptors[key].type_,
|
||||
value=value,
|
||||
)
|
||||
for key, value in entity_data.items()
|
||||
}
|
||||
|
||||
if callback_data.context == CommandContext.ENTITY_CREATE:
|
||||
user_permissions = get_user_permissions(user, entity_descriptor)
|
||||
if (
|
||||
EntityPermission.CREATE not in user_permissions
|
||||
and EntityPermission.CREATE_ALL not in user_permissions
|
||||
):
|
||||
return await message.answer(
|
||||
text=(await Settings.get(Settings.APP_STRINGS_FORBIDDEN))
|
||||
)
|
||||
|
||||
new_entity = await entity_type.create(
|
||||
session=db_session,
|
||||
obj_in=entity_type(**deser_entity_data),
|
||||
commit=True,
|
||||
)
|
||||
|
||||
form_name = (
|
||||
callback_data.form_params.split("&")[0]
|
||||
if callback_data.form_params
|
||||
else "default"
|
||||
)
|
||||
form_list = entity_descriptor.lists.get(
|
||||
form_name or "default", entity_descriptor.default_list
|
||||
)
|
||||
|
||||
state_data["navigation_context"] = ContextData(
|
||||
command=CallbackCommand.ENTITY_ITEM,
|
||||
entity_name=entity_descriptor.name,
|
||||
form_params=form_list.item_form,
|
||||
entity_id=str(new_entity.id),
|
||||
).pack()
|
||||
|
||||
state_data.update(state_data)
|
||||
|
||||
elif callback_data.context in [
|
||||
CommandContext.ENTITY_EDIT,
|
||||
CommandContext.ENTITY_FIELD_EDIT,
|
||||
]:
|
||||
entity_id = int(callback_data.entity_id)
|
||||
entity = await entity_type.get(session=db_session, id=entity_id)
|
||||
if not entity:
|
||||
return await message.answer(
|
||||
text=(await Settings.get(Settings.APP_STRINGS_NOT_FOUND))
|
||||
)
|
||||
|
||||
if not check_entity_permission(
|
||||
entity=entity, user=user, permission=EntityPermission.UPDATE
|
||||
):
|
||||
return await message.answer(
|
||||
text=(await Settings.get(Settings.APP_STRINGS_FORBIDDEN))
|
||||
)
|
||||
|
||||
for key, value in deser_entity_data.items():
|
||||
setattr(entity, key, value)
|
||||
|
||||
await db_session.commit()
|
||||
|
||||
clear_state(state_data=state_data)
|
||||
|
||||
await route_callback(message=message, back=True, **kwargs)
|
||||
@@ -1,98 +1,97 @@
|
||||
from types import UnionType
|
||||
from aiogram import Router
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.types import Message, CallbackQuery, InlineKeyboardButton, CopyTextButton
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from logging import getLogger
|
||||
from typing import Any, get_args, get_origin
|
||||
from typing import Any
|
||||
|
||||
from ....model.descriptors import EntityFieldDescriptor, EntityDescriptor
|
||||
from ....model.descriptors import EntityFieldDescriptor
|
||||
from ....model.language import LanguageBase
|
||||
from ....model.settings import Settings
|
||||
from ....utils import serialize
|
||||
from ....utils.main import get_send_message, get_local_text
|
||||
from ....utils.serialization import serialize
|
||||
from ..context import ContextData, CallbackCommand
|
||||
from ..common import get_send_message, get_local_text
|
||||
from .common import wrap_editor
|
||||
from .wrapper import wrap_editor
|
||||
|
||||
|
||||
logger = getLogger(__name__)
|
||||
router = Router()
|
||||
|
||||
|
||||
async def string_editor(message: Message | CallbackQuery,
|
||||
field_descriptor: EntityFieldDescriptor,
|
||||
callback_data: ContextData,
|
||||
current_value: Any,
|
||||
edit_prompt: str,
|
||||
state: FSMContext,
|
||||
locale_index: int = 0,
|
||||
**kwargs):
|
||||
|
||||
async def string_editor(
|
||||
message: Message | CallbackQuery,
|
||||
field_descriptor: EntityFieldDescriptor,
|
||||
callback_data: ContextData,
|
||||
current_value: Any,
|
||||
edit_prompt: str,
|
||||
state: FSMContext,
|
||||
locale_index: int = 0,
|
||||
**kwargs,
|
||||
):
|
||||
keyboard_builder = InlineKeyboardBuilder()
|
||||
|
||||
state_data: dict = kwargs["state_data"]
|
||||
|
||||
_edit_prompt = edit_prompt
|
||||
|
||||
# type_ = field_descriptor.type_
|
||||
# type_origin = get_origin(type_)
|
||||
# if type_origin == UnionType:
|
||||
# type_ = get_args(type_)[0]
|
||||
context_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,
|
||||
)
|
||||
|
||||
if field_descriptor.type_base == str and field_descriptor.localizable:
|
||||
|
||||
if field_descriptor.type_base is str and field_descriptor.localizable:
|
||||
current_locale = list(LanguageBase.all_members.values())[locale_index]
|
||||
context_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,
|
||||
save_state = True)
|
||||
|
||||
_edit_prompt = f"{edit_prompt}\n{(await Settings.get(
|
||||
Settings.APP_STRINGS_STRING_EDITOR_LOCALE_TEMPLATE_P_NAME)).format(name = current_locale)}"
|
||||
_current_value = get_local_text(current_value, current_locale) if current_value else None
|
||||
|
||||
state_data.update({
|
||||
"context_data": context_data.pack(),
|
||||
"edit_prompt": edit_prompt,
|
||||
"locale_index": str(locale_index),
|
||||
"current_value": current_value})
|
||||
|
||||
else:
|
||||
context_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,
|
||||
save_state = True)
|
||||
|
||||
_edit_prompt = f"{edit_prompt}\n{
|
||||
(
|
||||
await Settings.get(
|
||||
Settings.APP_STRINGS_STRING_EDITOR_LOCALE_TEMPLATE_P_NAME
|
||||
)
|
||||
).format(name=current_locale)
|
||||
}"
|
||||
_current_value = (
|
||||
get_local_text(current_value, current_locale) if current_value else None
|
||||
)
|
||||
|
||||
state_data.update(
|
||||
{
|
||||
"context_data": context_data.pack(),
|
||||
"edit_prompt": edit_prompt,
|
||||
"locale_index": str(locale_index),
|
||||
"current_value": current_value,
|
||||
}
|
||||
)
|
||||
|
||||
else:
|
||||
_current_value = serialize(current_value, field_descriptor)
|
||||
|
||||
state_data.update({
|
||||
"context_data": context_data.pack()})
|
||||
|
||||
if _current_value:
|
||||
state_data.update({"context_data": context_data.pack()})
|
||||
|
||||
_current_value_caption = f"{_current_value[:30]}..." if len(_current_value) > 30 else _current_value
|
||||
keyboard_builder.row(InlineKeyboardButton(text = _current_value_caption,
|
||||
copy_text = CopyTextButton(text = _current_value)))
|
||||
if _current_value:
|
||||
_current_value_caption = (
|
||||
f"{_current_value[:30]}..." if len(_current_value) > 30 else _current_value
|
||||
)
|
||||
keyboard_builder.row(
|
||||
InlineKeyboardButton(
|
||||
text=_current_value_caption,
|
||||
copy_text=CopyTextButton(text=_current_value),
|
||||
)
|
||||
)
|
||||
|
||||
state_data = kwargs["state_data"]
|
||||
|
||||
await wrap_editor(keyboard_builder = keyboard_builder,
|
||||
field_descriptor = field_descriptor,
|
||||
callback_data = callback_data,
|
||||
state_data = state_data)
|
||||
await wrap_editor(
|
||||
keyboard_builder=keyboard_builder,
|
||||
field_descriptor=field_descriptor,
|
||||
callback_data=callback_data,
|
||||
state_data=state_data,
|
||||
)
|
||||
|
||||
await state.set_data(state_data)
|
||||
|
||||
send_message = get_send_message(message)
|
||||
await send_message(text = _edit_prompt, reply_markup = keyboard_builder.as_markup())
|
||||
|
||||
# async def context_command_fiter(*args, **kwargs):
|
||||
# print(args, kwargs)
|
||||
# return True
|
||||
|
||||
send_message = get_send_message(message)
|
||||
await send_message(text=_edit_prompt, reply_markup=keyboard_builder.as_markup())
|
||||
|
||||
93
bot/handlers/editors/wrapper.py
Normal file
93
bot/handlers/editors/wrapper.py
Normal file
@@ -0,0 +1,93 @@
|
||||
from aiogram.types import InlineKeyboardButton
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from ....model.settings import Settings
|
||||
from ....model.descriptors import EntityFieldDescriptor
|
||||
from ..context import ContextData, CallbackCommand, CommandContext
|
||||
from ..navigation import get_navigation_context, pop_navigation_context
|
||||
|
||||
|
||||
async def wrap_editor(
|
||||
keyboard_builder: InlineKeyboardBuilder,
|
||||
field_descriptor: EntityFieldDescriptor,
|
||||
callback_data: ContextData,
|
||||
state_data: dict,
|
||||
):
|
||||
if callback_data.context in [
|
||||
CommandContext.ENTITY_CREATE,
|
||||
CommandContext.ENTITY_EDIT,
|
||||
CommandContext.ENTITY_FIELD_EDIT,
|
||||
]:
|
||||
form_name = (
|
||||
callback_data.form_params.split("&")[0]
|
||||
if callback_data.form_params
|
||||
else "default"
|
||||
)
|
||||
form = field_descriptor.entity_descriptor.forms.get(
|
||||
form_name, field_descriptor.entity_descriptor.default_form
|
||||
)
|
||||
|
||||
btns = []
|
||||
field_index = (
|
||||
form.edit_field_sequence.index(field_descriptor.name)
|
||||
if callback_data.context
|
||||
in [CommandContext.ENTITY_CREATE, CommandContext.ENTITY_EDIT]
|
||||
else 0
|
||||
)
|
||||
|
||||
stack, context = get_navigation_context(state_data=state_data)
|
||||
context = pop_navigation_context(stack)
|
||||
|
||||
if field_index > 0:
|
||||
btns.append(
|
||||
InlineKeyboardButton(
|
||||
text=(await Settings.get(Settings.APP_STRINGS_BACK_BTN)),
|
||||
callback_data=ContextData(
|
||||
command=CallbackCommand.FIELD_EDITOR,
|
||||
context=callback_data.context,
|
||||
entity_name=callback_data.entity_name,
|
||||
entity_id=callback_data.entity_id,
|
||||
form_params=callback_data.form_params,
|
||||
field_name=form.edit_field_sequence[field_index - 1],
|
||||
).pack(),
|
||||
)
|
||||
)
|
||||
|
||||
if field_descriptor.is_optional:
|
||||
btns.append(
|
||||
InlineKeyboardButton(
|
||||
text=(await Settings.get(Settings.APP_STRINGS_SKIP_BTN)),
|
||||
callback_data=ContextData(
|
||||
command=CallbackCommand.FIELD_EDITOR_CALLBACK,
|
||||
context=callback_data.context,
|
||||
entity_name=callback_data.entity_name,
|
||||
entity_id=callback_data.entity_id,
|
||||
form_params=callback_data.form_params,
|
||||
field_name=callback_data.field_name,
|
||||
data="skip",
|
||||
).pack(),
|
||||
)
|
||||
)
|
||||
|
||||
keyboard_builder.row(*btns)
|
||||
|
||||
keyboard_builder.row(
|
||||
InlineKeyboardButton(
|
||||
text=(await Settings.get(Settings.APP_STRINGS_CANCEL_BTN)),
|
||||
callback_data=context.pack(),
|
||||
)
|
||||
)
|
||||
|
||||
elif callback_data.context == CommandContext.SETTING_EDIT:
|
||||
keyboard_builder.row(
|
||||
InlineKeyboardButton(
|
||||
text=(await Settings.get(Settings.APP_STRINGS_CANCEL_BTN)),
|
||||
callback_data=ContextData(
|
||||
command=CallbackCommand.FIELD_EDITOR_CALLBACK,
|
||||
context=callback_data.context,
|
||||
field_name=callback_data.field_name,
|
||||
form_params=callback_data.form_params,
|
||||
data="cancel",
|
||||
).pack(),
|
||||
)
|
||||
)
|
||||
@@ -1,24 +1,28 @@
|
||||
from typing import get_args, get_origin, TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING
|
||||
from aiogram import Router, F
|
||||
from aiogram.filters import Command
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import StatesGroup, State
|
||||
from aiogram.types import Message, CallbackQuery, InlineKeyboardButton
|
||||
from aiogram.utils.i18n import I18n
|
||||
from aiogram.types import CallbackQuery, InlineKeyboardButton
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from logging import getLogger
|
||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
|
||||
from ....model.bot_entity import BotEntity
|
||||
from ....model.bot_enum import BotEnum
|
||||
from ....model.owned_bot_entity import OwnedBotEntity
|
||||
from ....model.descriptors import FieldEditButton, CommandButton
|
||||
from ....model.settings import Settings
|
||||
from ....model.user import UserBase
|
||||
from ....model.descriptors import EntityFieldDescriptor, EntityDescriptor
|
||||
from ....model import EntityPermission
|
||||
from ....utils import serialize, deserialize, get_user_permissions
|
||||
from ....utils.main import (
|
||||
check_entity_permission,
|
||||
get_send_message,
|
||||
clear_state,
|
||||
get_value_repr,
|
||||
get_callable_str,
|
||||
get_entity_descriptor,
|
||||
)
|
||||
from ..context import ContextData, CallbackCommand, CommandContext
|
||||
from ..common import get_send_message, get_local_text, get_entity_descriptor, get_callable_str, get_value_repr
|
||||
from ..navigation import (
|
||||
pop_navigation_context,
|
||||
save_navigation_context,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ....main import QBotApp
|
||||
@@ -30,204 +34,212 @@ router = Router()
|
||||
|
||||
@router.callback_query(ContextData.filter(F.command == CallbackCommand.ENTITY_ITEM))
|
||||
async def entity_item_callback(query: CallbackQuery, **kwargs):
|
||||
|
||||
callback_data: ContextData = kwargs["callback_data"]
|
||||
state: FSMContext = kwargs["state"]
|
||||
state_data = await state.get_data()
|
||||
kwargs["state_data"] = state_data
|
||||
|
||||
clear_state(state_data = state_data)
|
||||
stack = save_navigation_context(callback_data = callback_data, state_data = state_data)
|
||||
|
||||
await entity_item(query = query, navigation_stack = stack, **kwargs)
|
||||
clear_state(state_data=state_data)
|
||||
stack = save_navigation_context(callback_data=callback_data, state_data=state_data)
|
||||
|
||||
await entity_item(query=query, navigation_stack=stack, **kwargs)
|
||||
|
||||
|
||||
async def entity_item(query: CallbackQuery,
|
||||
callback_data: ContextData,
|
||||
db_session: AsyncSession,
|
||||
user: UserBase,
|
||||
app: "QBotApp",
|
||||
navigation_stack: list[ContextData],
|
||||
**kwargs):
|
||||
|
||||
async def entity_item(
|
||||
query: CallbackQuery,
|
||||
callback_data: ContextData,
|
||||
db_session: AsyncSession,
|
||||
user: UserBase,
|
||||
app: "QBotApp",
|
||||
navigation_stack: list[ContextData],
|
||||
**kwargs,
|
||||
):
|
||||
entity_descriptor = get_entity_descriptor(app, callback_data)
|
||||
user_permissions = get_user_permissions(user, entity_descriptor)
|
||||
entity_type: BotEntity = entity_descriptor.type_
|
||||
# user_permissions = get_user_permissions(user, entity_descriptor)
|
||||
entity_type = entity_descriptor.type_
|
||||
|
||||
keyboard_builder = InlineKeyboardBuilder()
|
||||
|
||||
entity_item = await entity_type.get(session = db_session, id = callback_data.entity_id)
|
||||
entity_item = await entity_type.get(session=db_session, id=callback_data.entity_id)
|
||||
|
||||
if not entity_item:
|
||||
return await query.answer(text = (await Settings.get(Settings.APP_STRINGS_NOT_FOUND)))
|
||||
return await query.answer(
|
||||
text=(await Settings.get(Settings.APP_STRINGS_NOT_FOUND))
|
||||
)
|
||||
|
||||
is_owned = issubclass(entity_type, OwnedBotEntity)
|
||||
# is_owned = issubclass(entity_type, OwnedBotEntity)
|
||||
|
||||
if (EntityPermission.READ not in user_permissions and
|
||||
EntityPermission.READ_ALL not in user_permissions):
|
||||
if not check_entity_permission(
|
||||
entity=entity_item, user=user, permission=EntityPermission.READ
|
||||
):
|
||||
return await query.answer(
|
||||
text=(await Settings.get(Settings.APP_STRINGS_FORBIDDEN))
|
||||
)
|
||||
|
||||
return await query.answer(text = (await Settings.get(Settings.APP_STRINGS_FORBIDDEN)))
|
||||
can_edit = check_entity_permission(
|
||||
entity=entity_item, user=user, permission=EntityPermission.UPDATE
|
||||
)
|
||||
|
||||
if (is_owned and
|
||||
EntityPermission.READ_ALL not in user_permissions and
|
||||
entity_item.user_id != user.id):
|
||||
|
||||
return await query.answer(text = (await Settings.get(Settings.APP_STRINGS_FORBIDDEN)))
|
||||
|
||||
can_edit = (EntityPermission.UPDATE_ALL in user_permissions or
|
||||
(EntityPermission.UPDATE in user_permissions and not is_owned) or
|
||||
(EntityPermission.UPDATE in user_permissions and is_owned and
|
||||
entity_item.user_id == user.id))
|
||||
|
||||
can_delete = (EntityPermission.DELETE_ALL in user_permissions or
|
||||
(EntityPermission.DELETE in user_permissions and not is_owned) or
|
||||
(EntityPermission.DELETE in user_permissions and is_owned and
|
||||
entity_item.user_id == user.id))
|
||||
form = entity_descriptor.forms.get(
|
||||
callback_data.form_params or "default", entity_descriptor.default_form
|
||||
)
|
||||
|
||||
if can_edit:
|
||||
for edit_buttons_row in entity_descriptor.edit_buttons:
|
||||
for edit_buttons_row in form.form_buttons:
|
||||
btn_row = []
|
||||
for field_name in edit_buttons_row:
|
||||
field_name, btn_caption = field_name if isinstance(field_name, tuple) else (field_name, None)
|
||||
if field_name in entity_descriptor.fields_descriptors:
|
||||
field_descriptor = entity_descriptor.fields_descriptors[field_name]
|
||||
# if field_descriptor.is_list and issubclass(field_descriptor.type_base, BotEntity):
|
||||
# await field_descriptor.type_base.
|
||||
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 == 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}"
|
||||
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
|
||||
)
|
||||
else:
|
||||
btn_text = (f"✏️ {get_callable_str(field_descriptor.caption, field_descriptor, entity_item, field_value)}"
|
||||
if field_descriptor.caption else f"✏️ {field_name}")
|
||||
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:
|
||||
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(),
|
||||
)
|
||||
)
|
||||
|
||||
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 = 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()))
|
||||
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()
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
if btn_row:
|
||||
keyboard_builder.row(*btn_row)
|
||||
|
||||
edit_delete_row = []
|
||||
if can_edit and entity_descriptor.edit_button_visible:
|
||||
if can_edit and form.show_edit_button:
|
||||
edit_delete_row.append(
|
||||
InlineKeyboardButton(
|
||||
text = (await Settings.get(Settings.APP_STRINGS_EDIT_BTN)),
|
||||
callback_data = ContextData(
|
||||
command = CallbackCommand.FIELD_EDITOR,
|
||||
context = CommandContext.ENTITY_EDIT,
|
||||
entity_name = entity_descriptor.name,
|
||||
entity_id = str(entity_item.id),
|
||||
field_name = entity_descriptor.field_sequence[0]).pack()))
|
||||
|
||||
if can_delete:
|
||||
text=(await Settings.get(Settings.APP_STRINGS_EDIT_BTN)),
|
||||
callback_data=ContextData(
|
||||
command=CallbackCommand.FIELD_EDITOR,
|
||||
context=CommandContext.ENTITY_EDIT,
|
||||
entity_name=entity_descriptor.name,
|
||||
entity_id=str(entity_item.id),
|
||||
form_params=callback_data.form_params,
|
||||
field_name=form.edit_field_sequence[0],
|
||||
).pack(),
|
||||
)
|
||||
)
|
||||
|
||||
if (
|
||||
check_entity_permission(
|
||||
entity=entity_item, user=user, permission=EntityPermission.DELETE
|
||||
)
|
||||
and form.show_delete_button
|
||||
):
|
||||
edit_delete_row.append(
|
||||
InlineKeyboardButton(
|
||||
text = (await Settings.get(Settings.APP_STRINGS_DELETE_BTN)),
|
||||
callback_data = ContextData(
|
||||
command = CallbackCommand.ENTITY_DELETE,
|
||||
entity_name = entity_descriptor.name,
|
||||
entity_id = str(entity_item.id)).pack()))
|
||||
|
||||
text=(await Settings.get(Settings.APP_STRINGS_DELETE_BTN)),
|
||||
callback_data=ContextData(
|
||||
command=CallbackCommand.ENTITY_DELETE,
|
||||
entity_name=entity_descriptor.name,
|
||||
form_params=callback_data.form_params,
|
||||
entity_id=str(entity_item.id),
|
||||
).pack(),
|
||||
)
|
||||
)
|
||||
|
||||
if edit_delete_row:
|
||||
keyboard_builder.row(*edit_delete_row)
|
||||
|
||||
entity_caption = get_callable_str(entity_descriptor.caption, entity_descriptor, entity_item)
|
||||
entity_item_name = get_local_text(entity_item.name, user.lang) if entity_descriptor.fields_descriptors["name"].localizable else entity_item.name
|
||||
if form.item_repr:
|
||||
item_text = form.item_repr(entity_descriptor, entity_item)
|
||||
else:
|
||||
entity_caption = (
|
||||
get_callable_str(
|
||||
entity_descriptor.full_name, entity_descriptor, entity_item
|
||||
)
|
||||
if entity_descriptor.full_name
|
||||
else entity_descriptor.name
|
||||
)
|
||||
|
||||
item_text = f"<b><u><i>{entity_caption or entity_descriptor.name}:</i></u></b> <b>{entity_item_name}</b>"
|
||||
entity_item_repr = (
|
||||
get_callable_str(
|
||||
entity_descriptor.item_repr, entity_descriptor, entity_item
|
||||
)
|
||||
if entity_descriptor.item_repr
|
||||
else str(entity_item.id)
|
||||
)
|
||||
|
||||
item_text = f"<b><u><i>{entity_caption or entity_descriptor.name}:</i></u></b> <b>{entity_item_repr}</b>"
|
||||
|
||||
for field_descriptor in entity_descriptor.fields_descriptors.values():
|
||||
if field_descriptor.is_visible:
|
||||
field_caption = get_callable_str(
|
||||
field_descriptor.caption, field_descriptor, entity_item
|
||||
)
|
||||
value = get_value_repr(
|
||||
value=getattr(entity_item, field_descriptor.name),
|
||||
field_descriptor=field_descriptor,
|
||||
locale=user.lang,
|
||||
)
|
||||
item_text += f"\n{field_caption or field_descriptor.name}:{f' <b>{value}</b>' if value else ''}"
|
||||
|
||||
for field_descriptor in entity_descriptor.fields_descriptors.values():
|
||||
if field_descriptor.name == "name" or not field_descriptor.is_visible:
|
||||
continue
|
||||
field_caption = get_callable_str(field_descriptor.caption, field_descriptor, entity_item)
|
||||
value = get_value_repr(value = getattr(entity_item, field_descriptor.name),
|
||||
field_descriptor = field_descriptor,
|
||||
locale = user.lang)
|
||||
item_text += f"\n{field_caption or field_descriptor.name}:{f" <b>{value}</b>" if value else ""}"
|
||||
|
||||
context = pop_navigation_context(navigation_stack)
|
||||
if context:
|
||||
keyboard_builder.row(
|
||||
InlineKeyboardButton(
|
||||
text = (await Settings.get(Settings.APP_STRINGS_BACK_BTN)),
|
||||
callback_data = context.pack()))
|
||||
|
||||
text=(await Settings.get(Settings.APP_STRINGS_BACK_BTN)),
|
||||
callback_data=context.pack(),
|
||||
)
|
||||
)
|
||||
|
||||
state: FSMContext = kwargs["state"]
|
||||
state_data = kwargs["state_data"]
|
||||
await state.set_data(state_data)
|
||||
|
||||
send_message = get_send_message(query)
|
||||
|
||||
await send_message(text = item_text, reply_markup = keyboard_builder.as_markup())
|
||||
|
||||
|
||||
@router.callback_query(ContextData.filter(F.command == CallbackCommand.ENTITY_DELETE))
|
||||
async def entity_delete_callback(query: CallbackQuery, **kwargs):
|
||||
|
||||
callback_data: ContextData = kwargs["callback_data"]
|
||||
user: UserBase = kwargs["user"]
|
||||
db_session: AsyncSession = kwargs["db_session"]
|
||||
app: "QBotApp" = kwargs["app"]
|
||||
|
||||
entity_descriptor = get_entity_descriptor(app = app, callback_data = callback_data)
|
||||
user_permissions = get_user_permissions(user, entity_descriptor)
|
||||
|
||||
entity = await entity_descriptor.type_.get(session = db_session, id = int(callback_data.entity_id))
|
||||
|
||||
if not (EntityPermission.DELETE_ALL in user_permissions or
|
||||
(EntityPermission.DELETE in user_permissions and not issubclass(entity_descriptor.type_, OwnedBotEntity)) or
|
||||
(EntityPermission.DELETE in user_permissions and issubclass(entity_descriptor.type_, OwnedBotEntity) and
|
||||
entity.user_id == user.id)):
|
||||
|
||||
return await query.answer(text = (await Settings.get(Settings.APP_STRINGS_FORBIDDEN)))
|
||||
|
||||
if callback_data.data == "yes":
|
||||
|
||||
await entity_descriptor.type_.remove(
|
||||
session = db_session, id = int(callback_data.entity_id), commit = True)
|
||||
|
||||
await route_callback(message = query, **kwargs)
|
||||
|
||||
elif callback_data.data == "no":
|
||||
await route_callback(message = query, back = False, **kwargs)
|
||||
|
||||
elif not callback_data.data:
|
||||
|
||||
field_descriptor = entity_descriptor.fields_descriptors["name"]
|
||||
|
||||
entity = await entity_descriptor.type_.get(session = db_session, id = int(callback_data.entity_id))
|
||||
|
||||
return await query.message.edit_text(
|
||||
text = (await Settings.get(Settings.APP_STRINGS_CONFIRM_DELETE_P_NAME)).format(
|
||||
name = get_value_repr(
|
||||
value = getattr(entity, field_descriptor.name),
|
||||
field_descriptor = field_descriptor,
|
||||
locale = user.lang)),
|
||||
reply_markup = InlineKeyboardBuilder().row(
|
||||
InlineKeyboardButton(
|
||||
text = (await Settings.get(Settings.APP_STRINGS_YES_BTN)),
|
||||
callback_data = ContextData(
|
||||
command = CallbackCommand.ENTITY_DELETE,
|
||||
entity_name = callback_data.entity_name,
|
||||
entity_id = callback_data.entity_id,
|
||||
data = "yes").pack()),
|
||||
InlineKeyboardButton(
|
||||
text = (await Settings.get(Settings.APP_STRINGS_NO_BTN)),
|
||||
callback_data = ContextData(
|
||||
command = CallbackCommand.ENTITY_ITEM,
|
||||
entity_name = callback_data.entity_name,
|
||||
entity_id = callback_data.entity_id,
|
||||
data = "no").pack())).as_markup())
|
||||
|
||||
|
||||
|
||||
from ..navigation import pop_navigation_context, save_navigation_context, clear_state, route_callback
|
||||
await send_message(text=item_text, reply_markup=keyboard_builder.as_markup())
|
||||
|
||||
96
bot/handlers/forms/entity_form_callbacks.py
Normal file
96
bot/handlers/forms/entity_form_callbacks.py
Normal file
@@ -0,0 +1,96 @@
|
||||
from aiogram import Router, F
|
||||
from aiogram.types import CallbackQuery, InlineKeyboardButton
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ..context import ContextData, CallbackCommand
|
||||
from ....model.user import UserBase
|
||||
from ....model.settings import Settings
|
||||
from ....model import EntityPermission
|
||||
from ....utils.main import (
|
||||
check_entity_permission,
|
||||
get_value_repr,
|
||||
get_entity_descriptor,
|
||||
)
|
||||
from ..common.routing import route_callback
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ....main import QBotApp
|
||||
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
@router.callback_query(ContextData.filter(F.command == CallbackCommand.ENTITY_DELETE))
|
||||
async def entity_delete_callback(query: CallbackQuery, **kwargs):
|
||||
callback_data: ContextData = kwargs["callback_data"]
|
||||
user: UserBase = kwargs["user"]
|
||||
db_session: AsyncSession = kwargs["db_session"]
|
||||
app: "QBotApp" = kwargs["app"]
|
||||
|
||||
entity_descriptor = get_entity_descriptor(app=app, callback_data=callback_data)
|
||||
|
||||
entity = await entity_descriptor.type_.get(
|
||||
session=db_session, id=int(callback_data.entity_id)
|
||||
)
|
||||
|
||||
if not check_entity_permission(
|
||||
entity=entity, user=user, permission=EntityPermission.DELETE
|
||||
):
|
||||
return await query.answer(
|
||||
text=(await Settings.get(Settings.APP_STRINGS_FORBIDDEN))
|
||||
)
|
||||
|
||||
if callback_data.data == "yes":
|
||||
await entity_descriptor.type_.remove(
|
||||
session=db_session, id=int(callback_data.entity_id), commit=True
|
||||
)
|
||||
|
||||
await route_callback(message=query, **kwargs)
|
||||
|
||||
elif callback_data.data == "no":
|
||||
await route_callback(message=query, back=False, **kwargs)
|
||||
|
||||
elif not callback_data.data:
|
||||
field_descriptor = entity_descriptor.fields_descriptors["name"]
|
||||
|
||||
entity = await entity_descriptor.type_.get(
|
||||
session=db_session, id=int(callback_data.entity_id)
|
||||
)
|
||||
|
||||
return await query.message.edit_text(
|
||||
text=(
|
||||
await Settings.get(Settings.APP_STRINGS_CONFIRM_DELETE_P_NAME)
|
||||
).format(
|
||||
name=get_value_repr(
|
||||
value=getattr(entity, field_descriptor.name),
|
||||
field_descriptor=field_descriptor,
|
||||
locale=user.lang,
|
||||
)
|
||||
),
|
||||
reply_markup=InlineKeyboardBuilder()
|
||||
.row(
|
||||
InlineKeyboardButton(
|
||||
text=(await Settings.get(Settings.APP_STRINGS_YES_BTN)),
|
||||
callback_data=ContextData(
|
||||
command=CallbackCommand.ENTITY_DELETE,
|
||||
entity_name=callback_data.entity_name,
|
||||
entity_id=callback_data.entity_id,
|
||||
form_params=callback_data.form_params,
|
||||
data="yes",
|
||||
).pack(),
|
||||
),
|
||||
InlineKeyboardButton(
|
||||
text=(await Settings.get(Settings.APP_STRINGS_NO_BTN)),
|
||||
callback_data=ContextData(
|
||||
command=CallbackCommand.ENTITY_ITEM,
|
||||
entity_name=callback_data.entity_name,
|
||||
entity_id=callback_data.entity_id,
|
||||
form_params=callback_data.form_params,
|
||||
data="no",
|
||||
).pack(),
|
||||
),
|
||||
)
|
||||
.as_markup(),
|
||||
)
|
||||
@@ -1,26 +1,29 @@
|
||||
from typing import get_args, get_origin, TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING
|
||||
from aiogram import Router, F
|
||||
from aiogram.filters import Command
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import StatesGroup, State
|
||||
from aiogram.types import Message, CallbackQuery, InlineKeyboardButton
|
||||
from aiogram.utils.i18n import I18n
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from logging import getLogger
|
||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
|
||||
from ....model.bot_entity import BotEntity
|
||||
from ....model.bot_enum import BotEnum
|
||||
from ....model.owned_bot_entity import OwnedBotEntity
|
||||
from ....model.settings import Settings
|
||||
from ....model.user import UserBase
|
||||
from ....model.view_setting import ViewSetting
|
||||
from ....model.descriptors import EntityFieldDescriptor, EntityDescriptor
|
||||
from ....model.descriptors import EntityDescriptor, Filter
|
||||
from ....model import EntityPermission
|
||||
from ....utils import serialize, deserialize, get_user_permissions
|
||||
from ....utils.main import (
|
||||
get_user_permissions,
|
||||
get_send_message,
|
||||
clear_state,
|
||||
get_entity_descriptor,
|
||||
get_callable_str,
|
||||
)
|
||||
from ....utils.serialization import deserialize
|
||||
from ..context import ContextData, CallbackCommand, CommandContext
|
||||
from ..common import (add_pagination_controls, get_local_text, get_entity_descriptor,
|
||||
get_callable_str, get_send_message, add_filter_controls)
|
||||
from ..common.pagination import add_pagination_controls
|
||||
from ..common.filtering import add_filter_controls
|
||||
from ..navigation import pop_navigation_context, save_navigation_context
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ....main import QBotApp
|
||||
@@ -32,135 +35,226 @@ router = Router()
|
||||
|
||||
@router.callback_query(ContextData.filter(F.command == CallbackCommand.ENTITY_LIST))
|
||||
async def entity_list_callback(query: CallbackQuery, **kwargs):
|
||||
|
||||
callback_data: ContextData = kwargs["callback_data"]
|
||||
|
||||
if callback_data.data == "skip":
|
||||
return
|
||||
|
||||
|
||||
state: FSMContext = kwargs["state"]
|
||||
state_data = await state.get_data()
|
||||
kwargs["state_data"] = state_data
|
||||
|
||||
clear_state(state_data = state_data)
|
||||
stack = save_navigation_context(callback_data = callback_data, state_data = state_data)
|
||||
|
||||
await entity_list(message = query, navigation_stack = stack, **kwargs)
|
||||
clear_state(state_data=state_data)
|
||||
stack = save_navigation_context(callback_data=callback_data, state_data=state_data)
|
||||
|
||||
await entity_list(message=query, navigation_stack=stack, **kwargs)
|
||||
|
||||
|
||||
def calc_total_pages(items_count: int, page_size: int) -> int:
|
||||
return max(items_count // page_size + (1 if items_count % page_size else 0), 1)
|
||||
return max(items_count // page_size + (1 if items_count % page_size else 0), 1)
|
||||
|
||||
async def entity_list(message: CallbackQuery | Message,
|
||||
callback_data: ContextData,
|
||||
db_session: AsyncSession,
|
||||
user: UserBase,
|
||||
app: "QBotApp",
|
||||
navigation_stack: list[ContextData],
|
||||
**kwargs):
|
||||
|
||||
|
||||
async def _prepare_static_filter(
|
||||
db_session: AsyncSession,
|
||||
entity_descriptor: EntityDescriptor,
|
||||
static_filters: list[Filter],
|
||||
params: list[str],
|
||||
) -> list[Filter]:
|
||||
return (
|
||||
[
|
||||
Filter(
|
||||
field_name=f.field_name,
|
||||
operator=f.operator,
|
||||
value_type="const",
|
||||
value=(
|
||||
f.value
|
||||
if f.value_type == "const"
|
||||
else await deserialize(
|
||||
session=db_session,
|
||||
type_=entity_descriptor.fields_descriptors[
|
||||
f.field_name
|
||||
].type_base,
|
||||
value=params[f.param_index],
|
||||
)
|
||||
),
|
||||
)
|
||||
for f in static_filters
|
||||
]
|
||||
if static_filters
|
||||
else None
|
||||
)
|
||||
|
||||
|
||||
async def entity_list(
|
||||
message: CallbackQuery | Message,
|
||||
callback_data: ContextData,
|
||||
db_session: AsyncSession,
|
||||
user: UserBase,
|
||||
app: "QBotApp",
|
||||
navigation_stack: list[ContextData],
|
||||
**kwargs,
|
||||
):
|
||||
page = int(callback_data.data or "1")
|
||||
|
||||
entity_descriptor = get_entity_descriptor(app, callback_data)
|
||||
user_permissions = get_user_permissions(user, entity_descriptor)
|
||||
entity_type = entity_descriptor.type_
|
||||
form_params = (
|
||||
callback_data.form_params.split("&") if callback_data.form_params else []
|
||||
)
|
||||
form_name = form_params.pop(0) if form_params else "default"
|
||||
form_list = entity_descriptor.lists.get(
|
||||
form_name or "default", entity_descriptor.default_list
|
||||
)
|
||||
form_item = entity_descriptor.forms.get(
|
||||
form_list.item_form or "default", entity_descriptor.default_form
|
||||
)
|
||||
|
||||
keyboard_builder = InlineKeyboardBuilder()
|
||||
|
||||
if EntityPermission.CREATE in user_permissions or EntityPermission.CREATE_ALL in user_permissions:
|
||||
if (
|
||||
EntityPermission.CREATE in user_permissions
|
||||
or EntityPermission.CREATE_ALL in user_permissions
|
||||
) and form_list.show_add_new_button:
|
||||
keyboard_builder.row(
|
||||
InlineKeyboardButton(
|
||||
text = (await Settings.get(Settings.APP_STRINGS_ADD_BTN)),
|
||||
callback_data = ContextData(
|
||||
command = CallbackCommand.FIELD_EDITOR,
|
||||
context = CommandContext.ENTITY_CREATE,
|
||||
entity_name = entity_descriptor.name,
|
||||
field_name = entity_descriptor.field_sequence[0],
|
||||
save_state = True).pack()))
|
||||
text=(await Settings.get(Settings.APP_STRINGS_ADD_BTN)),
|
||||
callback_data=ContextData(
|
||||
command=CallbackCommand.FIELD_EDITOR,
|
||||
context=CommandContext.ENTITY_CREATE,
|
||||
entity_name=entity_descriptor.name,
|
||||
field_name=form_item.edit_field_sequence[0],
|
||||
form_params=form_list.item_form,
|
||||
).pack(),
|
||||
)
|
||||
)
|
||||
|
||||
page_size = await Settings.get(Settings.PAGE_SIZE)
|
||||
|
||||
entity_filter = await ViewSetting.get_filter(session = db_session, user_id = user.id, entity_name = entity_descriptor.class_name)
|
||||
|
||||
if issubclass(entity_type, OwnedBotEntity):
|
||||
if EntityPermission.READ_ALL in user_permissions or EntityPermission.LIST_ALL in user_permissions:
|
||||
items_count = await entity_type.get_count(session = db_session, filter = entity_filter)
|
||||
total_pages = calc_total_pages(items_count, page_size)
|
||||
page = min(page, total_pages)
|
||||
items = await entity_type.get_multi(
|
||||
session = db_session, order_by = entity_type.name, filter = entity_filter,
|
||||
skip = page_size * (page - 1), limit = page_size)
|
||||
elif EntityPermission.READ in user_permissions or EntityPermission.LIST in user_permissions:
|
||||
items_count = await entity_type.get_count_by_user(session = db_session, user_id = user.id, filter = entity_filter)
|
||||
total_pages = calc_total_pages(items_count, page_size)
|
||||
page = min(page, total_pages)
|
||||
items = await entity_type.get_multi_by_user(
|
||||
session = db_session, user_id = user.id, order_by = entity_type.name, filter = entity_filter,
|
||||
skip = page_size * (page - 1), limit = page_size)
|
||||
else:
|
||||
items = list[OwnedBotEntity]()
|
||||
items_count = 0
|
||||
total_pages = 1
|
||||
page = 1
|
||||
elif issubclass(entity_type, BotEntity):
|
||||
if (EntityPermission.READ in user_permissions or EntityPermission.LIST in user_permissions or
|
||||
EntityPermission.READ_ALL in user_permissions or EntityPermission.LIST_ALL in user_permissions):
|
||||
items_count = await entity_type.get_count(session = db_session, filter = entity_filter)
|
||||
total_pages = calc_total_pages(items_count, page_size)
|
||||
page = min(page, total_pages)
|
||||
items = await entity_type.get_multi(
|
||||
session = db_session, order_by = entity_type.name, filter = entity_filter,
|
||||
skip = page_size * (page - 1), limit = page_size)
|
||||
|
||||
else:
|
||||
items = list[BotEntity]()
|
||||
total_pages = 1
|
||||
page = 1
|
||||
items_count = 0
|
||||
if form_list.filtering:
|
||||
entity_filter = await ViewSetting.get_filter(
|
||||
session=db_session,
|
||||
user_id=user.id,
|
||||
entity_name=entity_descriptor.class_name,
|
||||
)
|
||||
else:
|
||||
raise ValueError(f"Unsupported entity type: {entity_type}")
|
||||
|
||||
|
||||
entity_filter = None
|
||||
|
||||
list_all = (
|
||||
EntityPermission.LIST_ALL in user_permissions
|
||||
or EntityPermission.READ_ALL in user_permissions
|
||||
)
|
||||
if (
|
||||
list_all
|
||||
or EntityPermission.LIST in user_permissions
|
||||
or EntityPermission.READ in user_permissions
|
||||
):
|
||||
if form_list.pagination:
|
||||
page_size = await Settings.get(Settings.PAGE_SIZE)
|
||||
items_count = await entity_type.get_count(
|
||||
session=db_session,
|
||||
static_filter=await _prepare_static_filter(
|
||||
db_session=db_session,
|
||||
entity_descriptor=entity_descriptor,
|
||||
static_filters=form_list.static_filters,
|
||||
params=form_params,
|
||||
),
|
||||
filter=entity_filter,
|
||||
filter_fields=form_list.filtering_fields,
|
||||
user=user if not list_all else None,
|
||||
)
|
||||
total_pages = calc_total_pages(items_count, page_size)
|
||||
page = min(page, total_pages)
|
||||
skip = page_size * (page - 1)
|
||||
limit = page_size
|
||||
else:
|
||||
skip = 0
|
||||
limit = None
|
||||
|
||||
items = await entity_type.get_multi(
|
||||
session=db_session,
|
||||
order_by=form_list.order_by,
|
||||
static_filter=await _prepare_static_filter(
|
||||
db_session=db_session,
|
||||
entity_descriptor=entity_descriptor,
|
||||
static_filters=form_list.static_filters,
|
||||
params=form_params,
|
||||
),
|
||||
filter=entity_filter,
|
||||
filter_fields=form_list.filtering_fields,
|
||||
user=user if not list_all else None,
|
||||
skip=skip,
|
||||
limit=limit,
|
||||
)
|
||||
else:
|
||||
items = list[BotEntity]()
|
||||
items_count = 0
|
||||
total_pages = 1
|
||||
page = 1
|
||||
|
||||
for item in items:
|
||||
if entity_descriptor.item_caption:
|
||||
caption = entity_descriptor.item_caption(entity_descriptor, item)
|
||||
elif entity_descriptor.fields_descriptors["name"].localizable:
|
||||
caption = get_local_text(item.name, user.lang)
|
||||
if form_list.item_repr:
|
||||
caption = form_list.item_repr(entity_descriptor, item)
|
||||
elif entity_descriptor.item_repr:
|
||||
caption = entity_descriptor.item_repr(entity_descriptor, item)
|
||||
elif entity_descriptor.full_name:
|
||||
caption = f"{
|
||||
get_callable_str(
|
||||
callable_str=entity_descriptor.full_name,
|
||||
descriptor=entity_descriptor,
|
||||
entity=item,
|
||||
)
|
||||
}: {item.id}"
|
||||
else:
|
||||
caption = item.name
|
||||
caption = f"{entity_descriptor.name}: {item.id}"
|
||||
|
||||
keyboard_builder.row(
|
||||
InlineKeyboardButton(
|
||||
text = caption,
|
||||
callback_data = ContextData(
|
||||
command = CallbackCommand.ENTITY_ITEM,
|
||||
entity_name = entity_descriptor.name,
|
||||
entity_id = str(item.id)).pack()))
|
||||
|
||||
add_pagination_controls(keyboard_builder = keyboard_builder,
|
||||
callback_data = callback_data,
|
||||
total_pages = total_pages,
|
||||
command = CallbackCommand.ENTITY_LIST,
|
||||
page = page)
|
||||
|
||||
add_filter_controls(keyboard_builder = keyboard_builder,
|
||||
entity_descriptor = entity_descriptor,
|
||||
filter = entity_filter)
|
||||
|
||||
text=caption,
|
||||
callback_data=ContextData(
|
||||
command=CallbackCommand.ENTITY_ITEM,
|
||||
entity_name=entity_descriptor.name,
|
||||
form_params=form_list.item_form,
|
||||
entity_id=str(item.id),
|
||||
).pack(),
|
||||
)
|
||||
)
|
||||
|
||||
if form_list.pagination:
|
||||
add_pagination_controls(
|
||||
keyboard_builder=keyboard_builder,
|
||||
callback_data=callback_data,
|
||||
total_pages=total_pages,
|
||||
command=CallbackCommand.ENTITY_LIST,
|
||||
page=page,
|
||||
)
|
||||
|
||||
if form_list.filtering and form_list.filtering_fields:
|
||||
add_filter_controls(
|
||||
keyboard_builder=keyboard_builder,
|
||||
entity_descriptor=entity_descriptor,
|
||||
filter=entity_filter,
|
||||
)
|
||||
|
||||
context = pop_navigation_context(navigation_stack)
|
||||
if context:
|
||||
keyboard_builder.row(
|
||||
InlineKeyboardButton(
|
||||
text = (await Settings.get(Settings.APP_STRINGS_BACK_BTN)),
|
||||
callback_data = context.pack()))
|
||||
|
||||
if entity_descriptor.caption:
|
||||
entity_text = get_callable_str(entity_descriptor.caption_plural, entity_descriptor)
|
||||
text=(await Settings.get(Settings.APP_STRINGS_BACK_BTN)),
|
||||
callback_data=context.pack(),
|
||||
)
|
||||
)
|
||||
|
||||
if form_list.caption:
|
||||
entity_text = get_callable_str(form_list.caption, entity_descriptor)
|
||||
else:
|
||||
entity_text = entity_descriptor.name
|
||||
if entity_descriptor.description:
|
||||
entity_desciption = get_callable_str(entity_descriptor.description, entity_descriptor)
|
||||
else:
|
||||
entity_desciption = None
|
||||
if entity_descriptor.full_name_plural:
|
||||
entity_text = get_callable_str(
|
||||
entity_descriptor.full_name_plural, entity_descriptor
|
||||
)
|
||||
else:
|
||||
entity_text = entity_descriptor.name
|
||||
|
||||
if entity_descriptor.description:
|
||||
entity_text = f"{entity_text} {get_callable_str(entity_descriptor.description, entity_descriptor)}"
|
||||
|
||||
state: FSMContext = kwargs["state"]
|
||||
state_data = kwargs["state_data"]
|
||||
@@ -168,8 +262,4 @@ async def entity_list(message: CallbackQuery | Message,
|
||||
|
||||
send_message = get_send_message(message)
|
||||
|
||||
await send_message(text = f"{entity_text}{f"\n{entity_desciption}" if entity_desciption else ""}",
|
||||
reply_markup = keyboard_builder.as_markup())
|
||||
|
||||
|
||||
from ..navigation import pop_navigation_context, save_navigation_context, clear_state
|
||||
await send_message(text=entity_text, reply_markup=keyboard_builder.as_markup())
|
||||
|
||||
@@ -4,13 +4,12 @@ from aiogram.types import Message, CallbackQuery, InlineKeyboardButton
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from babel.support import LazyProxy
|
||||
from logging import getLogger
|
||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
from typing import TYPE_CHECKING
|
||||
from ....model.settings import Settings
|
||||
from ....model.user import UserBase
|
||||
from ..context import ContextData, CallbackCommand
|
||||
from ..common import get_send_message
|
||||
from ....utils.main import get_send_message
|
||||
from ....model.descriptors import EntityCaptionCallable
|
||||
from ..navigation import save_navigation_context, pop_navigation_context
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ....main import QBotApp
|
||||
@@ -20,55 +19,63 @@ logger = getLogger(__name__)
|
||||
router = Router()
|
||||
|
||||
|
||||
@router.callback_query(ContextData.filter(F.command == CallbackCommand.MENU_ENTRY_ENTITIES))
|
||||
@router.callback_query(
|
||||
ContextData.filter(F.command == CallbackCommand.MENU_ENTRY_ENTITIES)
|
||||
)
|
||||
async def menu_entry_entities(message: CallbackQuery, **kwargs):
|
||||
|
||||
callback_data: ContextData = kwargs["callback_data"]
|
||||
state: FSMContext = kwargs["state"]
|
||||
state_data = await state.get_data()
|
||||
kwargs["state_data"] = state_data
|
||||
|
||||
stack = save_navigation_context(callback_data = callback_data, state_data = state_data)
|
||||
stack = save_navigation_context(callback_data=callback_data, state_data=state_data)
|
||||
|
||||
await entities_menu(message = message, navigation_stack = stack, **kwargs)
|
||||
await entities_menu(message=message, navigation_stack=stack, **kwargs)
|
||||
|
||||
|
||||
async def entities_menu(message: Message | CallbackQuery,
|
||||
app: "QBotApp",
|
||||
state: FSMContext,
|
||||
navigation_stack: list[ContextData],
|
||||
**kwargs):
|
||||
|
||||
async def entities_menu(
|
||||
message: Message | CallbackQuery,
|
||||
app: "QBotApp",
|
||||
state: FSMContext,
|
||||
navigation_stack: list[ContextData],
|
||||
**kwargs,
|
||||
):
|
||||
keyboard_builder = InlineKeyboardBuilder()
|
||||
|
||||
entity_metadata = app.entity_metadata
|
||||
|
||||
for entity in entity_metadata.entity_descriptors.values():
|
||||
if entity.caption_plural.__class__ == EntityCaptionCallable:
|
||||
caption = entity.caption_plural(entity) or entity.name
|
||||
elif entity.caption_plural.__class__ == LazyProxy:
|
||||
caption = f"{f"{entity.icon} " if entity.icon else ""}{entity.caption_plural.value or entity.name}"
|
||||
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.caption_plural or entity.name}"
|
||||
|
||||
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()))
|
||||
|
||||
text=caption,
|
||||
callback_data=ContextData(
|
||||
command=CallbackCommand.ENTITY_LIST, entity_name=entity.name
|
||||
).pack(),
|
||||
)
|
||||
)
|
||||
|
||||
context = pop_navigation_context(navigation_stack)
|
||||
if context:
|
||||
keyboard_builder.row(
|
||||
InlineKeyboardButton(
|
||||
text = (await Settings.get(Settings.APP_STRINGS_BACK_BTN)),
|
||||
callback_data = context.pack()))
|
||||
|
||||
text=(await Settings.get(Settings.APP_STRINGS_BACK_BTN)),
|
||||
callback_data=context.pack(),
|
||||
)
|
||||
)
|
||||
|
||||
state_data = kwargs["state_data"]
|
||||
await state.set_data(state_data)
|
||||
|
||||
|
||||
send_message = get_send_message(message)
|
||||
|
||||
await send_message(text = (await Settings.get(Settings.APP_STRINGS_REFERENCES)), reply_markup = keyboard_builder.as_markup())
|
||||
|
||||
|
||||
from ..navigation import save_navigation_context, pop_navigation_context
|
||||
await send_message(
|
||||
text=(await Settings.get(Settings.APP_STRINGS_REFERENCES)),
|
||||
reply_markup=keyboard_builder.as_markup(),
|
||||
)
|
||||
|
||||
@@ -1,64 +1,85 @@
|
||||
from aiogram import Router, F
|
||||
from aiogram.types import Message, CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup
|
||||
from aiogram.types import (
|
||||
Message,
|
||||
CallbackQuery,
|
||||
InlineKeyboardButton,
|
||||
InlineKeyboardMarkup,
|
||||
)
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.utils.i18n import I18n
|
||||
from logging import getLogger
|
||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
|
||||
from ..navigation import pop_navigation_context, save_navigation_context
|
||||
from ....model.language import LanguageBase
|
||||
from ....model.settings import Settings
|
||||
from ....model.user import UserBase
|
||||
from ..context import ContextData, CallbackCommand
|
||||
from ..navigation import route_callback
|
||||
from ..common import get_send_message
|
||||
from ..common.routing import route_callback
|
||||
from ....utils.main import get_send_message
|
||||
|
||||
|
||||
logger = getLogger(__name__)
|
||||
router = Router()
|
||||
|
||||
|
||||
@router.callback_query(ContextData.filter(F.command == CallbackCommand.MENU_ENTRY_LANGUAGE))
|
||||
@router.callback_query(
|
||||
ContextData.filter(F.command == CallbackCommand.MENU_ENTRY_LANGUAGE)
|
||||
)
|
||||
async def menu_entry_language(message: CallbackQuery, **kwargs):
|
||||
|
||||
callback_data: ContextData = kwargs["callback_data"]
|
||||
state: FSMContext = kwargs["state"]
|
||||
state_data = await state.get_data()
|
||||
kwargs["state_data"] = state_data
|
||||
|
||||
stack = save_navigation_context(callback_data = callback_data, state_data = state_data)
|
||||
stack = save_navigation_context(callback_data=callback_data, state_data=state_data)
|
||||
|
||||
await language_menu(message, navigation_stack = stack, **kwargs)
|
||||
|
||||
await language_menu(message, navigation_stack=stack, **kwargs)
|
||||
|
||||
async def language_menu(message: Message | CallbackQuery,
|
||||
navigation_stack: list[ContextData],
|
||||
user: UserBase,
|
||||
**kwargs):
|
||||
|
||||
async def language_menu(
|
||||
message: Message | CallbackQuery,
|
||||
navigation_stack: list[ContextData],
|
||||
user: UserBase,
|
||||
**kwargs,
|
||||
):
|
||||
send_message = get_send_message(message)
|
||||
|
||||
inline_keyboard = [
|
||||
[InlineKeyboardButton(text = locale.localized(user.lang),
|
||||
callback_data = ContextData(command = CallbackCommand.SET_LANGUAGE,
|
||||
data = str(locale)).pack())]
|
||||
for locale in LanguageBase.all_members.values()]
|
||||
|
||||
[
|
||||
InlineKeyboardButton(
|
||||
text=locale.localized(user.lang),
|
||||
callback_data=ContextData(
|
||||
command=CallbackCommand.SET_LANGUAGE, data=str(locale)
|
||||
).pack(),
|
||||
)
|
||||
]
|
||||
for locale in LanguageBase.all_members.values()
|
||||
]
|
||||
|
||||
context = pop_navigation_context(navigation_stack)
|
||||
if context:
|
||||
inline_keyboard.append([InlineKeyboardButton(text = (await Settings.get(Settings.APP_STRINGS_BACK_BTN)),
|
||||
callback_data = context.pack())])
|
||||
|
||||
inline_keyboard.append(
|
||||
[
|
||||
InlineKeyboardButton(
|
||||
text=(await Settings.get(Settings.APP_STRINGS_BACK_BTN)),
|
||||
callback_data=context.pack(),
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
state: FSMContext = kwargs["state"]
|
||||
state_data = kwargs["state_data"]
|
||||
await state.set_data(state_data)
|
||||
|
||||
await send_message(text = (await Settings.get(Settings.APP_STRINGS_LANGUAGE)),
|
||||
reply_markup = InlineKeyboardMarkup(inline_keyboard = inline_keyboard))
|
||||
|
||||
await send_message(
|
||||
text=(await Settings.get(Settings.APP_STRINGS_LANGUAGE)),
|
||||
reply_markup=InlineKeyboardMarkup(inline_keyboard=inline_keyboard),
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(ContextData.filter(F.command == CallbackCommand.SET_LANGUAGE))
|
||||
async def set_language(message: CallbackQuery, **kwargs):
|
||||
|
||||
user: UserBase = kwargs["user"]
|
||||
callback_data: ContextData = kwargs["callback_data"]
|
||||
db_session: AsyncSession = kwargs["db_session"]
|
||||
@@ -73,6 +94,3 @@ async def set_language(message: CallbackQuery, **kwargs):
|
||||
i18n: I18n = kwargs["i18n"]
|
||||
with i18n.use_locale(user.lang):
|
||||
await route_callback(message, **kwargs)
|
||||
|
||||
|
||||
from ..navigation import pop_navigation_context, save_navigation_context
|
||||
@@ -1,93 +1,88 @@
|
||||
from aiogram import Router, F
|
||||
from aiogram.filters import Command
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.types import Message, CallbackQuery, InlineKeyboardButton
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from logging import getLogger
|
||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
from ....model.settings import Settings
|
||||
from ....model.user import UserBase
|
||||
from ..context import ContextData, CallbackCommand
|
||||
from ..common import get_send_message
|
||||
from ....utils.main import get_send_message
|
||||
from ..navigation import save_navigation_context, pop_navigation_context
|
||||
|
||||
import qbot.bot.handlers.menu.entities as entities
|
||||
import qbot.bot.handlers.menu.settings as settings
|
||||
import qbot.bot.handlers.menu.parameters as parameters
|
||||
import qbot.bot.handlers.menu.language as language
|
||||
import qbot.bot.handlers.editors.main as editor
|
||||
import qbot.bot.handlers.editors.main_callbacks as editor_callbacks
|
||||
import qbot.bot.handlers.forms.entity_list as entity_list
|
||||
import qbot.bot.handlers.forms.entity_form as entity_form
|
||||
import qbot.bot.handlers.forms.entity_form_callbacks as entity_form_callbacks
|
||||
import qbot.bot.handlers.common.filtering_callbacks as filtering_callbacks
|
||||
import qbot.bot.handlers.user_handlers as user_handlers
|
||||
|
||||
|
||||
logger = getLogger(__name__)
|
||||
router = Router()
|
||||
|
||||
|
||||
# @router.message(Command("menu"))
|
||||
# async def command_menu(message: Message, **kwargs):
|
||||
|
||||
# await clear_state(state = kwargs["state"], clear_nav = True)
|
||||
# callback_data = ContextData(command = CallbackCommand.MENU_ENTRY_MAIN)
|
||||
# stack = await save_navigation_context(callback_data = callback_data, state = kwargs["state"])
|
||||
# kwargs.update({"navigation_stack": stack, "callback_data": callback_data})
|
||||
|
||||
# await main_menu(message, **kwargs)
|
||||
|
||||
|
||||
# @router.callback_query(CallbackData.filter(F.command == CallbackCommand.MENU_ENTRY))
|
||||
# async def menu_entry(query: CallbackQuery, callback_data: CallbackData, user: UserBase, db_session: AsyncSession, app: QBotApp):
|
||||
|
||||
# pass
|
||||
|
||||
|
||||
@router.callback_query(ContextData.filter(F.command == CallbackCommand.MENU_ENTRY_MAIN))
|
||||
async def menu_entry_main(message: CallbackQuery, **kwargs):
|
||||
async def menu_entry_main(message: CallbackQuery, **kwargs):
|
||||
stack = await save_navigation_context(
|
||||
callback_data=kwargs["callback_data"], state=kwargs["state"]
|
||||
)
|
||||
|
||||
stack = await save_navigation_context(callback_data = kwargs["callback_data"], state = kwargs["state"])
|
||||
|
||||
await main_menu(message, navigation_stack = stack, **kwargs)
|
||||
await main_menu(message, navigation_stack=stack, **kwargs)
|
||||
|
||||
|
||||
async def main_menu(message: Message | CallbackQuery, navigation_stack: list[ContextData], **kwargs):
|
||||
|
||||
async def main_menu(
|
||||
message: Message | CallbackQuery, navigation_stack: list[ContextData], **kwargs
|
||||
):
|
||||
keyboard_builder = InlineKeyboardBuilder()
|
||||
|
||||
keyboard_builder.row(
|
||||
InlineKeyboardButton(
|
||||
text = (await Settings.get(Settings.APP_STRINGS_REFERENCES_BTN)),
|
||||
callback_data = ContextData(command = CallbackCommand.MENU_ENTRY_ENTITIES).pack()))
|
||||
|
||||
|
||||
keyboard_builder.row(
|
||||
InlineKeyboardButton(
|
||||
text = (await Settings.get(Settings.APP_STRINGS_SETTINGS_BTN)),
|
||||
callback_data = ContextData(command = CallbackCommand.MENU_ENTRY_SETTINGS).pack()))
|
||||
|
||||
text=(await Settings.get(Settings.APP_STRINGS_REFERENCES_BTN)),
|
||||
callback_data=ContextData(
|
||||
command=CallbackCommand.MENU_ENTRY_ENTITIES
|
||||
).pack(),
|
||||
)
|
||||
)
|
||||
|
||||
keyboard_builder.row(
|
||||
InlineKeyboardButton(
|
||||
text=(await Settings.get(Settings.APP_STRINGS_SETTINGS_BTN)),
|
||||
callback_data=ContextData(
|
||||
command=CallbackCommand.MENU_ENTRY_SETTINGS
|
||||
).pack(),
|
||||
)
|
||||
)
|
||||
|
||||
context = pop_navigation_context(navigation_stack)
|
||||
if context:
|
||||
keyboard_builder.row(
|
||||
InlineKeyboardButton(
|
||||
text = (await Settings.get(Settings.APP_STRINGS_BACK_BTN)),
|
||||
callback_data = context.pack()))
|
||||
|
||||
text=(await Settings.get(Settings.APP_STRINGS_BACK_BTN)),
|
||||
callback_data=context.pack(),
|
||||
)
|
||||
)
|
||||
|
||||
send_message = get_send_message(message)
|
||||
|
||||
await send_message(text = (await Settings.get(Settings.APP_STRINGS_MAIN_NENU)),
|
||||
reply_markup = keyboard_builder.as_markup())
|
||||
await send_message(
|
||||
text=(await Settings.get(Settings.APP_STRINGS_MAIN_NENU)),
|
||||
reply_markup=keyboard_builder.as_markup(),
|
||||
)
|
||||
|
||||
|
||||
from .entities import router as entities_router
|
||||
from .settings import router as settings_router
|
||||
from .parameters import router as parameters_router
|
||||
from .language import router as language_router
|
||||
from ..editors import router as editors_router
|
||||
from ..forms.entity_list import router as entity_list_router
|
||||
from ..forms.entity_form import router as entity_form_router
|
||||
from ..common import router as common_router
|
||||
from ..user_handlers import router as user_handlers_router
|
||||
|
||||
router.include_routers(
|
||||
entities_router,
|
||||
settings_router,
|
||||
parameters_router,
|
||||
language_router,
|
||||
editors_router,
|
||||
entity_list_router,
|
||||
entity_form_router,
|
||||
common_router,
|
||||
user_handlers_router
|
||||
entities.router,
|
||||
settings.router,
|
||||
parameters.router,
|
||||
language.router,
|
||||
editor.router,
|
||||
editor_callbacks.router,
|
||||
entity_list.router,
|
||||
entity_form.router,
|
||||
entity_form_callbacks.router,
|
||||
filtering_callbacks.router,
|
||||
user_handlers.router,
|
||||
)
|
||||
|
||||
from ..navigation import save_navigation_context, pop_navigation_context, clear_state
|
||||
@@ -1,79 +1,94 @@
|
||||
from aiogram import Router, F
|
||||
from aiogram.filters import Command
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import StatesGroup, State
|
||||
from aiogram.types import Message, CallbackQuery, InlineKeyboardButton
|
||||
from aiogram.utils.i18n import I18n
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from logging import getLogger
|
||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
|
||||
from ....model.settings import Settings
|
||||
from ....model.user import UserBase
|
||||
from ..context import ContextData, CallbackCommand, CommandContext
|
||||
|
||||
from ....utils.main import (
|
||||
get_send_message,
|
||||
clear_state,
|
||||
get_value_repr,
|
||||
get_callable_str,
|
||||
)
|
||||
from ..navigation import save_navigation_context, pop_navigation_context
|
||||
from ....auth import authorize_command
|
||||
|
||||
|
||||
logger = getLogger(__name__)
|
||||
router = Router()
|
||||
|
||||
|
||||
@router.callback_query(ContextData.filter(F.command == CallbackCommand.MENU_ENTRY_PARAMETERS))
|
||||
@router.callback_query(
|
||||
ContextData.filter(F.command == CallbackCommand.MENU_ENTRY_PARAMETERS)
|
||||
)
|
||||
async def menu_entry_parameters(message: CallbackQuery, **kwargs):
|
||||
|
||||
callback_data: ContextData = kwargs["callback_data"]
|
||||
state: FSMContext = kwargs["state"]
|
||||
state_data = await state.get_data()
|
||||
kwargs["state_data"] = state_data
|
||||
|
||||
clear_state(state_data = state_data)
|
||||
stack = save_navigation_context(callback_data = callback_data, state_data = state_data)
|
||||
clear_state(state_data=state_data)
|
||||
stack = save_navigation_context(callback_data=callback_data, state_data=state_data)
|
||||
|
||||
await parameters_menu(message = message, navigation_stack = stack, **kwargs)
|
||||
await parameters_menu(message=message, navigation_stack=stack, **kwargs)
|
||||
|
||||
|
||||
async def parameters_menu(message: Message | CallbackQuery,
|
||||
user: UserBase,
|
||||
callback_data: ContextData,
|
||||
navigation_stack: list[ContextData],
|
||||
**kwargs):
|
||||
|
||||
if not await authorize_command(user = user, callback_data = callback_data):
|
||||
async def parameters_menu(
|
||||
message: Message | CallbackQuery,
|
||||
user: UserBase,
|
||||
callback_data: ContextData,
|
||||
navigation_stack: list[ContextData],
|
||||
**kwargs,
|
||||
):
|
||||
if not await authorize_command(user=user, callback_data=callback_data):
|
||||
await message.answer(await Settings.get(Settings.APP_STRINGS_FORBIDDEN))
|
||||
|
||||
settings = await Settings.get_params()
|
||||
|
||||
|
||||
keyboard_builder = InlineKeyboardBuilder()
|
||||
for key, value in settings.items():
|
||||
|
||||
if not key.is_visible:
|
||||
continue
|
||||
|
||||
if key.caption_value:
|
||||
caption = get_callable_str(callable_str = key.caption_value, descriptor = key, entity = None, value = value)
|
||||
caption = get_callable_str(
|
||||
callable_str=key.caption_value, descriptor=key, entity=None, value=value
|
||||
)
|
||||
else:
|
||||
if key.caption:
|
||||
caption = get_callable_str(callable_str = key.caption, descriptor = key, entity = None, value = value)
|
||||
caption = get_callable_str(
|
||||
callable_str=key.caption, descriptor=key, entity=None, value=value
|
||||
)
|
||||
else:
|
||||
caption = key.name
|
||||
|
||||
if key.type_ == bool:
|
||||
caption = f"{"【✔︎】" if value else "【 】"} {caption}"
|
||||
else:
|
||||
caption = f"{caption}: {get_value_repr(value = value, field_descriptor = key, locale = user.lang)}"
|
||||
|
||||
caption = key.name
|
||||
|
||||
if key.type_ is bool:
|
||||
caption = f"{'【✔︎】' if value else '【 】'} {caption}"
|
||||
else:
|
||||
caption = f"{caption}: {get_value_repr(value=value, field_descriptor=key, locale=user.lang)}"
|
||||
|
||||
keyboard_builder.row(
|
||||
InlineKeyboardButton(
|
||||
text=caption,
|
||||
callback_data=ContextData(
|
||||
command=CallbackCommand.FIELD_EDITOR,
|
||||
context=CommandContext.SETTING_EDIT,
|
||||
field_name=key.name,
|
||||
).pack(),
|
||||
)
|
||||
)
|
||||
|
||||
keyboard_builder.row(InlineKeyboardButton(text = caption,
|
||||
callback_data = ContextData(
|
||||
command = CallbackCommand.FIELD_EDITOR,
|
||||
context = CommandContext.SETTING_EDIT,
|
||||
field_name = key.name).pack()))
|
||||
|
||||
context = pop_navigation_context(navigation_stack)
|
||||
if context:
|
||||
keyboard_builder.row(InlineKeyboardButton(text = (await Settings.get(Settings.APP_STRINGS_BACK_BTN)),
|
||||
callback_data = context.pack()))
|
||||
keyboard_builder.row(
|
||||
InlineKeyboardButton(
|
||||
text=(await Settings.get(Settings.APP_STRINGS_BACK_BTN)),
|
||||
callback_data=context.pack(),
|
||||
)
|
||||
)
|
||||
|
||||
state: FSMContext = kwargs["state"]
|
||||
state_data = kwargs["state_data"]
|
||||
@@ -81,8 +96,7 @@ async def parameters_menu(message: Message | CallbackQuery,
|
||||
|
||||
send_message = get_send_message(message)
|
||||
|
||||
await send_message(text = (await Settings.get(Settings.APP_STRINGS_PARAMETERS)), reply_markup = keyboard_builder.as_markup())
|
||||
|
||||
|
||||
from ..navigation import pop_navigation_context, get_navigation_context, clear_state
|
||||
from ..common import get_send_message, get_value_repr, get_callable_str, authorize_command
|
||||
await send_message(
|
||||
text=(await Settings.get(Settings.APP_STRINGS_PARAMETERS)),
|
||||
reply_markup=keyboard_builder.as_markup(),
|
||||
)
|
||||
|
||||
@@ -1,68 +1,80 @@
|
||||
from aiogram import Router, F
|
||||
from aiogram.filters import Command
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.types import Message, CallbackQuery, InlineKeyboardButton
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from logging import getLogger
|
||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
|
||||
from ....model.settings import Settings
|
||||
from ....model.user import UserBase
|
||||
from ....utils.main import get_send_message
|
||||
from ..context import ContextData, CallbackCommand
|
||||
from ..common import get_send_message, authorize_command
|
||||
from ....auth import authorize_command
|
||||
from ..navigation import save_navigation_context, pop_navigation_context
|
||||
|
||||
logger = getLogger(__name__)
|
||||
router = Router()
|
||||
|
||||
|
||||
@router.callback_query(ContextData.filter(F.command == CallbackCommand.MENU_ENTRY_SETTINGS))
|
||||
@router.callback_query(
|
||||
ContextData.filter(F.command == CallbackCommand.MENU_ENTRY_SETTINGS)
|
||||
)
|
||||
async def menu_entry_settings(message: CallbackQuery, **kwargs):
|
||||
|
||||
callback_data: ContextData = kwargs["callback_data"]
|
||||
state: FSMContext = kwargs["state"]
|
||||
state_data = await state.get_data()
|
||||
kwargs["state_data"] = state_data
|
||||
|
||||
stack = save_navigation_context(callback_data = callback_data, state_data = state_data)
|
||||
stack = save_navigation_context(callback_data=callback_data, state_data=state_data)
|
||||
|
||||
await settings_menu(message, navigation_stack = stack, **kwargs)
|
||||
await settings_menu(message, navigation_stack=stack, **kwargs)
|
||||
|
||||
|
||||
async def settings_menu(message: Message | CallbackQuery,
|
||||
user: UserBase,
|
||||
navigation_stack: list[ContextData],
|
||||
**kwargs):
|
||||
|
||||
async def settings_menu(
|
||||
message: Message | CallbackQuery,
|
||||
user: UserBase,
|
||||
navigation_stack: list[ContextData],
|
||||
**kwargs,
|
||||
):
|
||||
keyboard_builder = InlineKeyboardBuilder()
|
||||
|
||||
if await authorize_command(user = user, callback_data = ContextData(command = CallbackCommand.MENU_ENTRY_PARAMETERS)):
|
||||
|
||||
if await authorize_command(
|
||||
user=user,
|
||||
callback_data=ContextData(command=CallbackCommand.MENU_ENTRY_PARAMETERS),
|
||||
):
|
||||
keyboard_builder.row(
|
||||
InlineKeyboardButton(
|
||||
text = (await Settings.get(Settings.APP_STRINGS_PARAMETERS_BTN)),
|
||||
callback_data = ContextData(command = CallbackCommand.MENU_ENTRY_PARAMETERS).pack()))
|
||||
|
||||
text=(await Settings.get(Settings.APP_STRINGS_PARAMETERS_BTN)),
|
||||
callback_data=ContextData(
|
||||
command=CallbackCommand.MENU_ENTRY_PARAMETERS
|
||||
).pack(),
|
||||
)
|
||||
)
|
||||
|
||||
keyboard_builder.row(
|
||||
InlineKeyboardButton(
|
||||
text = (await Settings.get(Settings.APP_STRINGS_LANGUAGE_BTN)),
|
||||
callback_data = ContextData(command = CallbackCommand.MENU_ENTRY_LANGUAGE).pack()))
|
||||
|
||||
text=(await Settings.get(Settings.APP_STRINGS_LANGUAGE_BTN)),
|
||||
callback_data=ContextData(
|
||||
command=CallbackCommand.MENU_ENTRY_LANGUAGE
|
||||
).pack(),
|
||||
)
|
||||
)
|
||||
|
||||
context = pop_navigation_context(navigation_stack)
|
||||
if context:
|
||||
keyboard_builder.row(
|
||||
InlineKeyboardButton(
|
||||
text = (await Settings.get(Settings.APP_STRINGS_BACK_BTN)),
|
||||
callback_data = context.pack()))
|
||||
|
||||
text=(await Settings.get(Settings.APP_STRINGS_BACK_BTN)),
|
||||
callback_data=context.pack(),
|
||||
)
|
||||
)
|
||||
|
||||
state: FSMContext = kwargs["state"]
|
||||
state_data = kwargs["state_data"]
|
||||
await state.set_data(state_data)
|
||||
|
||||
|
||||
send_message = get_send_message(message)
|
||||
|
||||
|
||||
|
||||
await send_message(text = (await Settings.get(Settings.APP_STRINGS_SETTINGS)), reply_markup = keyboard_builder.as_markup())
|
||||
|
||||
|
||||
from ..navigation import pop_navigation_context, get_navigation_context
|
||||
await send_message(
|
||||
text=(await Settings.get(Settings.APP_STRINGS_SETTINGS)),
|
||||
reply_markup=keyboard_builder.as_markup(),
|
||||
)
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.types import Message, CallbackQuery
|
||||
|
||||
from .context import ContextData, CallbackCommand
|
||||
|
||||
|
||||
def save_navigation_context(callback_data: ContextData, state_data: dict) -> list[ContextData]:
|
||||
stack = [ContextData.unpack(item) for item in state_data.get("navigation_stack", [])]
|
||||
def save_navigation_context(
|
||||
callback_data: ContextData, state_data: dict
|
||||
) -> list[ContextData]:
|
||||
stack = [
|
||||
ContextData.unpack(item) for item in state_data.get("navigation_stack", [])
|
||||
]
|
||||
data_nc = state_data.get("navigation_context")
|
||||
navigation_context = ContextData.unpack(data_nc) if data_nc else None
|
||||
if callback_data.back:
|
||||
callback_data.back = False
|
||||
callback_data.back = False
|
||||
if stack:
|
||||
stack.pop()
|
||||
else:
|
||||
if (stack and navigation_context and
|
||||
navigation_context.command == callback_data.command and
|
||||
navigation_context.command != CallbackCommand.USER_COMMAND):
|
||||
if (
|
||||
stack
|
||||
and navigation_context
|
||||
and navigation_context.command == callback_data.command
|
||||
and navigation_context.command != CallbackCommand.USER_COMMAND
|
||||
):
|
||||
navigation_context = callback_data
|
||||
elif navigation_context:
|
||||
stack.append(navigation_context)
|
||||
@@ -31,65 +35,14 @@ def pop_navigation_context(stack: list[ContextData]) -> ContextData | None:
|
||||
data = stack[-1]
|
||||
data.back = True
|
||||
return data
|
||||
|
||||
|
||||
def get_navigation_context(state_data: dict) -> tuple[list[ContextData], ContextData | None]:
|
||||
|
||||
def get_navigation_context(
|
||||
state_data: dict,
|
||||
) -> tuple[list[ContextData], ContextData | None]:
|
||||
data_nc = state_data.get("navigation_context")
|
||||
context = ContextData.unpack(data_nc) if data_nc else None
|
||||
return ([ContextData.unpack(item) for item in state_data.get("navigation_stack", [])],
|
||||
context)
|
||||
|
||||
|
||||
def clear_state(state_data: dict, clear_nav: bool = False):
|
||||
if clear_nav:
|
||||
state_data.clear()
|
||||
else:
|
||||
stack = state_data.get("navigation_stack")
|
||||
context = state_data.get("navigation_context")
|
||||
state_data.clear()
|
||||
if stack:
|
||||
state_data["navigation_stack"] = stack
|
||||
if context:
|
||||
state_data["navigation_context"] = context
|
||||
|
||||
|
||||
async def route_callback(message: Message | CallbackQuery, back: bool = True, **kwargs):
|
||||
|
||||
state_data = kwargs["state_data"]
|
||||
stack, context = get_navigation_context(state_data)
|
||||
if back:
|
||||
context = pop_navigation_context(stack)
|
||||
stack = save_navigation_context(callback_data = context, state_data = state_data)
|
||||
kwargs.update({"callback_data": context, "navigation_stack": stack})
|
||||
if context:
|
||||
if context.command == CallbackCommand.MENU_ENTRY_MAIN:
|
||||
await main_menu(message, **kwargs)
|
||||
elif context.command == CallbackCommand.MENU_ENTRY_SETTINGS:
|
||||
await settings_menu(message, **kwargs)
|
||||
elif context.command == CallbackCommand.MENU_ENTRY_PARAMETERS:
|
||||
await parameters_menu(message, **kwargs)
|
||||
elif context.command == CallbackCommand.MENU_ENTRY_LANGUAGE:
|
||||
await language_menu(message, **kwargs)
|
||||
elif context.command == CallbackCommand.MENU_ENTRY_ENTITIES:
|
||||
await entities_menu(message, **kwargs)
|
||||
elif context.command == CallbackCommand.ENTITY_LIST:
|
||||
await entity_list(message, **kwargs)
|
||||
elif context.command == CallbackCommand.ENTITY_ITEM:
|
||||
await entity_item(message, **kwargs)
|
||||
elif context.command == CallbackCommand.FIELD_EDITOR:
|
||||
await field_editor(message, **kwargs)
|
||||
|
||||
else:
|
||||
raise ValueError(f"Unknown command {context.command}")
|
||||
else:
|
||||
raise ValueError("No navigation context")
|
||||
|
||||
|
||||
from .menu.main import main_menu
|
||||
from .menu.settings import settings_menu
|
||||
from .menu.parameters import parameters_menu
|
||||
from .menu.language import language_menu
|
||||
from .menu.entities import entities_menu
|
||||
from .forms.entity_list import entity_list
|
||||
from .forms.entity_form import entity_item
|
||||
from .editors import field_editor
|
||||
return (
|
||||
[ContextData.unpack(item) for item in state_data.get("navigation_stack", [])],
|
||||
context,
|
||||
)
|
||||
|
||||
@@ -8,7 +8,7 @@ from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
from ...main import QBotApp
|
||||
from ...model.settings import Settings
|
||||
from ...model.language import LanguageBase
|
||||
from .navigation import clear_state
|
||||
from ...utils.main import clear_state
|
||||
|
||||
|
||||
logger = getLogger(__name__)
|
||||
@@ -16,40 +16,56 @@ router = Router()
|
||||
|
||||
|
||||
@router.message(CommandStart())
|
||||
async def start(message: Message, db_session: AsyncSession, app: QBotApp, state: FSMContext):
|
||||
|
||||
async def start(
|
||||
message: Message, db_session: AsyncSession, app: QBotApp, state: FSMContext
|
||||
):
|
||||
state_data = await state.get_data()
|
||||
clear_state(state_data = state_data, clear_nav = True)
|
||||
clear_state(state_data=state_data, clear_nav=True)
|
||||
|
||||
User = app.user_class
|
||||
|
||||
user = await User.get(session = db_session, id = message.from_user.id)
|
||||
|
||||
user = await User.get(session=db_session, id=message.from_user.id)
|
||||
|
||||
if not user:
|
||||
msg_text = (await Settings.get(Settings.APP_STRINGS_WELCOME_P_NAME)).format(name = message.from_user.full_name)
|
||||
|
||||
msg_text = (await Settings.get(Settings.APP_STRINGS_WELCOME_P_NAME)).format(
|
||||
name=message.from_user.full_name
|
||||
)
|
||||
|
||||
try:
|
||||
if message.from_user.language_code in [item.value for item in LanguageBase.all_members.values()]:
|
||||
if message.from_user.language_code in [
|
||||
item.value for item in LanguageBase.all_members.values()
|
||||
]:
|
||||
lang = LanguageBase(message.from_user.language_code)
|
||||
user = await User.create(session = db_session,
|
||||
obj_in = User(
|
||||
id = message.from_user.id,
|
||||
name = message.from_user.full_name,
|
||||
lang = lang,
|
||||
is_active = True),
|
||||
commit = True)
|
||||
|
||||
user = await User.create(
|
||||
session=db_session,
|
||||
obj_in=User(
|
||||
id=message.from_user.id,
|
||||
name=message.from_user.full_name,
|
||||
lang=lang,
|
||||
is_active=True,
|
||||
),
|
||||
commit=True,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
|
||||
logger.error("Error creating user", exc_info = True)
|
||||
message.answer((await Settings.get(Settings.APP_STRINGS_INTERNAL_ERROR_P_ERROR)).format(error = str(e)))
|
||||
logger.error("Error creating user", exc_info=True)
|
||||
message.answer(
|
||||
(
|
||||
await Settings.get(Settings.APP_STRINGS_INTERNAL_ERROR_P_ERROR)
|
||||
).format(error=str(e))
|
||||
)
|
||||
|
||||
return
|
||||
|
||||
|
||||
else:
|
||||
if user.is_active:
|
||||
msg_text = (await Settings.get(Settings.APP_STRINGS_GREETING_P_NAME)).format(name = user.name)
|
||||
msg_text = (
|
||||
await Settings.get(Settings.APP_STRINGS_GREETING_P_NAME)
|
||||
).format(name=user.name)
|
||||
else:
|
||||
msg_text = (await Settings.get(Settings.APP_STRINGS_USER_BLOCKED_P_NAME)).format(name = user.name)
|
||||
|
||||
msg_text = (
|
||||
await Settings.get(Settings.APP_STRINGS_USER_BLOCKED_P_NAME)
|
||||
).format(name=user.name)
|
||||
|
||||
await message.answer(msg_text)
|
||||
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Callable, TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING
|
||||
from aiogram import Router, F
|
||||
from aiogram.types import Message, CallbackQuery, InlineKeyboardButton
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.utils.i18n import I18n
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
from ..context import ContextData, CallbackCommand
|
||||
from ....model.user import UserBase
|
||||
from ....model.settings import Settings
|
||||
from ....utils.main import get_send_message, clear_state
|
||||
from ....model.descriptors import CommandCallbackContext
|
||||
from ..navigation import (
|
||||
save_navigation_context,
|
||||
get_navigation_context,
|
||||
pop_navigation_context,
|
||||
)
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -18,51 +20,22 @@ if TYPE_CHECKING:
|
||||
router = Router()
|
||||
|
||||
|
||||
@dataclass(kw_only = True)
|
||||
class CommandCallbackContext[UT: UserBase]:
|
||||
|
||||
keyboard_builder: InlineKeyboardBuilder = field(default_factory = InlineKeyboardBuilder)
|
||||
message_text: str | None = None
|
||||
register_navigation: bool = True
|
||||
message: Message | CallbackQuery
|
||||
callback_data: ContextData
|
||||
db_session: AsyncSession
|
||||
user: UT
|
||||
app: "QBotApp"
|
||||
state_data: dict[str, Any]
|
||||
state: FSMContext
|
||||
i18n: I18n
|
||||
kwargs: dict[str, Any] = field(default_factory = dict)
|
||||
|
||||
|
||||
@dataclass(kw_only = True)
|
||||
class Command:
|
||||
|
||||
name: str
|
||||
handler: Callable[[CommandCallbackContext], None]
|
||||
caption: str | dict[str, str] | None = None
|
||||
register_navigation: bool = True
|
||||
clear_navigation: bool = False
|
||||
clear_state: bool = True
|
||||
|
||||
@router.message(F.text.startswith("/"))
|
||||
async def command_text(message: Message, **kwargs):
|
||||
|
||||
str_command = message.text.lstrip("/")
|
||||
callback_data = ContextData(command = CallbackCommand.USER_COMMAND,
|
||||
user_command = str_command)
|
||||
callback_data = ContextData(
|
||||
command=CallbackCommand.USER_COMMAND, user_command=str_command
|
||||
)
|
||||
|
||||
await command_handler(message = message, callback_data = callback_data, **kwargs)
|
||||
await command_handler(message=message, callback_data=callback_data, **kwargs)
|
||||
|
||||
|
||||
@router.callback_query(ContextData.filter(F.command == CallbackCommand.USER_COMMAND))
|
||||
async def command_callback(message: CallbackQuery, **kwargs):
|
||||
|
||||
await command_handler(message = message, **kwargs)
|
||||
await command_handler(message=message, **kwargs)
|
||||
|
||||
|
||||
async def command_handler(message: Message | CallbackQuery, **kwargs):
|
||||
|
||||
callback_data: ContextData = kwargs.pop("callback_data")
|
||||
str_command = callback_data.user_command
|
||||
app: "QBotApp" = kwargs.pop("app")
|
||||
@@ -70,45 +43,48 @@ async def command_handler(message: Message | CallbackQuery, **kwargs):
|
||||
|
||||
if not command:
|
||||
return
|
||||
|
||||
|
||||
state: FSMContext = kwargs.pop("state")
|
||||
state_data = await state.get_data()
|
||||
|
||||
|
||||
if command.register_navigation:
|
||||
clear_state(state_data = state_data)
|
||||
clear_state(state_data=state_data)
|
||||
|
||||
if command.clear_navigation:
|
||||
state_data.pop("navigation_stack", None)
|
||||
state_data.pop("navigation_context", None)
|
||||
|
||||
if command.register_navigation:
|
||||
stack = save_navigation_context(callback_data = callback_data, state_data = state_data)
|
||||
|
||||
stack = save_navigation_context(
|
||||
callback_data=callback_data, state_data=state_data
|
||||
)
|
||||
|
||||
callback_context = CommandCallbackContext[app.user_class](
|
||||
message = message,
|
||||
callback_data = callback_data,
|
||||
db_session = kwargs.pop("db_session"),
|
||||
user = kwargs.pop("user"),
|
||||
app = app,
|
||||
state_data = state_data,
|
||||
state = state,
|
||||
i18n = kwargs.pop("i18n"),
|
||||
kwargs = kwargs)
|
||||
message=message,
|
||||
callback_data=callback_data,
|
||||
db_session=kwargs.pop("db_session"),
|
||||
user=kwargs.pop("user"),
|
||||
app=app,
|
||||
state_data=state_data,
|
||||
state=state,
|
||||
i18n=kwargs.pop("i18n"),
|
||||
kwargs=kwargs,
|
||||
)
|
||||
|
||||
await command.handler(callback_context)
|
||||
|
||||
await state.set_data(state_data)
|
||||
|
||||
if command.register_navigation:
|
||||
|
||||
stack, navigation_context = get_navigation_context(state_data = state_data)
|
||||
back_callback_data = pop_navigation_context(stack = stack)
|
||||
stack, navigation_context = get_navigation_context(state_data=state_data)
|
||||
back_callback_data = pop_navigation_context(stack=stack)
|
||||
if back_callback_data:
|
||||
|
||||
callback_context.keyboard_builder.row(
|
||||
InlineKeyboardButton(
|
||||
text = (await Settings.get(Settings.APP_STRINGS_BACK_BTN)),
|
||||
callback_data = back_callback_data.pack()))
|
||||
text=(await Settings.get(Settings.APP_STRINGS_BACK_BTN)),
|
||||
callback_data=back_callback_data.pack(),
|
||||
)
|
||||
)
|
||||
|
||||
send_message = get_send_message(message)
|
||||
|
||||
@@ -116,10 +92,11 @@ async def command_handler(message: Message | CallbackQuery, **kwargs):
|
||||
message = message.message
|
||||
|
||||
if callback_context.message_text:
|
||||
await send_message(text = callback_context.message_text,
|
||||
reply_markup = callback_context.keyboard_builder.as_markup())
|
||||
await send_message(
|
||||
text=callback_context.message_text,
|
||||
reply_markup=callback_context.keyboard_builder.as_markup(),
|
||||
)
|
||||
else:
|
||||
await message.edit_reply_markup(reply_markup = callback_context.keyboard_builder.as_markup())
|
||||
|
||||
from ..common import get_send_message
|
||||
from ..navigation import save_navigation_context, get_navigation_context, clear_state, pop_navigation_context
|
||||
await message.edit_reply_markup(
|
||||
reply_markup=callback_context.keyboard_builder.as_markup()
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user