init
This commit is contained in:
commit
6dbe0536ca
94 files changed
+3467
No files matched your search
@@ -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
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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())
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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()))
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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