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