init
This commit is contained in:
371
bot/handlers/editors/__init__.py
Normal file
371
bot/handlers/editors/__init__.py
Normal file
@@ -0,0 +1,371 @@
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
from types import NoneType, UnionType
|
||||
from typing import get_args, get_origin
|
||||
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 ....main import QBotApp
|
||||
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 ..common import (get_value_repr, authorize_command, get_callable_str,
|
||||
get_entity_descriptor, get_field_descriptor)
|
||||
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
|
||||
|
||||
|
||||
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 settings_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)
|
||||
await state.clear()
|
||||
await state.update_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 = await get_navigation_context(state = state)
|
||||
|
||||
return await parameters_menu(message = message,
|
||||
navigation_stack = stack,
|
||||
**kwargs)
|
||||
|
||||
current_value = await Settings.get(field_descriptor)
|
||||
else:
|
||||
entity_descriptor = get_entity_descriptor(app, callback_data)
|
||||
field_descriptor = get_field_descriptor(app, callback_data)
|
||||
|
||||
current_value = None
|
||||
|
||||
if not entity_data and callback_data.context == CommandContext.ENTITY_EDIT:
|
||||
if (EntityPermission.READ_ALL in get_user_permissions(user, entity_descriptor) or
|
||||
(EntityPermission.READ in get_user_permissions(user, entity_descriptor) and
|
||||
not issubclass(entity_descriptor.type_, OwnedBotEntity)) or
|
||||
(EntityPermission.READ in get_user_permissions(user, entity_descriptor) and
|
||||
issubclass(entity_descriptor.type_, OwnedBotEntity) and
|
||||
entity_data.user_id == user.id)):
|
||||
|
||||
entity = await entity_descriptor.type_.get(session = kwargs["db_session"], id = int(callback_data.entity_id))
|
||||
if entity:
|
||||
entity_data = {key: serialize(getattr(entity, key), entity_descriptor.fields_descriptors[key]) for key in entity_descriptor.field_sequence}
|
||||
await state.update_data({"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))
|
||||
|
||||
|
||||
await show_editor(message = message,
|
||||
field_descriptor = field_descriptor,
|
||||
entity_descriptor = entity_descriptor,
|
||||
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"]
|
||||
|
||||
value_type = field_descriptor.type_
|
||||
|
||||
if field_descriptor.edit_prompt:
|
||||
edit_prompt = get_callable_str(field_descriptor.edit_prompt, field_descriptor, None, current_value)
|
||||
else:
|
||||
if field_descriptor.caption_str:
|
||||
caption_str = get_callable_str(field_descriptor.caption_str, 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 == UnionType:
|
||||
args = get_args(value_type)
|
||||
if args[1] == NoneType:
|
||||
value_type = args[0]
|
||||
|
||||
if value_type not in [int, float, Decimal, str]:
|
||||
await state.update_data({"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):
|
||||
|
||||
callback_data: ContextData = kwargs.get("callback_data", None)
|
||||
app: QBotApp = kwargs["app"]
|
||||
state: FSMContext = kwargs["state"]
|
||||
|
||||
state_data = await state.get_data()
|
||||
|
||||
if isinstance(message, Message):
|
||||
context_data = state_data.get("context_data")
|
||||
if context_data:
|
||||
context_data = ContextData.unpack(context_data)
|
||||
callback_data = context_data
|
||||
value = message.text
|
||||
field_descriptor = get_field_descriptor(app, callback_data)
|
||||
base_type = field_descriptor.type_
|
||||
if get_origin(base_type) == UnionType:
|
||||
args = get_args(base_type)
|
||||
if args[1] == NoneType:
|
||||
base_type = args[0]
|
||||
|
||||
if base_type == 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)
|
||||
|
||||
await state.update_data({"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)
|
||||
|
||||
elif (base_type in [int, float, Decimal]):
|
||||
try:
|
||||
_ = base_type(value) #@IgnoreException
|
||||
except:
|
||||
return await message.answer(text = (await Settings.get(Settings.APP_STRINGS_INVALID_INPUT)))
|
||||
else:
|
||||
if callback_data.data:
|
||||
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"]
|
||||
value = kwargs["value"]
|
||||
field_descriptor: EntityFieldDescriptor = kwargs["field_descriptor"]
|
||||
|
||||
if callback_data.context == CommandContext.SETTING_EDIT:
|
||||
|
||||
await clear_state(state = state)
|
||||
|
||||
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 = await get_navigation_context(state = state)
|
||||
|
||||
return await parameters_menu(message = message,
|
||||
navigation_stack = stack,
|
||||
**kwargs)
|
||||
|
||||
elif callback_data.context in [CommandContext.ENTITY_CREATE, CommandContext.ENTITY_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)
|
||||
|
||||
state_data = await state.get_data()
|
||||
entity_data = state_data.get("entity_data", {})
|
||||
|
||||
if current_index < len(field_sequence) - 1:
|
||||
|
||||
entity_data[field_descriptor.field_name] = value
|
||||
await state.update_data({"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 == CommandContext.ENTITY_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)
|
||||
|
||||
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 == CommandContext.ENTITY_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()
|
||||
|
||||
await clear_state(state = state)
|
||||
|
||||
await route_callback(message = message, back = False, **kwargs)
|
||||
|
||||
|
||||
from ..navigation import get_navigation_context, route_callback, clear_state, save_navigation_context
|
||||
BIN
bot/handlers/editors/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
bot/handlers/editors/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
bot/handlers/editors/__pycache__/bool.cpython-313.pyc
Normal file
BIN
bot/handlers/editors/__pycache__/bool.cpython-313.pyc
Normal file
Binary file not shown.
BIN
bot/handlers/editors/__pycache__/boolean.cpython-313.pyc
Normal file
BIN
bot/handlers/editors/__pycache__/boolean.cpython-313.pyc
Normal file
Binary file not shown.
BIN
bot/handlers/editors/__pycache__/common.cpython-313.pyc
Normal file
BIN
bot/handlers/editors/__pycache__/common.cpython-313.pyc
Normal file
Binary file not shown.
BIN
bot/handlers/editors/__pycache__/date.cpython-313.pyc
Normal file
BIN
bot/handlers/editors/__pycache__/date.cpython-313.pyc
Normal file
Binary file not shown.
BIN
bot/handlers/editors/__pycache__/entity.cpython-313.pyc
Normal file
BIN
bot/handlers/editors/__pycache__/entity.cpython-313.pyc
Normal file
Binary file not shown.
BIN
bot/handlers/editors/__pycache__/list_entity.cpython-313.pyc
Normal file
BIN
bot/handlers/editors/__pycache__/list_entity.cpython-313.pyc
Normal file
Binary file not shown.
BIN
bot/handlers/editors/__pycache__/string.cpython-313.pyc
Normal file
BIN
bot/handlers/editors/__pycache__/string.cpython-313.pyc
Normal file
Binary file not shown.
71
bot/handlers/editors/boolean.py
Normal file
71
bot/handlers/editors/boolean.py
Normal file
@@ -0,0 +1,71 @@
|
||||
from aiogram import Router
|
||||
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 logging import getLogger
|
||||
|
||||
from ....model.descriptors import EntityFieldDescriptor, EntityDescriptor
|
||||
from ..context import ContextData, CallbackCommand
|
||||
from ..common import get_send_message
|
||||
from .common import wrap_editor
|
||||
|
||||
|
||||
logger = getLogger(__name__)
|
||||
router = Router()
|
||||
|
||||
|
||||
async def bool_editor(message: Message | CallbackQuery,
|
||||
edit_prompt: str,
|
||||
entity_descriptor: EntityDescriptor,
|
||||
field_descriptor: EntityFieldDescriptor,
|
||||
callback_data: ContextData,
|
||||
**kwargs):
|
||||
|
||||
keyboard_builder = InlineKeyboardBuilder()
|
||||
|
||||
if isinstance(field_descriptor.bool_true_value_btn, LazyProxy):
|
||||
true_caption = field_descriptor.bool_true_value_btn.value
|
||||
else:
|
||||
true_caption = field_descriptor.bool_true_value_btn
|
||||
|
||||
if isinstance(field_descriptor.bool_false_value_btn, LazyProxy):
|
||||
false_caption = field_descriptor.bool_false_value_btn.value
|
||||
else:
|
||||
false_caption = field_descriptor.bool_false_value_btn
|
||||
|
||||
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())
|
||||
)
|
||||
|
||||
await wrap_editor(keyboard_builder = keyboard_builder,
|
||||
field_descriptor = field_descriptor,
|
||||
entity_descriptor = entity_descriptor,
|
||||
callback_data = callback_data,
|
||||
state = kwargs["state"])
|
||||
|
||||
send_message = get_send_message(message)
|
||||
|
||||
await send_message(text = edit_prompt, reply_markup = keyboard_builder.as_markup())
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
67
bot/handlers/editors/common.py
Normal file
67
bot/handlers/editors/common.py
Normal file
@@ -0,0 +1,67 @@
|
||||
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 ....model.descriptors import EntityFieldDescriptor, EntityDescriptor
|
||||
from ....model.settings import Settings
|
||||
from ..context import ContextData, CallbackCommand, CommandContext
|
||||
from ..navigation import get_navigation_context
|
||||
|
||||
|
||||
async def wrap_editor(keyboard_builder: InlineKeyboardBuilder,
|
||||
field_descriptor: EntityFieldDescriptor,
|
||||
entity_descriptor: EntityDescriptor,
|
||||
callback_data: ContextData,
|
||||
state: FSMContext):
|
||||
|
||||
if callback_data.context in [CommandContext.ENTITY_CREATE, CommandContext.ENTITY_EDIT]:
|
||||
|
||||
btns = []
|
||||
field_index = entity_descriptor.field_sequence.index(field_descriptor.name)
|
||||
|
||||
stack, context = await get_navigation_context(state)
|
||||
|
||||
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],
|
||||
save_state = True).pack()))
|
||||
|
||||
if get_origin(field_descriptor.type_) == UnionType:
|
||||
args = get_args(field_descriptor.type_)
|
||||
if args[1] == NoneType:
|
||||
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,
|
||||
save_state = True).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,
|
||||
data = "cancel").pack()))
|
||||
|
||||
167
bot/handlers/editors/date.py
Normal file
167
bot/handlers/editors/date.py
Normal file
@@ -0,0 +1,167 @@
|
||||
from datetime import datetime, timedelta
|
||||
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 ....main import QBotApp
|
||||
from ....model.descriptors import EntityFieldDescriptor, EntityDescriptor
|
||||
from ..context import ContextData, CallbackCommand
|
||||
from ..common import get_send_message, get_field_descriptor, get_entity_descriptor
|
||||
from .common import wrap_editor
|
||||
|
||||
|
||||
logger = getLogger(__name__)
|
||||
router = Router()
|
||||
|
||||
|
||||
async def date_picker(message: Message | CallbackQuery,
|
||||
field_descriptor: EntityFieldDescriptor,
|
||||
entity_descriptor: EntityDescriptor,
|
||||
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)
|
||||
|
||||
await wrap_editor(keyboard_builder = keyboard_builder,
|
||||
field_descriptor = field_descriptor,
|
||||
entity_descriptor = entity_descriptor,
|
||||
callback_data = callback_data,
|
||||
state = state)
|
||||
|
||||
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")
|
||||
|
||||
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"),
|
||||
save_state = True).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"),
|
||||
save_state = True).pack()))
|
||||
|
||||
field_descriptor = get_field_descriptor(app, callback_data)
|
||||
entity_descriptor = get_entity_descriptor(app, callback_data)
|
||||
|
||||
await wrap_editor(keyboard_builder = keyboard_builder,
|
||||
field_descriptor = field_descriptor,
|
||||
entity_descriptor = entity_descriptor,
|
||||
callback_data = callback_data,
|
||||
state = state)
|
||||
|
||||
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):
|
||||
|
||||
entity_descriptor = get_entity_descriptor(app, callback_data)
|
||||
field_descriptor = get_field_descriptor(app, callback_data)
|
||||
|
||||
await date_picker(query.message,
|
||||
field_descriptor = field_descriptor,
|
||||
entity_descriptor = entity_descriptor,
|
||||
callback_data = callback_data,
|
||||
current_value = datetime.strptime(callback_data.data, "%Y-%m-%d"),
|
||||
**kwargs)
|
||||
193
bot/handlers/editors/entity.py
Normal file
193
bot/handlers/editors/entity.py
Normal file
@@ -0,0 +1,193 @@
|
||||
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.ext.asyncio.session import AsyncSession
|
||||
from typing import get_args, get_origin
|
||||
|
||||
from ....main import QBotApp
|
||||
from ....model.bot_entity import BotEntity
|
||||
from ....model.bot_enum import BotEnum
|
||||
from ....model.settings import Settings
|
||||
from ....model.user import UserBase
|
||||
from ....model.descriptors import EntityFieldDescriptor, EntityDescriptor
|
||||
from ....utils 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)
|
||||
from .common import wrap_editor
|
||||
|
||||
|
||||
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],
|
||||
state: FSMContext,
|
||||
**kwargs):
|
||||
|
||||
await state.update_data({"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,
|
||||
state = state,
|
||||
current_value = current_value,
|
||||
edit_prompt = edit_prompt,
|
||||
**kwargs)
|
||||
|
||||
|
||||
async def render_entity_picker(*,
|
||||
field_descriptor: EntityFieldDescriptor,
|
||||
entity_descriptor: EntityDescriptor,
|
||||
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_
|
||||
|
||||
if not issubclass(type_, BotEntity) and not issubclass(type_, BotEnum):
|
||||
raise ValueError("Unsupported type")
|
||||
|
||||
page_size = await Settings.get(Settings.PAGE_SIZE)
|
||||
|
||||
if issubclass(type_, BotEnum):
|
||||
items_count = len(type_.all_members)
|
||||
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:
|
||||
items_count = await type_.get_count(session = db_session)
|
||||
entity_items = await type_.get_multi(session = db_session, order_by = type_.name, skip = page_size * (page - 1), limit = page_size)
|
||||
items = [{"text": f"{"" if not is_list else "【✔︎】 " if item in (current_value or []) else "【 】 "}{
|
||||
type_.bot_entity_descriptor.item_caption_btn(type_.bot_entity_descriptor, item) if type_.bot_entity_descriptor.item_caption_btn
|
||||
else get_local_text(item.name, user.lang) if field_descriptor.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)
|
||||
|
||||
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 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()))
|
||||
|
||||
await wrap_editor(keyboard_builder = keyboard_builder,
|
||||
field_descriptor = field_descriptor,
|
||||
entity_descriptor = entity_descriptor,
|
||||
callback_data = callback_data,
|
||||
state = state)
|
||||
|
||||
send_message = get_send_message(message)
|
||||
|
||||
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):
|
||||
|
||||
state_data = await state.get_data()
|
||||
|
||||
field_descriptor = get_field_descriptor(app = app, callback_data = callback_data)
|
||||
entity_descriptor = get_entity_descriptor(app = app, callback_data = callback_data)
|
||||
|
||||
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"])
|
||||
|
||||
if callback_data.command == CallbackCommand.ENTITY_PICKER_TOGGLE_ITEM:
|
||||
page, id_value = callback_data.data.split("&")
|
||||
page = int(page)
|
||||
type_ = get_args(field_descriptor.type_)[0]
|
||||
if issubclass(type_, BotEnum):
|
||||
item = type_(id_value)
|
||||
if item in value:
|
||||
value.remove(item)
|
||||
else:
|
||||
value.append(item)
|
||||
else:
|
||||
item = await type_.get(session = db_session, id = int(id_value))
|
||||
if item in value:
|
||||
value.remove(item)
|
||||
else:
|
||||
value.append(item)
|
||||
|
||||
await state.update_data({"value": serialize(value, field_descriptor)})
|
||||
elif callback_data.command == CallbackCommand.ENTITY_PICKER_PAGE:
|
||||
if callback_data.data == "skip":
|
||||
return
|
||||
page = int(callback_data.data)
|
||||
else:
|
||||
raise ValueError("Unsupported command")
|
||||
|
||||
await render_entity_picker(field_descriptor = field_descriptor,
|
||||
entity_descriptor = entity_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)
|
||||
|
||||
|
||||
94
bot/handlers/editors/string.py
Normal file
94
bot/handlers/editors/string.py
Normal file
@@ -0,0 +1,94 @@
|
||||
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 ....model.descriptors import EntityFieldDescriptor, EntityDescriptor
|
||||
from ....model.language import LanguageBase
|
||||
from ....model.settings import Settings
|
||||
from ....utils import serialize
|
||||
from ..context import ContextData, CallbackCommand
|
||||
from ..common import get_send_message, get_local_text
|
||||
from .common import wrap_editor
|
||||
|
||||
|
||||
logger = getLogger(__name__)
|
||||
router = Router()
|
||||
|
||||
|
||||
async def string_editor(message: Message | CallbackQuery,
|
||||
field_descriptor: EntityFieldDescriptor,
|
||||
entity_descriptor: EntityDescriptor,
|
||||
callback_data: ContextData,
|
||||
current_value: Any,
|
||||
edit_prompt: str,
|
||||
state: FSMContext,
|
||||
locale_index: int = 0,
|
||||
**kwargs):
|
||||
|
||||
keyboard_builder = InlineKeyboardBuilder()
|
||||
|
||||
_edit_prompt = edit_prompt
|
||||
|
||||
type_ = field_descriptor.type_
|
||||
type_origin = get_origin(type_)
|
||||
if type_origin == UnionType:
|
||||
type_ = get_args(type_)[0]
|
||||
|
||||
if type_ == 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)
|
||||
|
||||
await state.update_data({
|
||||
"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)
|
||||
|
||||
_current_value = serialize(current_value, field_descriptor)
|
||||
|
||||
await state.update_data({
|
||||
"context_data": context_data.pack()})
|
||||
|
||||
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)))
|
||||
|
||||
await wrap_editor(keyboard_builder = keyboard_builder,
|
||||
field_descriptor = field_descriptor,
|
||||
entity_descriptor = entity_descriptor,
|
||||
callback_data = callback_data,
|
||||
state = state)
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user