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