add bot entity's events, external bot command call
This commit is contained in:
@@ -19,4 +19,5 @@ from .model.descriptors import (
|
|||||||
CommandButton as CommandButton,
|
CommandButton as CommandButton,
|
||||||
FieldEditButton as FieldEditButton,
|
FieldEditButton as FieldEditButton,
|
||||||
InlineButton as InlineButton,
|
InlineButton as InlineButton,
|
||||||
|
FormField as FormField,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ def add_pagination_controls(
|
|||||||
):
|
):
|
||||||
if total_pages > 1:
|
if total_pages > 1:
|
||||||
navigation_buttons = []
|
navigation_buttons = []
|
||||||
ContextData(**callback_data.model_dump()).__setattr__
|
# ContextData(**callback_data.model_dump()).__setattr__
|
||||||
if total_pages > 10:
|
if total_pages > 10:
|
||||||
navigation_buttons.append(
|
navigation_buttons.append(
|
||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
|
|||||||
@@ -12,17 +12,18 @@ from ....utils.navigation import (
|
|||||||
pop_navigation_context,
|
pop_navigation_context,
|
||||||
)
|
)
|
||||||
|
|
||||||
import quickbot.bot.handlers.menu.main as menu_main
|
|
||||||
import quickbot.bot.handlers.menu.settings as menu_settings
|
|
||||||
import quickbot.bot.handlers.menu.parameters as menu_parameters
|
|
||||||
import quickbot.bot.handlers.menu.language as menu_language
|
|
||||||
import quickbot.bot.handlers.menu.entities as menu_entities
|
|
||||||
import quickbot.bot.handlers.forms.entity_list as form_list
|
|
||||||
import quickbot.bot.handlers.forms.entity_form as form_item
|
|
||||||
import quickbot.bot.handlers.editors.main as editor
|
|
||||||
|
|
||||||
|
|
||||||
async def route_callback(message: Message | CallbackQuery, back: bool = True, **kwargs):
|
async def route_callback(message: Message | CallbackQuery, back: bool = True, **kwargs):
|
||||||
|
import quickbot.bot.handlers.menu.main as menu_main
|
||||||
|
import quickbot.bot.handlers.menu.language as menu_language
|
||||||
|
import quickbot.bot.handlers.menu.settings as menu_settings
|
||||||
|
import quickbot.bot.handlers.menu.parameters as menu_parameters
|
||||||
|
import quickbot.bot.handlers.menu.entities as menu_entities
|
||||||
|
import quickbot.bot.handlers.forms.entity_list as form_list
|
||||||
|
import quickbot.bot.handlers.forms.entity_form as form_item
|
||||||
|
import quickbot.bot.handlers.editors.main as editor
|
||||||
|
import quickbot.bot.handlers.user_handlers.main as user_handler
|
||||||
|
|
||||||
state_data = kwargs["state_data"]
|
state_data = kwargs["state_data"]
|
||||||
stack, context = get_navigation_context(state_data)
|
stack, context = get_navigation_context(state_data)
|
||||||
if back:
|
if back:
|
||||||
@@ -47,12 +48,10 @@ async def route_callback(message: Message | CallbackQuery, back: bool = True, **
|
|||||||
elif context.command == CallbackCommand.FIELD_EDITOR:
|
elif context.command == CallbackCommand.FIELD_EDITOR:
|
||||||
await editor.field_editor(message, **kwargs)
|
await editor.field_editor(message, **kwargs)
|
||||||
elif context.command == CallbackCommand.USER_COMMAND:
|
elif context.command == CallbackCommand.USER_COMMAND:
|
||||||
import quickbot.bot.handlers.user_handlers.main as user_handler
|
|
||||||
|
|
||||||
app: "QBotApp" = kwargs["app"]
|
app: "QBotApp" = kwargs["app"]
|
||||||
cmd = app.bot_commands.get(context.user_command.split("&")[0])
|
cmd = app.bot_commands.get(context.user_command.split("&")[0])
|
||||||
|
|
||||||
await user_handler.cammand_handler(message=message, cmd=cmd, **kwargs)
|
await user_handler.command_handler(message=message, cmd=cmd, **kwargs)
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Unknown command {context.command}")
|
raise ValueError(f"Unknown command {context.command}")
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ class CallbackCommand(StrEnum):
|
|||||||
ENTITY_PICKER_TOGGLE_ITEM = "et"
|
ENTITY_PICKER_TOGGLE_ITEM = "et"
|
||||||
VIEW_FILTER_EDIT = "vf"
|
VIEW_FILTER_EDIT = "vf"
|
||||||
USER_COMMAND = "uc"
|
USER_COMMAND = "uc"
|
||||||
|
DELETE_MESSAGE = "dl"
|
||||||
|
|
||||||
|
|
||||||
class CommandContext(StrEnum):
|
class CommandContext(StrEnum):
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
from aiogram.types import Message, CallbackQuery
|
from aiogram.types import Message, CallbackQuery
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from datetime import datetime, time
|
from datetime import datetime, time
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from quickbot.main import QBotApp
|
if TYPE_CHECKING:
|
||||||
|
from quickbot.main import QBotApp
|
||||||
from quickbot.utils.serialization import deserialize
|
from quickbot.utils.serialization import deserialize
|
||||||
|
|
||||||
from ....model.bot_entity import BotEntity
|
from ....model.bot_entity import BotEntity
|
||||||
@@ -25,7 +27,7 @@ async def show_editor(message: Message | CallbackQuery, **kwargs):
|
|||||||
callback_data: ContextData = kwargs.get("callback_data", None)
|
callback_data: ContextData = kwargs.get("callback_data", None)
|
||||||
state_data: dict = kwargs["state_data"]
|
state_data: dict = kwargs["state_data"]
|
||||||
db_session = kwargs["db_session"]
|
db_session = kwargs["db_session"]
|
||||||
app: QBotApp = kwargs["app"]
|
app: "QBotApp" = kwargs["app"]
|
||||||
|
|
||||||
value_type = field_descriptor.type_base
|
value_type = field_descriptor.type_base
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from inspect import iscoroutinefunction
|
||||||
from aiogram import Router, F
|
from aiogram import Router, F
|
||||||
from aiogram.fsm.context import FSMContext
|
from aiogram.fsm.context import FSMContext
|
||||||
from aiogram.types import Message, CallbackQuery, InlineKeyboardButton
|
from aiogram.types import Message, CallbackQuery, InlineKeyboardButton
|
||||||
@@ -14,13 +15,14 @@ from ....model.bot_enum import BotEnum
|
|||||||
from ....model.settings import Settings
|
from ....model.settings import Settings
|
||||||
from ....model.user import UserBase
|
from ....model.user import UserBase
|
||||||
from ....model.view_setting import ViewSetting
|
from ....model.view_setting import ViewSetting
|
||||||
from ....model.descriptors import BotContext, EntityList, FieldDescriptor, Filter
|
from ....model.descriptors import BotContext, EntityList, FieldDescriptor
|
||||||
from ....model import EntityPermission
|
from ....model import EntityPermission
|
||||||
from ....utils.main import (
|
from ....utils.main import (
|
||||||
get_user_permissions,
|
get_user_permissions,
|
||||||
get_send_message,
|
get_send_message,
|
||||||
get_field_descriptor,
|
get_field_descriptor,
|
||||||
get_callable_str,
|
get_callable_str,
|
||||||
|
prepare_static_filter,
|
||||||
)
|
)
|
||||||
from ....utils.serialization import serialize, deserialize
|
from ....utils.serialization import serialize, deserialize
|
||||||
from ..context import ContextData, CallbackCommand
|
from ..context import ContextData, CallbackCommand
|
||||||
@@ -108,8 +110,34 @@ async def render_entity_picker(
|
|||||||
for item in enum_items
|
for item in enum_items
|
||||||
]
|
]
|
||||||
elif issubclass(type_, BotEntity):
|
elif issubclass(type_, BotEntity):
|
||||||
|
ep_form = "default"
|
||||||
|
ep_form_params = []
|
||||||
|
|
||||||
|
if field_descriptor.ep_form:
|
||||||
|
if callable(field_descriptor.ep_form):
|
||||||
|
context = BotContext(
|
||||||
|
db_session=db_session,
|
||||||
|
app=kwargs["app"],
|
||||||
|
app_state=kwargs["app_state"],
|
||||||
|
user=user,
|
||||||
|
message=message,
|
||||||
|
)
|
||||||
|
|
||||||
|
if iscoroutinefunction(field_descriptor.ep_form):
|
||||||
|
ep_form = await field_descriptor.ep_form(context)
|
||||||
|
else:
|
||||||
|
ep_form = field_descriptor.ep_form(context)
|
||||||
|
|
||||||
|
else:
|
||||||
|
ep_form = field_descriptor.ep_form
|
||||||
|
|
||||||
|
ep_form_list = ep_form.split("&")
|
||||||
|
|
||||||
|
ep_form = ep_form_list[0]
|
||||||
|
ep_form_params = ep_form_list[1:] if len(ep_form_list) > 1 else []
|
||||||
|
|
||||||
form_list: EntityList = type_.bot_entity_descriptor.lists.get(
|
form_list: EntityList = type_.bot_entity_descriptor.lists.get(
|
||||||
field_descriptor.ep_form, type_.bot_entity_descriptor.default_list
|
ep_form, type_.bot_entity_descriptor.default_list
|
||||||
)
|
)
|
||||||
permissions = get_user_permissions(user, type_.bot_entity_descriptor)
|
permissions = get_user_permissions(user, type_.bot_entity_descriptor)
|
||||||
if form_list.filtering:
|
if form_list.filtering:
|
||||||
@@ -125,7 +153,7 @@ async def render_entity_picker(
|
|||||||
if list_all or EntityPermission.LIST in permissions:
|
if list_all or EntityPermission.LIST in permissions:
|
||||||
if (
|
if (
|
||||||
field_descriptor.ep_parent_field
|
field_descriptor.ep_parent_field
|
||||||
and field_descriptor.ep_parent_field
|
and field_descriptor.ep_child_field
|
||||||
and callback_data.entity_id
|
and callback_data.entity_id
|
||||||
):
|
):
|
||||||
entity = await field_descriptor.entity_descriptor.type_.get(
|
entity = await field_descriptor.entity_descriptor.type_.get(
|
||||||
@@ -140,19 +168,11 @@ async def render_entity_picker(
|
|||||||
if form_list.pagination:
|
if form_list.pagination:
|
||||||
items_count = await type_.get_count(
|
items_count = await type_.get_count(
|
||||||
session=db_session,
|
session=db_session,
|
||||||
static_filter=(
|
static_filter=await prepare_static_filter(
|
||||||
[
|
db_session=db_session,
|
||||||
Filter(
|
entity_descriptor=type_.bot_entity_descriptor,
|
||||||
field_name=f.field_name,
|
static_filters=form_list.static_filters,
|
||||||
operator=f.operator,
|
params=ep_form_params,
|
||||||
value_type="const",
|
|
||||||
value=f.value,
|
|
||||||
)
|
|
||||||
for f in form_list.static_filters
|
|
||||||
if f.value_type == "const"
|
|
||||||
]
|
|
||||||
if isinstance(form_list.static_filters, list)
|
|
||||||
else form_list.static_filters
|
|
||||||
),
|
),
|
||||||
ext_filter=ext_filter,
|
ext_filter=ext_filter,
|
||||||
filter=entity_filter,
|
filter=entity_filter,
|
||||||
@@ -169,19 +189,11 @@ async def render_entity_picker(
|
|||||||
entity_items = await type_.get_multi(
|
entity_items = await type_.get_multi(
|
||||||
session=db_session,
|
session=db_session,
|
||||||
order_by=form_list.order_by,
|
order_by=form_list.order_by,
|
||||||
static_filter=(
|
static_filter=await prepare_static_filter(
|
||||||
[
|
db_session=db_session,
|
||||||
Filter(
|
entity_descriptor=type_.bot_entity_descriptor,
|
||||||
field_name=f.field_name,
|
static_filters=form_list.static_filters,
|
||||||
operator=f.operator,
|
params=ep_form_params,
|
||||||
value_type="const",
|
|
||||||
value=f.value,
|
|
||||||
)
|
|
||||||
for f in form_list.static_filters
|
|
||||||
if f.value_type == "const"
|
|
||||||
]
|
|
||||||
if isinstance(form_list.static_filters, list)
|
|
||||||
else form_list.static_filters
|
|
||||||
),
|
),
|
||||||
ext_filter=ext_filter,
|
ext_filter=ext_filter,
|
||||||
filter=entity_filter,
|
filter=entity_filter,
|
||||||
@@ -313,9 +325,14 @@ async def render_entity_picker(
|
|||||||
|
|
||||||
await state.set_data(state_data)
|
await state.set_data(state_data)
|
||||||
|
|
||||||
send_message = get_send_message(message)
|
if message:
|
||||||
|
send_message = get_send_message(message)
|
||||||
await send_message(text=edit_prompt, reply_markup=keyboard_builder.as_markup())
|
await send_message(text=edit_prompt, reply_markup=keyboard_builder.as_markup())
|
||||||
|
else:
|
||||||
|
app: "QBotApp" = kwargs["app"]
|
||||||
|
await app.bot.send_message(
|
||||||
|
chat_id=user.id, text=edit_prompt, reply_markup=keyboard_builder.as_markup()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(
|
@router.callback_query(
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
|
from inspect import iscoroutinefunction
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
from aiogram import Router, F
|
from aiogram import Router, F
|
||||||
from aiogram.fsm.context import FSMContext
|
from aiogram.fsm.context import FSMContext
|
||||||
from aiogram.types import Message, CallbackQuery
|
from aiogram.types import Message, CallbackQuery
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
|
from sqlalchemy.orm.collections import InstrumentedList
|
||||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||||
|
|
||||||
from quickbot.model.descriptors import BotContext, EntityForm
|
from quickbot.model.descriptors import BotContext, EntityForm
|
||||||
|
|
||||||
from ....model import EntityPermission
|
from ....model import EntityPermission
|
||||||
@@ -14,6 +15,7 @@ from ....utils.main import (
|
|||||||
build_field_sequence,
|
build_field_sequence,
|
||||||
check_entity_permission,
|
check_entity_permission,
|
||||||
get_field_descriptor,
|
get_field_descriptor,
|
||||||
|
clear_state,
|
||||||
)
|
)
|
||||||
from ....utils.serialization import deserialize, serialize
|
from ....utils.serialization import deserialize, serialize
|
||||||
from ..context import ContextData, CallbackCommand, CommandContext
|
from ..context import ContextData, CallbackCommand, CommandContext
|
||||||
@@ -92,6 +94,14 @@ async def field_editor(message: Message | CallbackQuery, **kwargs):
|
|||||||
|
|
||||||
current_value = None
|
current_value = None
|
||||||
|
|
||||||
|
context = BotContext(
|
||||||
|
db_session=db_session,
|
||||||
|
app=app,
|
||||||
|
app_state=kwargs["app_state"],
|
||||||
|
user=user,
|
||||||
|
message=message,
|
||||||
|
)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
field_descriptor.type_base is bool
|
field_descriptor.type_base is bool
|
||||||
and callback_data.context == CommandContext.ENTITY_FIELD_EDIT
|
and callback_data.context == CommandContext.ENTITY_FIELD_EDIT
|
||||||
@@ -102,19 +112,73 @@ async def field_editor(message: Message | CallbackQuery, **kwargs):
|
|||||||
if check_entity_permission(
|
if check_entity_permission(
|
||||||
entity=entity, user=user, permission=EntityPermission.UPDATE
|
entity=entity, user=user, permission=EntityPermission.UPDATE
|
||||||
):
|
):
|
||||||
|
old_values = {}
|
||||||
|
|
||||||
|
for f in entity.bot_entity_descriptor.fields_descriptors.values():
|
||||||
|
value = getattr(entity, f.field_name)
|
||||||
|
if isinstance(value, InstrumentedList):
|
||||||
|
value = list(value)
|
||||||
|
old_values[f.field_name] = value
|
||||||
|
|
||||||
|
new_values = old_values.copy()
|
||||||
|
|
||||||
current_value: bool = (
|
current_value: bool = (
|
||||||
getattr(entity, field_descriptor.field_name) or False
|
getattr(entity, field_descriptor.field_name) or False
|
||||||
)
|
)
|
||||||
setattr(entity, field_descriptor.field_name, not current_value)
|
new_values[field_descriptor.field_name] = not current_value
|
||||||
|
|
||||||
await db_session.commit()
|
can_update = True
|
||||||
stack, context = get_navigation_context(state_data=state_data)
|
|
||||||
|
|
||||||
kwargs.update({"callback_data": context})
|
if entity_descriptor.before_update_save:
|
||||||
|
if iscoroutinefunction(entity_descriptor.before_update_save):
|
||||||
|
can_update = await entity_descriptor.before_update_save(
|
||||||
|
old_values,
|
||||||
|
new_values,
|
||||||
|
context,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
can_update = entity_descriptor.before_update_save(
|
||||||
|
old_values,
|
||||||
|
new_values,
|
||||||
|
context,
|
||||||
|
)
|
||||||
|
if isinstance(can_update, str):
|
||||||
|
await message.answer(text=can_update, **{"show_alert": True})
|
||||||
|
elif not can_update:
|
||||||
|
await message.answer(
|
||||||
|
text=(await Settings.get(Settings.APP_STRINGS_FORBIDDEN)),
|
||||||
|
**{"show_alert": True},
|
||||||
|
)
|
||||||
|
|
||||||
return await entity_item(
|
if isinstance(can_update, bool) and can_update:
|
||||||
query=message, navigation_stack=stack, **kwargs
|
for attr in new_values:
|
||||||
)
|
if attr != "id":
|
||||||
|
setattr(entity, attr, new_values[attr])
|
||||||
|
|
||||||
|
await db_session.commit()
|
||||||
|
|
||||||
|
if entity_descriptor.on_updated:
|
||||||
|
if iscoroutinefunction(entity_descriptor.on_updated):
|
||||||
|
await entity_descriptor.on_updated(
|
||||||
|
old_values,
|
||||||
|
entity,
|
||||||
|
context,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
entity_descriptor.on_updated(
|
||||||
|
old_values,
|
||||||
|
entity,
|
||||||
|
context,
|
||||||
|
)
|
||||||
|
|
||||||
|
stack, context_data = get_navigation_context(state_data=state_data)
|
||||||
|
kwargs.update({"callback_data": context_data})
|
||||||
|
|
||||||
|
return await entity_item(
|
||||||
|
query=message, navigation_stack=stack, **kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
if not entity_data and callback_data.context in [
|
if not entity_data and callback_data.context in [
|
||||||
CommandContext.ENTITY_EDIT,
|
CommandContext.ENTITY_EDIT,
|
||||||
@@ -138,13 +202,6 @@ async def field_editor(message: Message | CallbackQuery, **kwargs):
|
|||||||
if form.edit_field_sequence:
|
if form.edit_field_sequence:
|
||||||
field_sequence = form.edit_field_sequence
|
field_sequence = form.edit_field_sequence
|
||||||
else:
|
else:
|
||||||
context = BotContext(
|
|
||||||
db_session=db_session,
|
|
||||||
app=app,
|
|
||||||
app_state=kwargs["app_state"],
|
|
||||||
user=user,
|
|
||||||
message=message,
|
|
||||||
)
|
|
||||||
field_sequence = await build_field_sequence(
|
field_sequence = await build_field_sequence(
|
||||||
entity_descriptor=entity_descriptor,
|
entity_descriptor=entity_descriptor,
|
||||||
user=user,
|
user=user,
|
||||||
@@ -168,6 +225,24 @@ async def field_editor(message: Message | CallbackQuery, **kwargs):
|
|||||||
}
|
}
|
||||||
state_data.update({"entity_data": entity_data})
|
state_data.update({"entity_data": entity_data})
|
||||||
|
|
||||||
|
if callback_data.context == CommandContext.ENTITY_CREATE:
|
||||||
|
if entity_descriptor.before_create:
|
||||||
|
if iscoroutinefunction(entity_descriptor.before_create):
|
||||||
|
can_create = await entity_descriptor.before_create(
|
||||||
|
context,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
can_create = entity_descriptor.before_create(
|
||||||
|
context,
|
||||||
|
)
|
||||||
|
if isinstance(can_create, str):
|
||||||
|
return await message.answer(text=can_create, **{"show_alert": True})
|
||||||
|
elif not can_create:
|
||||||
|
return await message.answer(
|
||||||
|
text=(await Settings.get(Settings.APP_STRINGS_FORBIDDEN)),
|
||||||
|
**{"show_alert": True},
|
||||||
|
)
|
||||||
|
|
||||||
if entity_data:
|
if entity_data:
|
||||||
current_value = await deserialize(
|
current_value = await deserialize(
|
||||||
session=db_session,
|
session=db_session,
|
||||||
@@ -181,6 +256,16 @@ async def field_editor(message: Message | CallbackQuery, **kwargs):
|
|||||||
await show_editor(message=message, current_value=current_value, **kwargs)
|
await show_editor(message=message, current_value=current_value, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(ContextData.filter(F.command == CallbackCommand.DELETE_MESSAGE))
|
||||||
|
async def delete_message_callback(query: CallbackQuery, **kwargs):
|
||||||
|
state: FSMContext = kwargs["state"]
|
||||||
|
state_data = await state.get_data()
|
||||||
|
clear_state(state_data=state_data)
|
||||||
|
await state.set_data(state_data)
|
||||||
|
|
||||||
|
await query.message.delete()
|
||||||
|
|
||||||
|
|
||||||
router.include_routers(
|
router.include_routers(
|
||||||
string_editor_router,
|
string_editor_router,
|
||||||
date_picker_router,
|
date_picker_router,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from inspect import iscoroutinefunction
|
|||||||
from aiogram import Router, F
|
from aiogram import Router, F
|
||||||
from aiogram.types import Message, CallbackQuery
|
from aiogram.types import Message, CallbackQuery
|
||||||
from aiogram.fsm.context import FSMContext
|
from aiogram.fsm.context import FSMContext
|
||||||
|
from sqlalchemy.orm.collections import InstrumentedList
|
||||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||||
from typing import TYPE_CHECKING, Any
|
from typing import TYPE_CHECKING, Any
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
@@ -9,7 +10,7 @@ import json
|
|||||||
|
|
||||||
from ..context import ContextData, CallbackCommand, CommandContext
|
from ..context import ContextData, CallbackCommand, CommandContext
|
||||||
from ...command_context_filter import CallbackCommandFilter
|
from ...command_context_filter import CallbackCommandFilter
|
||||||
from ..user_handlers.main import cammand_handler
|
from ..user_handlers.main import command_handler
|
||||||
from ....model import EntityPermission
|
from ....model import EntityPermission
|
||||||
from ....model.user import UserBase
|
from ....model.user import UserBase
|
||||||
from ....model.settings import Settings
|
from ....model.settings import Settings
|
||||||
@@ -354,44 +355,68 @@ async def process_field_edit_callback(message: Message | CallbackQuery, **kwargs
|
|||||||
text=(await Settings.get(Settings.APP_STRINGS_FORBIDDEN))
|
text=(await Settings.get(Settings.APP_STRINGS_FORBIDDEN))
|
||||||
)
|
)
|
||||||
|
|
||||||
new_entity = await entity_type.create(
|
new_entity = entity_type(**deser_entity_data)
|
||||||
session=db_session,
|
|
||||||
obj_in=entity_type(**deser_entity_data),
|
|
||||||
commit=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
if entity_descriptor.on_created:
|
can_create = True
|
||||||
if iscoroutinefunction(entity_descriptor.on_created):
|
|
||||||
await entity_descriptor.on_created(
|
if entity_descriptor.before_update_save:
|
||||||
|
if iscoroutinefunction(entity_descriptor.before_update_save):
|
||||||
|
can_create = await entity_descriptor.before_update_save(
|
||||||
new_entity,
|
new_entity,
|
||||||
context,
|
context,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
entity_descriptor.on_created(
|
can_create = entity_descriptor.before_update_save(
|
||||||
new_entity,
|
new_entity,
|
||||||
context,
|
context,
|
||||||
)
|
)
|
||||||
|
if isinstance(can_create, str):
|
||||||
|
await message.answer(text=can_create, **{"show_alert": True})
|
||||||
|
elif not can_create:
|
||||||
|
await message.answer(
|
||||||
|
text=(await Settings.get(Settings.APP_STRINGS_FORBIDDEN)),
|
||||||
|
**{"show_alert": True},
|
||||||
|
)
|
||||||
|
|
||||||
form_name = (
|
if isinstance(can_create, bool) and can_create:
|
||||||
callback_data.form_params.split("&")[0]
|
new_entity = await entity_type.create(
|
||||||
if callback_data.form_params
|
session=db_session,
|
||||||
else None
|
obj_in=new_entity,
|
||||||
)
|
commit=True,
|
||||||
form_list = entity_descriptor.lists.get(
|
)
|
||||||
form_name, entity_descriptor.default_list
|
|
||||||
)
|
|
||||||
|
|
||||||
state_data["navigation_context"] = ContextData(
|
if entity_descriptor.on_created:
|
||||||
command=CallbackCommand.ENTITY_ITEM,
|
if iscoroutinefunction(entity_descriptor.on_created):
|
||||||
entity_name=entity_descriptor.name,
|
await entity_descriptor.on_created(
|
||||||
form_params=form_list.item_form,
|
new_entity,
|
||||||
entity_id=str(new_entity.id),
|
context,
|
||||||
).pack()
|
)
|
||||||
|
else:
|
||||||
|
entity_descriptor.on_created(
|
||||||
|
new_entity,
|
||||||
|
context,
|
||||||
|
)
|
||||||
|
|
||||||
state_data.update(state_data)
|
form_name = (
|
||||||
|
callback_data.form_params.split("&")[0]
|
||||||
|
if callback_data.form_params
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
form_list = entity_descriptor.lists.get(
|
||||||
|
form_name, entity_descriptor.default_list
|
||||||
|
)
|
||||||
|
|
||||||
clear_state(state_data=state_data)
|
state_data["navigation_context"] = ContextData(
|
||||||
return await route_callback(message=message, back=False, **kwargs)
|
command=CallbackCommand.ENTITY_ITEM,
|
||||||
|
entity_name=entity_descriptor.name,
|
||||||
|
form_params=form_list.item_form,
|
||||||
|
entity_id=str(new_entity.id),
|
||||||
|
).pack()
|
||||||
|
|
||||||
|
state_data.update(state_data)
|
||||||
|
|
||||||
|
clear_state(state_data=state_data)
|
||||||
|
return await route_callback(message=message, back=False, **kwargs)
|
||||||
|
|
||||||
elif callback_data.context in [
|
elif callback_data.context in [
|
||||||
CommandContext.ENTITY_EDIT,
|
CommandContext.ENTITY_EDIT,
|
||||||
@@ -412,27 +437,67 @@ async def process_field_edit_callback(message: Message | CallbackQuery, **kwargs
|
|||||||
text=(await Settings.get(Settings.APP_STRINGS_FORBIDDEN))
|
text=(await Settings.get(Settings.APP_STRINGS_FORBIDDEN))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
old_values = {}
|
||||||
|
|
||||||
|
for f in entity.bot_entity_descriptor.fields_descriptors.values():
|
||||||
|
value = getattr(entity, f.field_name)
|
||||||
|
if isinstance(value, InstrumentedList):
|
||||||
|
value = list(value)
|
||||||
|
old_values[f.field_name] = value
|
||||||
|
|
||||||
|
new_values = old_values.copy()
|
||||||
|
|
||||||
for key, value in deser_entity_data.items():
|
for key, value in deser_entity_data.items():
|
||||||
setattr(
|
new_values[
|
||||||
entity,
|
entity.bot_entity_descriptor.fields_descriptors[key].field_name
|
||||||
entity.bot_entity_descriptor.fields_descriptors[key].field_name,
|
] = value
|
||||||
value,
|
|
||||||
)
|
|
||||||
|
|
||||||
await db_session.commit()
|
can_update = True
|
||||||
await db_session.refresh(entity)
|
|
||||||
|
|
||||||
if entity_descriptor.on_updated:
|
if entity_descriptor.before_update_save:
|
||||||
if iscoroutinefunction(entity_descriptor.on_updated):
|
if iscoroutinefunction(entity_descriptor.before_update_save):
|
||||||
await entity_descriptor.on_updated(
|
can_update = await entity_descriptor.before_update_save(
|
||||||
entity,
|
old_values,
|
||||||
|
new_values,
|
||||||
context,
|
context,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
entity_descriptor.on_updated(
|
can_update = entity_descriptor.before_update_save(
|
||||||
entity,
|
old_values,
|
||||||
|
new_values,
|
||||||
context,
|
context,
|
||||||
)
|
)
|
||||||
|
if isinstance(can_update, str):
|
||||||
|
await message.answer(text=can_update, **{"show_alert": True})
|
||||||
|
elif not can_update:
|
||||||
|
await message.answer(
|
||||||
|
text=(await Settings.get(Settings.APP_STRINGS_FORBIDDEN)),
|
||||||
|
**{"show_alert": True},
|
||||||
|
)
|
||||||
|
|
||||||
|
if isinstance(can_update, bool) and can_update:
|
||||||
|
for attr in new_values:
|
||||||
|
if attr != "id":
|
||||||
|
setattr(entity, attr, new_values[attr])
|
||||||
|
|
||||||
|
await db_session.commit()
|
||||||
|
await db_session.refresh(entity)
|
||||||
|
|
||||||
|
if entity_descriptor.on_updated:
|
||||||
|
if iscoroutinefunction(entity_descriptor.on_updated):
|
||||||
|
await entity_descriptor.on_updated(
|
||||||
|
old_values,
|
||||||
|
entity,
|
||||||
|
context,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
entity_descriptor.on_updated(
|
||||||
|
old_values,
|
||||||
|
entity,
|
||||||
|
context,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await db_session.rollback()
|
||||||
|
|
||||||
elif callback_data.context == CommandContext.COMMAND_FORM:
|
elif callback_data.context == CommandContext.COMMAND_FORM:
|
||||||
clear_state(state_data=state_data)
|
clear_state(state_data=state_data)
|
||||||
@@ -449,7 +514,7 @@ async def process_field_edit_callback(message: Message | CallbackQuery, **kwargs
|
|||||||
|
|
||||||
cmd = app.bot_commands.get(callback_data.user_command.split("&")[0])
|
cmd = app.bot_commands.get(callback_data.user_command.split("&")[0])
|
||||||
|
|
||||||
return await cammand_handler(message=message, cmd=cmd, **kwargs)
|
return await command_handler(message=message, cmd=cmd, **kwargs)
|
||||||
|
|
||||||
clear_state(state_data=state_data)
|
clear_state(state_data=state_data)
|
||||||
|
|
||||||
|
|||||||
@@ -99,7 +99,9 @@ async def wrap_editor(
|
|||||||
keyboard_builder.row(
|
keyboard_builder.row(
|
||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
text=(await Settings.get(Settings.APP_STRINGS_CANCEL_BTN)),
|
text=(await Settings.get(Settings.APP_STRINGS_CANCEL_BTN)),
|
||||||
callback_data=context.pack(),
|
callback_data=context.pack()
|
||||||
|
if context
|
||||||
|
else ContextData(command=CallbackCommand.DELETE_MESSAGE).pack(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -58,23 +58,47 @@ async def entity_delete_callback(query: CallbackQuery, **kwargs):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if callback_data.data == "yes":
|
if callback_data.data == "yes":
|
||||||
entity = await entity_descriptor.type_.remove(
|
can_delete = True
|
||||||
session=db_session, id=int(callback_data.entity_id), commit=True
|
|
||||||
)
|
|
||||||
|
|
||||||
if entity_descriptor.on_deleted:
|
if entity_descriptor.before_delete:
|
||||||
if iscoroutinefunction(entity_descriptor.on_created):
|
if iscoroutinefunction(entity_descriptor.before_delete):
|
||||||
await entity_descriptor.on_deleted(
|
can_delete = await entity_descriptor.before_delete(
|
||||||
entity,
|
entity,
|
||||||
context,
|
context,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
entity_descriptor.on_deleted(
|
can_delete = entity_descriptor.before_delete(
|
||||||
entity,
|
entity,
|
||||||
context,
|
context,
|
||||||
)
|
)
|
||||||
|
if isinstance(can_delete, str):
|
||||||
|
await query.answer(text=can_delete, show_alert=True)
|
||||||
|
elif not can_delete:
|
||||||
|
await query.answer(
|
||||||
|
text=(await Settings.get(Settings.APP_STRINGS_FORBIDDEN)),
|
||||||
|
show_alert=True,
|
||||||
|
)
|
||||||
|
|
||||||
await route_callback(message=query, **kwargs)
|
if isinstance(can_delete, bool) and can_delete:
|
||||||
|
await db_session.delete(entity)
|
||||||
|
await db_session.commit()
|
||||||
|
|
||||||
|
if entity_descriptor.on_updated:
|
||||||
|
if iscoroutinefunction(entity_descriptor.on_updated):
|
||||||
|
await entity_descriptor.on_updated(
|
||||||
|
entity,
|
||||||
|
context,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
entity_descriptor.on_updated(
|
||||||
|
entity,
|
||||||
|
context,
|
||||||
|
)
|
||||||
|
|
||||||
|
await route_callback(message=query, **kwargs)
|
||||||
|
|
||||||
|
else:
|
||||||
|
await route_callback(message=query, back=False, **kwargs)
|
||||||
|
|
||||||
elif not callback_data.data:
|
elif not callback_data.data:
|
||||||
entity = await entity_descriptor.type_.get(
|
entity = await entity_descriptor.type_.get(
|
||||||
|
|||||||
@@ -12,10 +12,8 @@ from ....model.user import UserBase
|
|||||||
from ....model.view_setting import ViewSetting
|
from ....model.view_setting import ViewSetting
|
||||||
from ....model.descriptors import (
|
from ....model.descriptors import (
|
||||||
BotContext,
|
BotContext,
|
||||||
EntityDescriptor,
|
|
||||||
EntityForm,
|
EntityForm,
|
||||||
EntityList,
|
EntityList,
|
||||||
Filter,
|
|
||||||
)
|
)
|
||||||
from ....model import EntityPermission
|
from ....model import EntityPermission
|
||||||
from ....utils.main import (
|
from ....utils.main import (
|
||||||
@@ -25,8 +23,8 @@ from ....utils.main import (
|
|||||||
get_entity_descriptor,
|
get_entity_descriptor,
|
||||||
get_callable_str,
|
get_callable_str,
|
||||||
build_field_sequence,
|
build_field_sequence,
|
||||||
|
prepare_static_filter,
|
||||||
)
|
)
|
||||||
from ....utils.serialization import deserialize
|
|
||||||
from ..context import ContextData, CallbackCommand, CommandContext
|
from ..context import ContextData, CallbackCommand, CommandContext
|
||||||
from ..common.pagination import add_pagination_controls
|
from ..common.pagination import add_pagination_controls
|
||||||
from ..common.filtering import add_filter_controls
|
from ..common.filtering import add_filter_controls
|
||||||
@@ -61,37 +59,6 @@ def calc_total_pages(items_count: int, page_size: int) -> int:
|
|||||||
return max(items_count // page_size + (1 if items_count % page_size else 0), 1)
|
return max(items_count // page_size + (1 if items_count % page_size else 0), 1)
|
||||||
|
|
||||||
|
|
||||||
async def _prepare_static_filter(
|
|
||||||
db_session: AsyncSession,
|
|
||||||
entity_descriptor: EntityDescriptor,
|
|
||||||
static_filters: list[Filter],
|
|
||||||
params: list[str],
|
|
||||||
) -> list[Filter]:
|
|
||||||
return (
|
|
||||||
[
|
|
||||||
Filter(
|
|
||||||
field_name=f.field_name,
|
|
||||||
operator=f.operator,
|
|
||||||
value_type="const",
|
|
||||||
value=(
|
|
||||||
f.value
|
|
||||||
if f.value_type == "const"
|
|
||||||
else await deserialize(
|
|
||||||
session=db_session,
|
|
||||||
type_=entity_descriptor.fields_descriptors[
|
|
||||||
f.field_name
|
|
||||||
].type_base,
|
|
||||||
value=params[f.param_index],
|
|
||||||
)
|
|
||||||
),
|
|
||||||
)
|
|
||||||
for f in static_filters
|
|
||||||
]
|
|
||||||
if static_filters
|
|
||||||
else None
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def entity_list(
|
async def entity_list(
|
||||||
message: CallbackQuery | Message,
|
message: CallbackQuery | Message,
|
||||||
callback_data: ContextData,
|
callback_data: ContextData,
|
||||||
@@ -175,7 +142,7 @@ async def entity_list(
|
|||||||
page_size = await Settings.get(Settings.PAGE_SIZE)
|
page_size = await Settings.get(Settings.PAGE_SIZE)
|
||||||
items_count = await entity_type.get_count(
|
items_count = await entity_type.get_count(
|
||||||
session=db_session,
|
session=db_session,
|
||||||
static_filter=await _prepare_static_filter(
|
static_filter=await prepare_static_filter(
|
||||||
db_session=db_session,
|
db_session=db_session,
|
||||||
entity_descriptor=entity_descriptor,
|
entity_descriptor=entity_descriptor,
|
||||||
static_filters=form_list.static_filters,
|
static_filters=form_list.static_filters,
|
||||||
@@ -196,7 +163,7 @@ async def entity_list(
|
|||||||
items = await entity_type.get_multi(
|
items = await entity_type.get_multi(
|
||||||
session=db_session,
|
session=db_session,
|
||||||
order_by=form_list.order_by,
|
order_by=form_list.order_by,
|
||||||
static_filter=await _prepare_static_filter(
|
static_filter=await prepare_static_filter(
|
||||||
db_session=db_session,
|
db_session=db_session,
|
||||||
entity_descriptor=entity_descriptor,
|
entity_descriptor=entity_descriptor,
|
||||||
static_filters=form_list.static_filters,
|
static_filters=form_list.static_filters,
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ from ....model.language import LanguageBase
|
|||||||
from ....model.settings import Settings
|
from ....model.settings import Settings
|
||||||
from ....model.user import UserBase
|
from ....model.user import UserBase
|
||||||
from ..context import ContextData, CallbackCommand
|
from ..context import ContextData, CallbackCommand
|
||||||
from ..common.routing import route_callback
|
|
||||||
from ....utils.main import get_send_message
|
from ....utils.main import get_send_message
|
||||||
|
|
||||||
|
|
||||||
@@ -94,3 +93,6 @@ async def set_language(message: CallbackQuery, **kwargs):
|
|||||||
i18n: I18n = kwargs["i18n"]
|
i18n: I18n = kwargs["i18n"]
|
||||||
with i18n.use_locale(user.lang.value):
|
with i18n.use_locale(user.lang.value):
|
||||||
await route_callback(message, **kwargs)
|
await route_callback(message, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
from ..common.routing import route_callback # noqa: E402
|
||||||
|
|||||||
126
src/quickbot/bot/handlers/user_handlers/command_handler.py
Normal file
126
src/quickbot/bot/handlers/user_handlers/command_handler.py
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
from aiogram.fsm.context import FSMContext
|
||||||
|
from aiogram.types import Message, CallbackQuery, InlineKeyboardButton
|
||||||
|
from inspect import iscoroutinefunction
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from quickbot.utils.main import clear_state
|
||||||
|
from quickbot.utils.navigation import (
|
||||||
|
get_navigation_context,
|
||||||
|
pop_navigation_context,
|
||||||
|
)
|
||||||
|
from quickbot.bot.handlers.editors.main import field_editor
|
||||||
|
from quickbot.utils.serialization import deserialize
|
||||||
|
from quickbot.utils.main import get_send_message
|
||||||
|
from quickbot.model.descriptors import BotCommand, CommandCallbackContext
|
||||||
|
from quickbot.model.settings import Settings
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from quickbot.main import QBotApp
|
||||||
|
from quickbot.model.user import UserBase
|
||||||
|
|
||||||
|
from ..context import ContextData, CallbackCommand, CommandContext
|
||||||
|
|
||||||
|
|
||||||
|
async def command_handler(message: Message | CallbackQuery, cmd: BotCommand, **kwargs):
|
||||||
|
callback_data: ContextData = kwargs["callback_data"]
|
||||||
|
state: FSMContext = kwargs["state"]
|
||||||
|
state_data: dict = kwargs["state_data"]
|
||||||
|
app: "QBotApp" = kwargs["app"]
|
||||||
|
user: "UserBase" = kwargs["user"]
|
||||||
|
|
||||||
|
entity_data_dict: dict = state_data.get("entity_data")
|
||||||
|
form_data = (
|
||||||
|
{
|
||||||
|
key: await deserialize(
|
||||||
|
session=kwargs["db_session"],
|
||||||
|
type_=cmd.param_form[key].type_,
|
||||||
|
value=value,
|
||||||
|
)
|
||||||
|
for key, value in entity_data_dict.items()
|
||||||
|
}
|
||||||
|
if entity_data_dict and cmd.param_form
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
|
||||||
|
callback_context = CommandCallbackContext(
|
||||||
|
message=message,
|
||||||
|
callback_data=callback_data,
|
||||||
|
form_data=form_data,
|
||||||
|
db_session=kwargs["db_session"],
|
||||||
|
user=user,
|
||||||
|
app=app,
|
||||||
|
app_state=kwargs["app_state"],
|
||||||
|
state_data=state_data,
|
||||||
|
state=state,
|
||||||
|
i18n=kwargs["i18n"],
|
||||||
|
register_navigation=cmd.register_navigation,
|
||||||
|
clear_navigation=cmd.clear_navigation,
|
||||||
|
kwargs=kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
if cmd.pre_check and (not cmd.param_form or (cmd.param_form and form_data is None)):
|
||||||
|
if iscoroutinefunction(cmd.pre_check):
|
||||||
|
if not await cmd.pre_check(callback_context):
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
if not cmd.pre_check(callback_context):
|
||||||
|
return
|
||||||
|
|
||||||
|
if form_data is None and cmd.param_form:
|
||||||
|
field_descriptor = list(cmd.param_form.values())[0]
|
||||||
|
kwargs["callback_data"] = ContextData(
|
||||||
|
command=CallbackCommand.FIELD_EDITOR,
|
||||||
|
context=CommandContext.COMMAND_FORM,
|
||||||
|
field_name=field_descriptor.name,
|
||||||
|
user_command=callback_data.user_command,
|
||||||
|
)
|
||||||
|
|
||||||
|
return await field_editor(message=message, **kwargs)
|
||||||
|
|
||||||
|
await cmd.handler(callback_context)
|
||||||
|
|
||||||
|
if callback_context.register_navigation:
|
||||||
|
await state.set_data(state_data)
|
||||||
|
|
||||||
|
stack, navigation_context = get_navigation_context(state_data=state_data)
|
||||||
|
back_callback_data = pop_navigation_context(stack=stack)
|
||||||
|
if back_callback_data:
|
||||||
|
callback_context.keyboard_builder.row(
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text=(await Settings.get(Settings.APP_STRINGS_BACK_BTN)),
|
||||||
|
callback_data=back_callback_data.pack(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if message:
|
||||||
|
send_message = get_send_message(message)
|
||||||
|
|
||||||
|
if isinstance(message, CallbackQuery):
|
||||||
|
message = message.message
|
||||||
|
|
||||||
|
if callback_context.message_text:
|
||||||
|
await send_message(
|
||||||
|
text=callback_context.message_text,
|
||||||
|
reply_markup=callback_context.keyboard_builder.as_markup(),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await message.edit_reply_markup(
|
||||||
|
reply_markup=callback_context.keyboard_builder.as_markup()
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
if callback_context.message_text:
|
||||||
|
await app.bot.send_message(
|
||||||
|
chat_id=user.id,
|
||||||
|
text=callback_context.message_text,
|
||||||
|
reply_markup=callback_context.keyboard_builder.as_markup(),
|
||||||
|
)
|
||||||
|
|
||||||
|
if not callback_context.register_navigation:
|
||||||
|
if callback_context.clear_navigation:
|
||||||
|
clear_state(state_data=state_data, clear_nav=True)
|
||||||
|
await state.set_data(state_data)
|
||||||
|
else:
|
||||||
|
clear_state(state_data=state_data)
|
||||||
|
await route_callback(message, back=True, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
from quickbot.bot.handlers.common.routing import route_callback # noqa: E402
|
||||||
@@ -1,27 +1,16 @@
|
|||||||
from aiogram import Router, F
|
from aiogram import Router, F
|
||||||
from aiogram.fsm.context import FSMContext
|
from aiogram.fsm.context import FSMContext
|
||||||
from aiogram.types import Message, CallbackQuery, InlineKeyboardButton
|
from aiogram.types import Message, CallbackQuery
|
||||||
from inspect import iscoroutinefunction
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from quickbot.utils.main import clear_state
|
from quickbot.utils.main import clear_state
|
||||||
from quickbot.utils.navigation import (
|
from quickbot.utils.navigation import save_navigation_context
|
||||||
save_navigation_context,
|
|
||||||
get_navigation_context,
|
|
||||||
pop_navigation_context,
|
|
||||||
)
|
|
||||||
from quickbot.bot.handlers.editors.main import field_editor
|
|
||||||
from quickbot.bot.handlers.common.routing import route_callback
|
|
||||||
from quickbot.utils.serialization import deserialize
|
|
||||||
from quickbot.utils.main import get_send_message
|
|
||||||
from quickbot.model.descriptors import BotCommand, CommandCallbackContext
|
|
||||||
from quickbot.model.settings import Settings
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from quickbot.main import QBotApp
|
from quickbot.main import QBotApp
|
||||||
|
|
||||||
from ..context import ContextData, CallbackCommand, CommandContext
|
from ..context import ContextData, CallbackCommand
|
||||||
|
from .command_handler import command_handler
|
||||||
|
|
||||||
router = Router()
|
router = Router()
|
||||||
|
|
||||||
@@ -68,93 +57,4 @@ async def process_command_handler(message: Message | CallbackQuery, **kwargs):
|
|||||||
clear_state(state_data=state_data)
|
clear_state(state_data=state_data)
|
||||||
save_navigation_context(callback_data=callback_data, state_data=state_data)
|
save_navigation_context(callback_data=callback_data, state_data=state_data)
|
||||||
|
|
||||||
await cammand_handler(message=message, cmd=cmd, **kwargs)
|
await command_handler(message=message, cmd=cmd, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
async def cammand_handler(message: Message | CallbackQuery, cmd: BotCommand, **kwargs):
|
|
||||||
callback_data: ContextData = kwargs["callback_data"]
|
|
||||||
state: FSMContext = kwargs["state"]
|
|
||||||
state_data: dict = kwargs["state_data"]
|
|
||||||
app: "QBotApp" = kwargs["app"]
|
|
||||||
|
|
||||||
entity_data_dict: dict = state_data.get("entity_data")
|
|
||||||
form_data = (
|
|
||||||
{
|
|
||||||
key: await deserialize(
|
|
||||||
session=kwargs["db_session"],
|
|
||||||
type_=cmd.param_form[key].type_,
|
|
||||||
value=value,
|
|
||||||
)
|
|
||||||
for key, value in entity_data_dict.items()
|
|
||||||
}
|
|
||||||
if entity_data_dict and cmd.param_form
|
|
||||||
else None
|
|
||||||
)
|
|
||||||
|
|
||||||
callback_context = CommandCallbackContext(
|
|
||||||
message=message,
|
|
||||||
callback_data=callback_data,
|
|
||||||
form_data=form_data,
|
|
||||||
db_session=kwargs["db_session"],
|
|
||||||
user=kwargs["user"],
|
|
||||||
app=app,
|
|
||||||
app_state=kwargs["app_state"],
|
|
||||||
state_data=state_data,
|
|
||||||
state=state,
|
|
||||||
i18n=kwargs["i18n"],
|
|
||||||
register_navigation=cmd.register_navigation,
|
|
||||||
kwargs=kwargs,
|
|
||||||
)
|
|
||||||
|
|
||||||
if cmd.pre_check and (not cmd.param_form or (cmd.param_form and form_data is None)):
|
|
||||||
if iscoroutinefunction(cmd.pre_check):
|
|
||||||
if not await cmd.pre_check(callback_context):
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
if not cmd.pre_check(callback_context):
|
|
||||||
return
|
|
||||||
|
|
||||||
if form_data is None and cmd.param_form:
|
|
||||||
field_descriptor = list(cmd.param_form.values())[0]
|
|
||||||
kwargs["callback_data"] = ContextData(
|
|
||||||
command=CallbackCommand.FIELD_EDITOR,
|
|
||||||
context=CommandContext.COMMAND_FORM,
|
|
||||||
field_name=field_descriptor.name,
|
|
||||||
user_command=callback_data.user_command,
|
|
||||||
)
|
|
||||||
|
|
||||||
return await field_editor(message=message, **kwargs)
|
|
||||||
|
|
||||||
await cmd.handler(callback_context)
|
|
||||||
|
|
||||||
if callback_context.register_navigation:
|
|
||||||
await state.set_data(state_data)
|
|
||||||
|
|
||||||
stack, navigation_context = get_navigation_context(state_data=state_data)
|
|
||||||
back_callback_data = pop_navigation_context(stack=stack)
|
|
||||||
if back_callback_data:
|
|
||||||
callback_context.keyboard_builder.row(
|
|
||||||
InlineKeyboardButton(
|
|
||||||
text=(await Settings.get(Settings.APP_STRINGS_BACK_BTN)),
|
|
||||||
callback_data=back_callback_data.pack(),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
send_message = get_send_message(message)
|
|
||||||
|
|
||||||
if isinstance(message, CallbackQuery):
|
|
||||||
message = message.message
|
|
||||||
|
|
||||||
if callback_context.message_text:
|
|
||||||
await send_message(
|
|
||||||
text=callback_context.message_text,
|
|
||||||
reply_markup=callback_context.keyboard_builder.as_markup(),
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
await message.edit_reply_markup(
|
|
||||||
reply_markup=callback_context.keyboard_builder.as_markup()
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
|
||||||
clear_state(state_data=state_data)
|
|
||||||
await route_callback(message, back=True, **kwargs)
|
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ from fastapi import FastAPI
|
|||||||
from fastapi.applications import Lifespan, AppType
|
from fastapi.applications import Lifespan, AppType
|
||||||
from fastapi.datastructures import State
|
from fastapi.datastructures import State
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
|
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||||
|
|
||||||
|
from quickbot.bot.handlers.user_handlers.main import command_handler
|
||||||
from quickbot.utils.main import clear_state
|
from quickbot.utils.main import clear_state
|
||||||
from quickbot.utils.navigation import save_navigation_context
|
from quickbot.utils.navigation import save_navigation_context
|
||||||
|
|
||||||
@@ -185,9 +187,9 @@ class QBotApp(Generic[UserType, ConfigType], FastAPI):
|
|||||||
user_id: int,
|
user_id: int,
|
||||||
entity: type[BotEntity] | str,
|
entity: type[BotEntity] | str,
|
||||||
entity_id: int,
|
entity_id: int,
|
||||||
|
db_session: AsyncSession = None,
|
||||||
form_name: str = None,
|
form_name: str = None,
|
||||||
form_params: list[Any] = None,
|
form_params: list[Any] = None,
|
||||||
**kwargs,
|
|
||||||
):
|
):
|
||||||
f_params = []
|
f_params = []
|
||||||
|
|
||||||
@@ -195,7 +197,7 @@ class QBotApp(Generic[UserType, ConfigType], FastAPI):
|
|||||||
f_params.append(form_name)
|
f_params.append(form_name)
|
||||||
|
|
||||||
if form_params:
|
if form_params:
|
||||||
f_params.extend(form_params)
|
f_params.extend([str(p) for p in form_params])
|
||||||
|
|
||||||
if isinstance(entity, type):
|
if isinstance(entity, type):
|
||||||
entity = entity.bot_entity_descriptor.name
|
entity = entity.bot_entity_descriptor.name
|
||||||
@@ -214,7 +216,6 @@ class QBotApp(Generic[UserType, ConfigType], FastAPI):
|
|||||||
callback_data=callback_data, state_data=state_data
|
callback_data=callback_data, state_data=state_data
|
||||||
)
|
)
|
||||||
|
|
||||||
db_session = kwargs.get("db_session")
|
|
||||||
if not db_session:
|
if not db_session:
|
||||||
db_session = await get_db()
|
db_session = await get_db()
|
||||||
|
|
||||||
@@ -226,7 +227,7 @@ class QBotApp(Generic[UserType, ConfigType], FastAPI):
|
|||||||
with self.i18n.context(), self.i18n.use_locale(user.lang.value):
|
with self.i18n.context(), self.i18n.use_locale(user.lang.value):
|
||||||
await entity_item(
|
await entity_item(
|
||||||
query=None,
|
query=None,
|
||||||
db_session=kwargs.get("db_session"),
|
db_session=db_session,
|
||||||
callback_data=callback_data,
|
callback_data=callback_data,
|
||||||
app=self,
|
app=self,
|
||||||
user=user,
|
user=user,
|
||||||
@@ -236,3 +237,52 @@ class QBotApp(Generic[UserType, ConfigType], FastAPI):
|
|||||||
i18n=self.i18n,
|
i18n=self.i18n,
|
||||||
app_state=app_state,
|
app_state=app_state,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def execute_command(
|
||||||
|
self,
|
||||||
|
app_state: State,
|
||||||
|
command: str,
|
||||||
|
user_id: int,
|
||||||
|
db_session: AsyncSession = None,
|
||||||
|
):
|
||||||
|
state = self.dp.fsm.get_context(bot=self.bot, chat_id=user_id, user_id=user_id)
|
||||||
|
state_data = await state.get_data()
|
||||||
|
callback_data = ContextData(
|
||||||
|
command=CallbackCommand.USER_COMMAND,
|
||||||
|
user_command=command,
|
||||||
|
)
|
||||||
|
command_name = command.split("&")[0]
|
||||||
|
cmd = self.bot_commands.get(command_name)
|
||||||
|
|
||||||
|
if not db_session:
|
||||||
|
db_session = await get_db()
|
||||||
|
|
||||||
|
user = await self.user_class.get(
|
||||||
|
session=db_session,
|
||||||
|
id=user_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
if cmd is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
if cmd.clear_navigation:
|
||||||
|
state_data.pop("navigation_stack", None)
|
||||||
|
state_data.pop("navigation_context", None)
|
||||||
|
|
||||||
|
if cmd.register_navigation:
|
||||||
|
clear_state(state_data=state_data)
|
||||||
|
save_navigation_context(callback_data=callback_data, state_data=state_data)
|
||||||
|
|
||||||
|
with self.i18n.context(), self.i18n.use_locale(user.lang.value):
|
||||||
|
await command_handler(
|
||||||
|
message=None,
|
||||||
|
cmd=cmd,
|
||||||
|
db_session=db_session,
|
||||||
|
callback_data=callback_data,
|
||||||
|
app=self,
|
||||||
|
user=user,
|
||||||
|
state=state,
|
||||||
|
state_data=state_data,
|
||||||
|
i18n=self.i18n,
|
||||||
|
app_state=app_state,
|
||||||
|
)
|
||||||
|
|||||||
@@ -135,6 +135,13 @@ class EnumMember(object):
|
|||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
return hash(self.value)
|
return hash(self.value)
|
||||||
|
|
||||||
|
def __lt__(self, other: Self | str | Any | None) -> bool:
|
||||||
|
if isinstance(other, str):
|
||||||
|
return self.value < other
|
||||||
|
if isinstance(other, EnumMember):
|
||||||
|
return self.value < other.value
|
||||||
|
return False
|
||||||
|
|
||||||
def localized(self, lang: str = None) -> str:
|
def localized(self, lang: str = None) -> str:
|
||||||
if self.loc_obj:
|
if self.loc_obj:
|
||||||
if not lang:
|
if not lang:
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ class _BaseFieldDescriptor:
|
|||||||
localizable: bool = False
|
localizable: bool = False
|
||||||
bool_false_value: str | LazyProxy = "no"
|
bool_false_value: str | LazyProxy = "no"
|
||||||
bool_true_value: str | LazyProxy = "yes"
|
bool_true_value: str | LazyProxy = "yes"
|
||||||
ep_form: str | None = None
|
ep_form: str | Callable[["BotContext"], str] | None = None
|
||||||
ep_parent_field: str | None = None
|
ep_parent_field: str | None = None
|
||||||
ep_child_field: str | None = None
|
ep_child_field: str | None = None
|
||||||
dt_type: Literal["date", "datetime"] = "date"
|
dt_type: Literal["date", "datetime"] = "date"
|
||||||
@@ -195,9 +195,20 @@ class _BaseEntityDescriptor:
|
|||||||
EntityPermission.DELETE_ALL: [RoleBase.SUPER_USER],
|
EntityPermission.DELETE_ALL: [RoleBase.SUPER_USER],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
before_create: Callable[["BotContext"], Union[bool, str]] | None = None
|
||||||
|
before_create_save: (
|
||||||
|
Callable[["BotEntity", "BotContext"], Union[bool, str]] | None
|
||||||
|
) = None
|
||||||
|
before_update_save: (
|
||||||
|
Callable[[dict[str, Any], dict[str, Any], "BotContext"], Union[bool, str]]
|
||||||
|
| None
|
||||||
|
) = None
|
||||||
|
before_delete: Callable[["BotEntity", "BotContext"], Union[bool, str]] | None = None
|
||||||
on_created: Callable[["BotEntity", "BotContext"], None] | None = None
|
on_created: Callable[["BotEntity", "BotContext"], None] | None = None
|
||||||
on_deleted: Callable[["BotEntity", "BotContext"], None] | None = None
|
on_deleted: Callable[["BotEntity", "BotContext"], None] | None = None
|
||||||
on_updated: Callable[["BotEntity", "BotContext"], None] | None = None
|
on_updated: Callable[[dict[str, Any], "BotEntity", "BotContext"], None] | None = (
|
||||||
|
None
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclass(kw_only=True)
|
@dataclass(kw_only=True)
|
||||||
@@ -220,6 +231,7 @@ class CommandCallbackContext[UT: UserBase]:
|
|||||||
)
|
)
|
||||||
message_text: str | None = None
|
message_text: str | None = None
|
||||||
register_navigation: bool = True
|
register_navigation: bool = True
|
||||||
|
clear_navigation: bool = False
|
||||||
message: Message | CallbackQuery
|
message: Message | CallbackQuery
|
||||||
callback_data: ContextData
|
callback_data: ContextData
|
||||||
db_session: AsyncSession
|
db_session: AsyncSession
|
||||||
|
|||||||
@@ -2,9 +2,12 @@ from babel.support import LazyProxy
|
|||||||
from inspect import iscoroutinefunction, signature
|
from inspect import iscoroutinefunction, signature
|
||||||
from aiogram.types import Message, CallbackQuery
|
from aiogram.types import Message, CallbackQuery
|
||||||
from aiogram.utils.i18n import I18n
|
from aiogram.utils.i18n import I18n
|
||||||
|
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||||
from typing import Any, TYPE_CHECKING, Callable
|
from typing import Any, TYPE_CHECKING, Callable
|
||||||
import ujson as json
|
import ujson as json
|
||||||
|
|
||||||
|
from quickbot.utils.serialization import deserialize
|
||||||
|
|
||||||
from ..model.bot_entity import BotEntity
|
from ..model.bot_entity import BotEntity
|
||||||
from ..model.bot_enum import BotEnum
|
from ..model.bot_enum import BotEnum
|
||||||
from ..model.settings import Settings
|
from ..model.settings import Settings
|
||||||
@@ -16,6 +19,7 @@ from ..model.descriptors import (
|
|||||||
EntityDescriptor,
|
EntityDescriptor,
|
||||||
EntityPermission,
|
EntityPermission,
|
||||||
_BaseFieldDescriptor,
|
_BaseFieldDescriptor,
|
||||||
|
Filter,
|
||||||
)
|
)
|
||||||
|
|
||||||
from ..bot.handlers.context import CallbackCommand, ContextData, CommandContext
|
from ..bot.handlers.context import CallbackCommand, ContextData, CommandContext
|
||||||
@@ -330,3 +334,34 @@ async def build_field_sequence(
|
|||||||
field_sequence.append(fd.field_name)
|
field_sequence.append(fd.field_name)
|
||||||
|
|
||||||
return field_sequence
|
return field_sequence
|
||||||
|
|
||||||
|
|
||||||
|
async def prepare_static_filter(
|
||||||
|
db_session: AsyncSession,
|
||||||
|
entity_descriptor: EntityDescriptor,
|
||||||
|
static_filters: list[Filter],
|
||||||
|
params: list[str],
|
||||||
|
) -> list[Filter]:
|
||||||
|
return (
|
||||||
|
[
|
||||||
|
Filter(
|
||||||
|
field_name=f.field_name,
|
||||||
|
operator=f.operator,
|
||||||
|
value_type="const",
|
||||||
|
value=(
|
||||||
|
f.value
|
||||||
|
if f.value_type == "const"
|
||||||
|
else await deserialize(
|
||||||
|
session=db_session,
|
||||||
|
type_=entity_descriptor.fields_descriptors[
|
||||||
|
f.field_name
|
||||||
|
].type_base,
|
||||||
|
value=params[f.param_index],
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
for f in static_filters
|
||||||
|
]
|
||||||
|
if static_filters
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user