add visibility delegates for fields in edit form
This commit is contained in:
@@ -5,7 +5,7 @@ from aiogram.utils.keyboard import InlineKeyboardBuilder
|
|||||||
from babel.support import LazyProxy
|
from babel.support import LazyProxy
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
|
|
||||||
from ....model.descriptors import FieldDescriptor
|
from ....model.descriptors import BotContext, FieldDescriptor
|
||||||
from ....model.user import UserBase
|
from ....model.user import UserBase
|
||||||
from ..context import ContextData, CallbackCommand
|
from ..context import ContextData, CallbackCommand
|
||||||
from ....utils.main import get_send_message
|
from ....utils.main import get_send_message
|
||||||
@@ -67,12 +67,20 @@ async def bool_editor(
|
|||||||
|
|
||||||
state_data = kwargs["state_data"]
|
state_data = kwargs["state_data"]
|
||||||
|
|
||||||
|
context = BotContext(
|
||||||
|
db_session=kwargs["db_session"],
|
||||||
|
app=kwargs["app"],
|
||||||
|
app_state=kwargs["app_state"],
|
||||||
|
message=message,
|
||||||
|
)
|
||||||
|
|
||||||
await wrap_editor(
|
await wrap_editor(
|
||||||
keyboard_builder=keyboard_builder,
|
keyboard_builder=keyboard_builder,
|
||||||
field_descriptor=field_descriptor,
|
field_descriptor=field_descriptor,
|
||||||
callback_data=callback_data,
|
callback_data=callback_data,
|
||||||
state_data=state_data,
|
state_data=state_data,
|
||||||
user=user,
|
user=user,
|
||||||
|
context=context,
|
||||||
)
|
)
|
||||||
|
|
||||||
state: FSMContext = kwargs["state"]
|
state: FSMContext = kwargs["state"]
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from aiogram.utils.keyboard import InlineKeyboardBuilder
|
|||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from ....model.descriptors import FieldDescriptor
|
from ....model.descriptors import BotContext, FieldDescriptor
|
||||||
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
|
||||||
@@ -158,6 +158,12 @@ async def time_picker(
|
|||||||
)
|
)
|
||||||
|
|
||||||
state_data = kwargs["state_data"]
|
state_data = kwargs["state_data"]
|
||||||
|
context = BotContext(
|
||||||
|
db_session=kwargs["db_session"],
|
||||||
|
app=kwargs["app"],
|
||||||
|
app_state=kwargs["app_state"],
|
||||||
|
message=message,
|
||||||
|
)
|
||||||
|
|
||||||
await wrap_editor(
|
await wrap_editor(
|
||||||
keyboard_builder=keyboard_builder,
|
keyboard_builder=keyboard_builder,
|
||||||
@@ -165,6 +171,7 @@ async def time_picker(
|
|||||||
callback_data=callback_data,
|
callback_data=callback_data,
|
||||||
state_data=state_data,
|
state_data=state_data,
|
||||||
user=user,
|
user=user,
|
||||||
|
context=context,
|
||||||
)
|
)
|
||||||
|
|
||||||
await state.set_data(state_data)
|
await state.set_data(state_data)
|
||||||
@@ -272,12 +279,20 @@ async def date_picker(
|
|||||||
|
|
||||||
state_data = kwargs["state_data"]
|
state_data = kwargs["state_data"]
|
||||||
|
|
||||||
|
context = BotContext(
|
||||||
|
db_session=kwargs["db_session"],
|
||||||
|
app=kwargs["app"],
|
||||||
|
app_state=kwargs["app_state"],
|
||||||
|
message=message,
|
||||||
|
)
|
||||||
|
|
||||||
await wrap_editor(
|
await wrap_editor(
|
||||||
keyboard_builder=keyboard_builder,
|
keyboard_builder=keyboard_builder,
|
||||||
field_descriptor=field_descriptor,
|
field_descriptor=field_descriptor,
|
||||||
callback_data=callback_data,
|
callback_data=callback_data,
|
||||||
state_data=state_data,
|
state_data=state_data,
|
||||||
user=user,
|
user=user,
|
||||||
|
context=context,
|
||||||
)
|
)
|
||||||
|
|
||||||
await state.set_data(state_data)
|
await state.set_data(state_data)
|
||||||
@@ -295,11 +310,11 @@ async def date_picker(
|
|||||||
async def date_picker_year(
|
async def date_picker_year(
|
||||||
query: CallbackQuery,
|
query: CallbackQuery,
|
||||||
callback_data: ContextData,
|
callback_data: ContextData,
|
||||||
app: "QBotApp",
|
|
||||||
state: FSMContext,
|
state: FSMContext,
|
||||||
user: UserBase,
|
user: UserBase,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
|
app: "QBotApp" = kwargs["app"]
|
||||||
start_date = datetime.strptime(callback_data.data, "%Y-%m-%d %H-%M")
|
start_date = datetime.strptime(callback_data.data, "%Y-%m-%d %H-%M")
|
||||||
|
|
||||||
state_data = await state.get_data()
|
state_data = await state.get_data()
|
||||||
@@ -366,12 +381,20 @@ async def date_picker_year(
|
|||||||
|
|
||||||
field_descriptor = get_field_descriptor(app, callback_data)
|
field_descriptor = get_field_descriptor(app, callback_data)
|
||||||
|
|
||||||
|
context = BotContext(
|
||||||
|
db_session=kwargs["db_session"],
|
||||||
|
app=app,
|
||||||
|
app_state=kwargs["app_state"],
|
||||||
|
message=query,
|
||||||
|
)
|
||||||
|
|
||||||
await wrap_editor(
|
await wrap_editor(
|
||||||
keyboard_builder=keyboard_builder,
|
keyboard_builder=keyboard_builder,
|
||||||
field_descriptor=field_descriptor,
|
field_descriptor=field_descriptor,
|
||||||
callback_data=callback_data,
|
callback_data=callback_data,
|
||||||
state_data=state_data,
|
state_data=state_data,
|
||||||
user=user,
|
user=user,
|
||||||
|
context=context,
|
||||||
)
|
)
|
||||||
|
|
||||||
await query.message.edit_reply_markup(reply_markup=keyboard_builder.as_markup())
|
await query.message.edit_reply_markup(reply_markup=keyboard_builder.as_markup())
|
||||||
@@ -380,9 +403,8 @@ async def date_picker_year(
|
|||||||
@router.callback_query(
|
@router.callback_query(
|
||||||
ContextData.filter(F.command == CallbackCommand.DATE_PICKER_MONTH)
|
ContextData.filter(F.command == CallbackCommand.DATE_PICKER_MONTH)
|
||||||
)
|
)
|
||||||
async def date_picker_month(
|
async def date_picker_month(query: CallbackQuery, callback_data: ContextData, **kwargs):
|
||||||
query: CallbackQuery, callback_data: ContextData, app: "QBotApp", **kwargs
|
app: "QBotApp" = kwargs["app"]
|
||||||
):
|
|
||||||
field_descriptor = get_field_descriptor(app, callback_data)
|
field_descriptor = get_field_descriptor(app, callback_data)
|
||||||
state: FSMContext = kwargs["state"]
|
state: FSMContext = kwargs["state"]
|
||||||
state_data = await state.get_data()
|
state_data = await state.get_data()
|
||||||
|
|||||||
@@ -293,12 +293,20 @@ async def render_entity_picker(
|
|||||||
|
|
||||||
state_data = kwargs["state_data"]
|
state_data = kwargs["state_data"]
|
||||||
|
|
||||||
|
context = BotContext(
|
||||||
|
db_session=db_session,
|
||||||
|
app=kwargs["app"],
|
||||||
|
app_state=kwargs["app_state"],
|
||||||
|
message=message,
|
||||||
|
)
|
||||||
|
|
||||||
await wrap_editor(
|
await wrap_editor(
|
||||||
keyboard_builder=keyboard_builder,
|
keyboard_builder=keyboard_builder,
|
||||||
field_descriptor=field_descriptor,
|
field_descriptor=field_descriptor,
|
||||||
callback_data=callback_data,
|
callback_data=callback_data,
|
||||||
state_data=state_data,
|
state_data=state_data,
|
||||||
user=user,
|
user=user,
|
||||||
|
context=context,
|
||||||
)
|
)
|
||||||
|
|
||||||
await state.set_data(state_data)
|
await state.set_data(state_data)
|
||||||
|
|||||||
@@ -5,12 +5,13 @@ from aiogram.types import Message, CallbackQuery
|
|||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||||
|
|
||||||
from quickbot.model.descriptors import EntityForm
|
from quickbot.model.descriptors import BotContext, EntityForm
|
||||||
|
|
||||||
from ....model import EntityPermission
|
from ....model import EntityPermission
|
||||||
from ....model.settings import Settings
|
from ....model.settings import Settings
|
||||||
from ....model.user import UserBase
|
from ....model.user import UserBase
|
||||||
from ....utils.main import (
|
from ....utils.main import (
|
||||||
|
build_field_sequence,
|
||||||
check_entity_permission,
|
check_entity_permission,
|
||||||
get_field_descriptor,
|
get_field_descriptor,
|
||||||
)
|
)
|
||||||
@@ -134,6 +135,22 @@ async def field_editor(message: Message | CallbackQuery, **kwargs):
|
|||||||
form: EntityForm = entity_descriptor.forms.get(
|
form: EntityForm = entity_descriptor.forms.get(
|
||||||
form_name, entity_descriptor.default_form
|
form_name, entity_descriptor.default_form
|
||||||
)
|
)
|
||||||
|
if form.edit_field_sequence:
|
||||||
|
field_sequence = form.edit_field_sequence
|
||||||
|
else:
|
||||||
|
context = BotContext(
|
||||||
|
db_session=db_session,
|
||||||
|
app=app,
|
||||||
|
app_state=kwargs["app_state"],
|
||||||
|
message=message,
|
||||||
|
)
|
||||||
|
field_sequence = await build_field_sequence(
|
||||||
|
entity_descriptor=entity_descriptor,
|
||||||
|
user=user,
|
||||||
|
callback_data=callback_data,
|
||||||
|
state_data=state_data,
|
||||||
|
context=context,
|
||||||
|
)
|
||||||
entity_data = {
|
entity_data = {
|
||||||
key: serialize(
|
key: serialize(
|
||||||
getattr(
|
getattr(
|
||||||
@@ -143,7 +160,7 @@ async def field_editor(message: Message | CallbackQuery, **kwargs):
|
|||||||
entity_descriptor.fields_descriptors[key],
|
entity_descriptor.fields_descriptors[key],
|
||||||
)
|
)
|
||||||
for key in (
|
for key in (
|
||||||
form.edit_field_sequence
|
field_sequence
|
||||||
if callback_data.context == CommandContext.ENTITY_EDIT
|
if callback_data.context == CommandContext.ENTITY_EDIT
|
||||||
else [callback_data.field_name]
|
else [callback_data.field_name]
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ 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 sqlmodel.ext.asyncio.session import AsyncSession
|
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING, Any
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
import json
|
import json
|
||||||
|
|
||||||
@@ -39,6 +39,26 @@ if TYPE_CHECKING:
|
|||||||
router = Router()
|
router = Router()
|
||||||
|
|
||||||
|
|
||||||
|
async def _validate_value(
|
||||||
|
field_descriptor: FieldDescriptor,
|
||||||
|
value: Any,
|
||||||
|
message: Message | CallbackQuery,
|
||||||
|
**kwargs: Any,
|
||||||
|
) -> bool | str:
|
||||||
|
if field_descriptor.validator:
|
||||||
|
context = BotContext(
|
||||||
|
db_session=kwargs["db_session"],
|
||||||
|
app=kwargs["app"],
|
||||||
|
app_state=kwargs["app_state"],
|
||||||
|
message=message,
|
||||||
|
)
|
||||||
|
if iscoroutinefunction(field_descriptor.validator):
|
||||||
|
return await field_descriptor.validator(value, context)
|
||||||
|
else:
|
||||||
|
return field_descriptor.validator(value, context)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
@router.message(CallbackCommandFilter(CallbackCommand.FIELD_EDITOR_CALLBACK))
|
@router.message(CallbackCommandFilter(CallbackCommand.FIELD_EDITOR_CALLBACK))
|
||||||
@router.callback_query(
|
@router.callback_query(
|
||||||
ContextData.filter(F.command == CallbackCommand.FIELD_EDITOR_CALLBACK)
|
ContextData.filter(F.command == CallbackCommand.FIELD_EDITOR_CALLBACK)
|
||||||
@@ -59,6 +79,39 @@ async def field_editor_callback(message: Message | CallbackQuery, **kwargs):
|
|||||||
field_descriptor = get_field_descriptor(app, callback_data)
|
field_descriptor = get_field_descriptor(app, callback_data)
|
||||||
type_base = field_descriptor.type_base
|
type_base = field_descriptor.type_base
|
||||||
|
|
||||||
|
if type_base in [int, float, Decimal]:
|
||||||
|
try:
|
||||||
|
val = type_base(value) # @IgnoreException
|
||||||
|
except Exception:
|
||||||
|
return await message.answer(
|
||||||
|
text=(await Settings.get(Settings.APP_STRINGS_INVALID_INPUT))
|
||||||
|
)
|
||||||
|
elif type_base is str and field_descriptor.localizable:
|
||||||
|
locale_index = int(state_data.get("locale_index"))
|
||||||
|
val = {
|
||||||
|
list(LanguageBase.all_members.values())[
|
||||||
|
locale_index
|
||||||
|
].value: message.text
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
val = value
|
||||||
|
|
||||||
|
validation_result = await _validate_value(
|
||||||
|
field_descriptor=field_descriptor,
|
||||||
|
value=val,
|
||||||
|
message=message,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
if isinstance(validation_result, str):
|
||||||
|
return await message.answer(
|
||||||
|
text=f"{await Settings.get(Settings.APP_STRINGS_INVALID_INPUT)}\n{validation_result}"
|
||||||
|
)
|
||||||
|
elif not validation_result:
|
||||||
|
return await message.answer(
|
||||||
|
text=(await Settings.get(Settings.APP_STRINGS_INVALID_INPUT))
|
||||||
|
)
|
||||||
|
|
||||||
if type_base is str and field_descriptor.localizable:
|
if type_base is str and field_descriptor.localizable:
|
||||||
locale_index = int(state_data.get("locale_index"))
|
locale_index = int(state_data.get("locale_index"))
|
||||||
|
|
||||||
@@ -90,13 +143,6 @@ async def field_editor_callback(message: Message | CallbackQuery, **kwargs):
|
|||||||
**kwargs,
|
**kwargs,
|
||||||
)
|
)
|
||||||
|
|
||||||
elif type_base in [int, float, Decimal]:
|
|
||||||
try:
|
|
||||||
_ = type_base(value) # @IgnoreException
|
|
||||||
except Exception:
|
|
||||||
return await message.answer(
|
|
||||||
text=(await Settings.get(Settings.APP_STRINGS_INVALID_INPUT))
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
callback_data: ContextData = kwargs["callback_data"]
|
callback_data: ContextData = kwargs["callback_data"]
|
||||||
if callback_data.data:
|
if callback_data.data:
|
||||||
@@ -150,6 +196,9 @@ async def process_field_edit_callback(message: Message | CallbackQuery, **kwargs
|
|||||||
app: "QBotApp" = kwargs["app"]
|
app: "QBotApp" = kwargs["app"]
|
||||||
entity_descriptor = get_entity_descriptor(app, callback_data)
|
entity_descriptor = get_entity_descriptor(app, callback_data)
|
||||||
|
|
||||||
|
entity_data = state_data.get("entity_data", {})
|
||||||
|
entity_data[field_descriptor.field_name] = value
|
||||||
|
|
||||||
if callback_data.context == CommandContext.COMMAND_FORM:
|
if callback_data.context == CommandContext.COMMAND_FORM:
|
||||||
field_sequence = list(field_descriptor.command.param_form.keys())
|
field_sequence = list(field_descriptor.command.param_form.keys())
|
||||||
current_index = field_sequence.index(callback_data.field_name)
|
current_index = field_sequence.index(callback_data.field_name)
|
||||||
@@ -167,11 +216,18 @@ async def process_field_edit_callback(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:
|
||||||
field_sequence = build_field_sequence(
|
context = BotContext(
|
||||||
|
db_session=kwargs["db_session"],
|
||||||
|
app=kwargs["app"],
|
||||||
|
app_state=kwargs["app_state"],
|
||||||
|
message=message,
|
||||||
|
)
|
||||||
|
field_sequence = await build_field_sequence(
|
||||||
entity_descriptor=entity_descriptor,
|
entity_descriptor=entity_descriptor,
|
||||||
user=user,
|
user=user,
|
||||||
callback_data=callback_data,
|
callback_data=callback_data,
|
||||||
state_data=state_data,
|
state_data=state_data,
|
||||||
|
context=context,
|
||||||
)
|
)
|
||||||
|
|
||||||
current_index = (
|
current_index = (
|
||||||
@@ -182,8 +238,6 @@ async def process_field_edit_callback(message: Message | CallbackQuery, **kwargs
|
|||||||
)
|
)
|
||||||
field_descriptors = entity_descriptor.fields_descriptors
|
field_descriptors = entity_descriptor.fields_descriptors
|
||||||
|
|
||||||
entity_data = state_data.get("entity_data", {})
|
|
||||||
|
|
||||||
if callback_data.context == CommandContext.ENTITY_CREATE:
|
if callback_data.context == CommandContext.ENTITY_CREATE:
|
||||||
stack = state_data.get("navigation_stack", [])
|
stack = state_data.get("navigation_stack", [])
|
||||||
prev_callback_data = ContextData.unpack(stack[-1]) if stack else None
|
prev_callback_data = ContextData.unpack(stack[-1]) if stack else None
|
||||||
@@ -224,7 +278,7 @@ async def process_field_edit_callback(message: Message | CallbackQuery, **kwargs
|
|||||||
]
|
]
|
||||||
and current_index < len(field_sequence) - 1
|
and current_index < len(field_sequence) - 1
|
||||||
):
|
):
|
||||||
entity_data[field_descriptor.field_name] = value
|
# entity_data[field_descriptor.field_name] = value
|
||||||
state_data.update({"entity_data": entity_data})
|
state_data.update({"entity_data": entity_data})
|
||||||
|
|
||||||
next_field_name = field_sequence[current_index + 1]
|
next_field_name = field_sequence[current_index + 1]
|
||||||
@@ -252,7 +306,7 @@ async def process_field_edit_callback(message: Message | CallbackQuery, **kwargs
|
|||||||
)
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
entity_data[field_descriptor.name] = value
|
# entity_data[field_descriptor.field_name] = value
|
||||||
|
|
||||||
# What if user has several roles and each role has its own ownership field? Should we allow creation even
|
# What if user has several roles and each role has its own ownership field? Should we allow creation even
|
||||||
# if user has no CREATE_ALL permission
|
# if user has no CREATE_ALL permission
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from aiogram.utils.keyboard import InlineKeyboardBuilder
|
|||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from ....model.descriptors import FieldDescriptor
|
from ....model.descriptors import BotContext, FieldDescriptor
|
||||||
from ....model.language import LanguageBase
|
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
|
||||||
@@ -87,12 +87,20 @@ async def string_editor(
|
|||||||
|
|
||||||
state_data = kwargs["state_data"]
|
state_data = kwargs["state_data"]
|
||||||
|
|
||||||
|
context = BotContext(
|
||||||
|
db_session=kwargs["db_session"],
|
||||||
|
app=kwargs["app"],
|
||||||
|
app_state=kwargs["app_state"],
|
||||||
|
message=message,
|
||||||
|
)
|
||||||
|
|
||||||
await wrap_editor(
|
await wrap_editor(
|
||||||
keyboard_builder=keyboard_builder,
|
keyboard_builder=keyboard_builder,
|
||||||
field_descriptor=field_descriptor,
|
field_descriptor=field_descriptor,
|
||||||
callback_data=callback_data,
|
callback_data=callback_data,
|
||||||
state_data=state_data,
|
state_data=state_data,
|
||||||
user=user,
|
user=user,
|
||||||
|
context=context,
|
||||||
)
|
)
|
||||||
|
|
||||||
await state.set_data(state_data)
|
await state.set_data(state_data)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from aiogram.types import InlineKeyboardButton
|
|||||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||||
|
|
||||||
from ....model.settings import Settings
|
from ....model.settings import Settings
|
||||||
from ....model.descriptors import EntityForm, FieldDescriptor
|
from ....model.descriptors import BotContext, EntityForm, FieldDescriptor
|
||||||
from ....model.user import UserBase
|
from ....model.user import UserBase
|
||||||
from ..context import ContextData, CallbackCommand, CommandContext
|
from ..context import ContextData, CallbackCommand, CommandContext
|
||||||
from ....utils.navigation import get_navigation_context, pop_navigation_context
|
from ....utils.navigation import get_navigation_context, pop_navigation_context
|
||||||
@@ -15,6 +15,7 @@ async def wrap_editor(
|
|||||||
callback_data: ContextData,
|
callback_data: ContextData,
|
||||||
state_data: dict,
|
state_data: dict,
|
||||||
user: UserBase,
|
user: UserBase,
|
||||||
|
context: BotContext,
|
||||||
):
|
):
|
||||||
if callback_data.context in [
|
if callback_data.context in [
|
||||||
CommandContext.ENTITY_CREATE,
|
CommandContext.ENTITY_CREATE,
|
||||||
@@ -42,11 +43,12 @@ async def wrap_editor(
|
|||||||
if form.edit_field_sequence:
|
if form.edit_field_sequence:
|
||||||
field_sequence = form.edit_field_sequence
|
field_sequence = form.edit_field_sequence
|
||||||
else:
|
else:
|
||||||
field_sequence = build_field_sequence(
|
field_sequence = await build_field_sequence(
|
||||||
entity_descriptor=field_descriptor.entity_descriptor,
|
entity_descriptor=field_descriptor.entity_descriptor,
|
||||||
user=user,
|
user=user,
|
||||||
callback_data=callback_data,
|
callback_data=callback_data,
|
||||||
state_data=state_data,
|
state_data=state_data,
|
||||||
|
context=context,
|
||||||
)
|
)
|
||||||
field_index = (
|
field_index = (
|
||||||
field_sequence.index(field_descriptor.name)
|
field_sequence.index(field_descriptor.name)
|
||||||
|
|||||||
@@ -208,11 +208,12 @@ async def entity_item(
|
|||||||
if form.edit_field_sequence:
|
if form.edit_field_sequence:
|
||||||
field_sequence = form.edit_field_sequence
|
field_sequence = form.edit_field_sequence
|
||||||
else:
|
else:
|
||||||
field_sequence = build_field_sequence(
|
field_sequence = await build_field_sequence(
|
||||||
entity_descriptor=entity_descriptor,
|
entity_descriptor=entity_descriptor,
|
||||||
user=user,
|
user=user,
|
||||||
callback_data=callback_data,
|
callback_data=callback_data,
|
||||||
state_data=state_data,
|
state_data=state_data,
|
||||||
|
context=context,
|
||||||
)
|
)
|
||||||
edit_delete_row.append(
|
edit_delete_row.append(
|
||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
@@ -314,30 +315,32 @@ async def entity_item(
|
|||||||
if skip:
|
if skip:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
field_caption = (
|
|
||||||
await get_callable_str(
|
|
||||||
callable_str=field_descriptor.caption,
|
|
||||||
context=context,
|
|
||||||
descriptor=field_descriptor,
|
|
||||||
)
|
|
||||||
if field_descriptor.caption
|
|
||||||
else field_descriptor.field_name
|
|
||||||
)
|
|
||||||
if field_descriptor.caption_value:
|
if field_descriptor.caption_value:
|
||||||
value = await get_callable_str(
|
item_text += f"\n{
|
||||||
callable_str=field_descriptor.caption_value,
|
await get_callable_str(
|
||||||
context=context,
|
callable_str=field_descriptor.caption_value,
|
||||||
descriptor=field_descriptor,
|
context=context,
|
||||||
entity=entity_item,
|
descriptor=field_descriptor,
|
||||||
)
|
entity=entity_item,
|
||||||
|
)
|
||||||
|
}"
|
||||||
else:
|
else:
|
||||||
|
field_caption = (
|
||||||
|
await get_callable_str(
|
||||||
|
callable_str=field_descriptor.caption,
|
||||||
|
context=context,
|
||||||
|
descriptor=field_descriptor,
|
||||||
|
)
|
||||||
|
if field_descriptor.caption
|
||||||
|
else field_descriptor.field_name
|
||||||
|
)
|
||||||
value = await get_value_repr(
|
value = await get_value_repr(
|
||||||
value=getattr(entity_item, field_descriptor.field_name),
|
value=getattr(entity_item, field_descriptor.field_name),
|
||||||
field_descriptor=field_descriptor,
|
field_descriptor=field_descriptor,
|
||||||
context=context,
|
context=context,
|
||||||
locale=user.lang,
|
locale=user.lang,
|
||||||
)
|
)
|
||||||
item_text += f"\n{field_caption or field_descriptor.name}:{f' <b>{value}</b>' if value else ''}"
|
item_text += f"\n{field_caption or field_descriptor.name}:{f' <b>{value}</b>' if value else ''}"
|
||||||
|
|
||||||
context = pop_navigation_context(navigation_stack)
|
context = pop_navigation_context(navigation_stack)
|
||||||
if context:
|
if context:
|
||||||
|
|||||||
@@ -118,6 +118,12 @@ async def entity_list(
|
|||||||
)
|
)
|
||||||
|
|
||||||
keyboard_builder = InlineKeyboardBuilder()
|
keyboard_builder = InlineKeyboardBuilder()
|
||||||
|
context = BotContext(
|
||||||
|
db_session=db_session,
|
||||||
|
app=app,
|
||||||
|
app_state=kwargs["app_state"],
|
||||||
|
message=message,
|
||||||
|
)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
EntityPermission.CREATE in user_permissions
|
EntityPermission.CREATE in user_permissions
|
||||||
@@ -126,11 +132,12 @@ async def entity_list(
|
|||||||
if form_item.edit_field_sequence:
|
if form_item.edit_field_sequence:
|
||||||
field_sequence = form_item.edit_field_sequence
|
field_sequence = form_item.edit_field_sequence
|
||||||
else:
|
else:
|
||||||
field_sequence = build_field_sequence(
|
field_sequence = await build_field_sequence(
|
||||||
entity_descriptor=entity_descriptor,
|
entity_descriptor=entity_descriptor,
|
||||||
user=user,
|
user=user,
|
||||||
callback_data=callback_data,
|
callback_data=callback_data,
|
||||||
state_data=kwargs["state_data"],
|
state_data=kwargs["state_data"],
|
||||||
|
context=context,
|
||||||
)
|
)
|
||||||
keyboard_builder.row(
|
keyboard_builder.row(
|
||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
@@ -206,13 +213,6 @@ async def entity_list(
|
|||||||
total_pages = 1
|
total_pages = 1
|
||||||
page = 1
|
page = 1
|
||||||
|
|
||||||
context = BotContext(
|
|
||||||
db_session=db_session,
|
|
||||||
app=app,
|
|
||||||
app_state=kwargs["app_state"],
|
|
||||||
message=message,
|
|
||||||
)
|
|
||||||
|
|
||||||
for item in items:
|
for item in items:
|
||||||
caption = None
|
caption = None
|
||||||
|
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ async def cammand_handler(message: Message | CallbackQuery, cmd: BotCommand, **k
|
|||||||
db_session=kwargs["db_session"],
|
db_session=kwargs["db_session"],
|
||||||
user=kwargs["user"],
|
user=kwargs["user"],
|
||||||
app=app,
|
app=app,
|
||||||
|
app_state=kwargs["app_state"],
|
||||||
state_data=state_data,
|
state_data=state_data,
|
||||||
state=state,
|
state=state,
|
||||||
i18n=kwargs["i18n"],
|
i18n=kwargs["i18n"],
|
||||||
|
|||||||
@@ -161,6 +161,7 @@ class QBotApp(Generic[UserType, ConfigType], FastAPI):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
for locale, description in command.caption.items():
|
for locale, description in command.caption.items():
|
||||||
|
locale = "default" if locale == "en" else locale
|
||||||
if locale not in commands_captions:
|
if locale not in commands_captions:
|
||||||
commands_captions[locale] = []
|
commands_captions[locale] = []
|
||||||
commands_captions[locale].append((command_name, description))
|
commands_captions[locale].append((command_name, description))
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class FieldEditButton:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class CommandButton:
|
class CommandButton:
|
||||||
command: ContextData | Callable[[ContextData, Any], ContextData] | str
|
command: ContextData | Callable[["BotEntity", "BotContext"], ContextData] | str
|
||||||
caption: str | LazyProxy | Callable[["BotEntity", "BotContext"], str] | None = None
|
caption: str | LazyProxy | Callable[["BotEntity", "BotContext"], str] | None = None
|
||||||
visibility: Callable[["BotEntity", "BotContext"], bool] | None = None
|
visibility: Callable[["BotEntity", "BotContext"], bool] | None = None
|
||||||
|
|
||||||
@@ -113,6 +113,12 @@ class _BaseFieldDescriptor:
|
|||||||
is_visible: (
|
is_visible: (
|
||||||
bool | Callable[["FieldDescriptor", "BotEntity", "BotContext"], bool] | None
|
bool | Callable[["FieldDescriptor", "BotEntity", "BotContext"], bool] | None
|
||||||
) = None
|
) = None
|
||||||
|
is_visible_in_edit_form: (
|
||||||
|
bool
|
||||||
|
| Callable[["FieldDescriptor", Union["BotEntity", Any], "BotContext"], bool]
|
||||||
|
| None
|
||||||
|
) = None
|
||||||
|
validator: Callable[[Any, "BotContext"], Union[bool, str]] | None = None
|
||||||
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"
|
||||||
@@ -219,6 +225,7 @@ class CommandCallbackContext[UT: UserBase]:
|
|||||||
db_session: AsyncSession
|
db_session: AsyncSession
|
||||||
user: UT
|
user: UT
|
||||||
app: "QBotApp"
|
app: "QBotApp"
|
||||||
|
app_state: State
|
||||||
state_data: dict[str, Any]
|
state_data: dict[str, Any]
|
||||||
state: FSMContext
|
state: FSMContext
|
||||||
form_data: dict[str, Any]
|
form_data: dict[str, Any]
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from types import NoneType, UnionType
|
|||||||
from aiogram.utils.i18n.context import get_i18n
|
from aiogram.utils.i18n.context import get_i18n
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from sqlmodel import SQLModel, Field, select
|
from sqlmodel import SQLModel, Field, select
|
||||||
|
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||||
from typing import Any, get_args, get_origin
|
from typing import Any, get_args, get_origin
|
||||||
|
|
||||||
from ..db import async_session
|
from ..db import async_session
|
||||||
@@ -193,11 +194,23 @@ class Settings(metaclass=SettingsMetaclass):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def get[T](cls, param: T, all_locales=False, locale: str = None) -> T:
|
async def get[T](
|
||||||
|
cls,
|
||||||
|
param: T,
|
||||||
|
session: AsyncSession = None,
|
||||||
|
all_locales=False,
|
||||||
|
locale: str = None,
|
||||||
|
) -> T:
|
||||||
name = param.field_name
|
name = param.field_name
|
||||||
|
|
||||||
if name not in cls._cache.keys():
|
if name not in cls._cache.keys():
|
||||||
cls._cache[name] = await cls.load_param(param)
|
if session is None:
|
||||||
|
async with async_session() as session:
|
||||||
|
cls._cache[name] = await cls.load_param(
|
||||||
|
session=session, param=param
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
cls._cache[name] = await cls.load_param(session=session, param=param)
|
||||||
|
|
||||||
ret_val = cls._cache[name]
|
ret_val = cls._cache[name]
|
||||||
|
|
||||||
@@ -213,18 +226,17 @@ class Settings(metaclass=SettingsMetaclass):
|
|||||||
return ret_val
|
return ret_val
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def load_param(cls, param: FieldDescriptor) -> Any:
|
async def load_param(cls, session: AsyncSession, param: FieldDescriptor) -> Any:
|
||||||
async with async_session() as session:
|
db_setting = (
|
||||||
db_setting = (
|
await session.exec(
|
||||||
await session.exec(
|
select(DbSettings).where(DbSettings.name == param.field_name)
|
||||||
select(DbSettings).where(DbSettings.name == param.field_name)
|
)
|
||||||
)
|
).first()
|
||||||
).first()
|
|
||||||
|
|
||||||
if db_setting:
|
if db_setting:
|
||||||
return await deserialize(
|
return await deserialize(
|
||||||
session=session, type_=param.type_, value=db_setting.value
|
session=session, type_=param.type_, value=db_setting.value
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
param.default_factory()
|
param.default_factory()
|
||||||
@@ -240,20 +252,20 @@ class Settings(metaclass=SettingsMetaclass):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
# @classmethod
|
||||||
async def load_params(cls):
|
# async def load_params(cls):
|
||||||
async with async_session() as session:
|
# async with async_session() as session:
|
||||||
db_settings = (await session.exec(select(DbSettings))).all()
|
# db_settings = (await session.exec(select(DbSettings))).all()
|
||||||
for db_setting in db_settings:
|
# for db_setting in db_settings:
|
||||||
if db_setting.name in cls.__dict__:
|
# if db_setting.name in cls.__dict__:
|
||||||
setting = cls.__dict__[db_setting.name] # type: FieldDescriptor
|
# setting = cls.__dict__[db_setting.name] # type: FieldDescriptor
|
||||||
cls._cache[db_setting.name] = await deserialize(
|
# cls._cache[db_setting.name] = await deserialize(
|
||||||
session=session,
|
# session=session,
|
||||||
type_=setting.type_,
|
# type_=setting.type_,
|
||||||
value=db_setting.value,
|
# value=db_setting.value,
|
||||||
)
|
# )
|
||||||
|
|
||||||
cls._loaded = True
|
# cls._loaded = True
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def set_param(cls, param: str | FieldDescriptor, value) -> None:
|
async def set_param(cls, param: str | FieldDescriptor, value) -> None:
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ from ..model.descriptors import (
|
|||||||
FieldDescriptor,
|
FieldDescriptor,
|
||||||
EntityDescriptor,
|
EntityDescriptor,
|
||||||
EntityPermission,
|
EntityPermission,
|
||||||
|
_BaseFieldDescriptor,
|
||||||
)
|
)
|
||||||
|
|
||||||
from ..bot.handlers.context import CallbackCommand, ContextData, CommandContext
|
from ..bot.handlers.context import CallbackCommand, ContextData, CommandContext
|
||||||
@@ -198,10 +199,13 @@ async def get_callable_str(
|
|||||||
if len(args) == 3:
|
if len(args) == 3:
|
||||||
return await callable_str(descriptor, entity, context)
|
return await callable_str(descriptor, entity, context)
|
||||||
else:
|
else:
|
||||||
if issubclass(args[0].annotation, BotEntity):
|
param = args[next(iter(args))]
|
||||||
return await callable_str(entity, context)
|
if not isinstance(param.annotation, str) and issubclass(
|
||||||
else:
|
param.annotation, _BaseFieldDescriptor
|
||||||
|
):
|
||||||
return await callable_str(descriptor, context)
|
return await callable_str(descriptor, context)
|
||||||
|
else:
|
||||||
|
return await callable_str(entity, context)
|
||||||
else:
|
else:
|
||||||
if len(args) == 3:
|
if len(args) == 3:
|
||||||
return callable_str(descriptor, entity, context)
|
return callable_str(descriptor, entity, context)
|
||||||
@@ -245,11 +249,12 @@ def get_field_descriptor(
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def build_field_sequence(
|
async def build_field_sequence(
|
||||||
entity_descriptor: EntityDescriptor,
|
entity_descriptor: EntityDescriptor,
|
||||||
user: "UserBase",
|
user: "UserBase",
|
||||||
callback_data: ContextData,
|
callback_data: ContextData,
|
||||||
state_data: dict,
|
state_data: dict,
|
||||||
|
context: BotContext,
|
||||||
):
|
):
|
||||||
prev_form_list = None
|
prev_form_list = None
|
||||||
|
|
||||||
@@ -269,47 +274,57 @@ def build_field_sequence(
|
|||||||
prev_form_name, entity_descriptor.default_list
|
prev_form_name, entity_descriptor.default_list
|
||||||
)
|
)
|
||||||
|
|
||||||
|
entity_data = state_data.get("entity_data", {})
|
||||||
|
|
||||||
field_sequence = list[str]()
|
field_sequence = list[str]()
|
||||||
# exclude ownership fields from edit if user has no CREATE_ALL/UPDATE_ALL permission
|
# exclude ownership fields from edit if user has no CREATE_ALL/UPDATE_ALL permission
|
||||||
user_permissions = get_user_permissions(user, entity_descriptor)
|
user_permissions = get_user_permissions(user, entity_descriptor)
|
||||||
for fd in entity_descriptor.fields_descriptors.values():
|
for fd in entity_descriptor.fields_descriptors.values():
|
||||||
skip = False
|
if isinstance(fd.is_visible_in_edit_form, bool):
|
||||||
if (
|
skip = not fd.is_visible_in_edit_form
|
||||||
fd.is_optional
|
elif callable(fd.is_visible_in_edit_form):
|
||||||
or fd.field_name == "id"
|
if iscoroutinefunction(fd.is_visible_in_edit_form):
|
||||||
or (
|
skip = not await fd.is_visible_in_edit_form(fd, entity_data, context)
|
||||||
fd.field_name[-3:] == "_id"
|
else:
|
||||||
and fd.field_name[:-3] in entity_descriptor.fields_descriptors
|
skip = not fd.is_visible_in_edit_form(fd, entity_data, context)
|
||||||
)
|
else:
|
||||||
or fd.default is not None
|
skip = False
|
||||||
or fd.default_factory is not None
|
|
||||||
):
|
|
||||||
skip = True
|
|
||||||
for own_field in entity_descriptor.ownership_fields.items():
|
|
||||||
if (
|
if (
|
||||||
own_field[1].rstrip("_id") == fd.field_name.rstrip("_id")
|
fd.is_optional
|
||||||
and own_field[0] in user.roles
|
or fd.field_name == "id"
|
||||||
and (
|
or (
|
||||||
(
|
fd.field_name[-3:] == "_id"
|
||||||
EntityPermission.CREATE_ALL not in user_permissions
|
and fd.field_name[:-3] in entity_descriptor.fields_descriptors
|
||||||
and callback_data.context == CommandContext.ENTITY_CREATE
|
|
||||||
)
|
|
||||||
or (
|
|
||||||
EntityPermission.UPDATE_ALL not in user_permissions
|
|
||||||
and callback_data.context == CommandContext.ENTITY_EDIT
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
or fd.default is not None
|
||||||
|
or fd.default_factory is not None
|
||||||
):
|
):
|
||||||
skip = True
|
skip = True
|
||||||
break
|
for own_field in entity_descriptor.ownership_fields.items():
|
||||||
|
if (
|
||||||
|
own_field[1].rstrip("_id") == fd.field_name.rstrip("_id")
|
||||||
|
and own_field[0] in user.roles
|
||||||
|
and (
|
||||||
|
(
|
||||||
|
EntityPermission.CREATE_ALL not in user_permissions
|
||||||
|
and callback_data.context == CommandContext.ENTITY_CREATE
|
||||||
|
)
|
||||||
|
or (
|
||||||
|
EntityPermission.UPDATE_ALL not in user_permissions
|
||||||
|
and callback_data.context == CommandContext.ENTITY_EDIT
|
||||||
|
)
|
||||||
|
)
|
||||||
|
):
|
||||||
|
skip = True
|
||||||
|
break
|
||||||
|
|
||||||
if (
|
if (
|
||||||
prev_form_list
|
prev_form_list
|
||||||
and prev_form_list.static_filters
|
and prev_form_list.static_filters
|
||||||
and fd.field_name.rstrip("_id")
|
and fd.field_name.rstrip("_id")
|
||||||
in [f.field_name.rstrip("_id") for f in prev_form_list.static_filters]
|
in [f.field_name.rstrip("_id") for f in prev_form_list.static_filters]
|
||||||
):
|
):
|
||||||
skip = True
|
skip = True
|
||||||
|
|
||||||
if not skip:
|
if not skip:
|
||||||
field_sequence.append(fd.field_name)
|
field_sequence.append(fd.field_name)
|
||||||
|
|||||||
Reference in New Issue
Block a user