from typing import TYPE_CHECKING 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.ext.asyncio.session import AsyncSession from ....model.bot_entity import BotEntity from ....model.settings import Settings from ....model.user import UserBase from ....model.view_setting import ViewSetting from ....model.descriptors import EntityDescriptor, Filter from ....model import EntityPermission 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.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 logger = getLogger(__name__) 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) 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 _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 ) 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=form_item.edit_field_sequence[0], form_params=form_list.item_form, ).pack(), ) ) if form_list.filtering: entity_filter = await ViewSetting.get_filter( session=db_session, user_id=user.id, entity_name=entity_descriptor.class_name, ) else: 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 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 = f"{entity_descriptor.name}: {item.id}" keyboard_builder.row( InlineKeyboardButton( 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 form_list.caption: entity_text = get_callable_str(form_list.caption, entity_descriptor) else: 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"] await state.set_data(state_data) send_message = get_send_message(message) await send_message(text=entity_text, reply_markup=keyboard_builder.as_markup())