from typing import get_args, get_origin, 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.descriptors import EntityFieldDescriptor, EntityDescriptor
from ....model import EntityPermission
from ....utils import serialize, deserialize, get_user_permissions
from ..context import ContextData, CallbackCommand, CommandContext
from ..common import get_send_message, get_local_text, get_entity_descriptor, get_callable_str, get_value_repr
if TYPE_CHECKING:
from ....main import QBotApp
logger = getLogger(__name__)
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)
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_
keyboard_builder = InlineKeyboardBuilder()
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)))
is_owned = issubclass(entity_type, OwnedBotEntity)
if (EntityPermission.READ not in user_permissions and
EntityPermission.READ_ALL not in user_permissions):
return await query.answer(text = (await Settings.get(Settings.APP_STRINGS_FORBIDDEN)))
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))
if can_edit:
for edit_buttons_row in entity_descriptor.edit_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}"
else:
btn_text = (f"✏️ {get_callable_str(field_descriptor.caption, field_descriptor, entity_item, field_value)}"
if field_descriptor.caption else f"✏️ {field_name}")
btn_row.append(
InlineKeyboardButton(
text = btn_text,
callback_data = ContextData(
command = CallbackCommand.FIELD_EDITOR,
context = CommandContext.ENTITY_FIELD_EDIT,
entity_name = entity_descriptor.name,
entity_id = str(entity_item.id),
field_name = field_name).pack()))
if btn_row:
keyboard_builder.row(*btn_row)
edit_delete_row = []
if can_edit and entity_descriptor.edit_button_visible:
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:
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()))
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
item_text = f"{entity_caption or entity_descriptor.name}: {entity_item_name}"
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" {value}" 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()))
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