Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3208721d9e | ||
|
|
b7211368cc | ||
|
|
ca374cdea0 |
No files matched your search
@@ -1,86 +0,0 @@
|
|||||||
name: Build Docs
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
changes:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
# Set job outputs to values from filter step
|
|
||||||
outputs:
|
|
||||||
docs: ${{ steps.filter.outputs.docs }}
|
|
||||||
steps:
|
|
||||||
# - name: Base requirements
|
|
||||||
# run: |
|
|
||||||
# apk update && apk add --no-cache nodejs
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
# For pull requests it's not necessary to checkout the code but for the main branch it is
|
|
||||||
- uses: dorny/paths-filter@v3
|
|
||||||
id: filter
|
|
||||||
with:
|
|
||||||
filters: |
|
|
||||||
docs:
|
|
||||||
- README.md
|
|
||||||
- docs/**
|
|
||||||
- requirements-docs.txt
|
|
||||||
- pyproject.toml
|
|
||||||
- mkdocs.yml
|
|
||||||
- Dockerfile.docs-site
|
|
||||||
- docker-compose.docs-site.yml
|
|
||||||
- .gitea/workflows/build-docs.yml
|
|
||||||
- .gitea/workflows/deploy-docs.yml
|
|
||||||
|
|
||||||
build-docs:
|
|
||||||
needs:
|
|
||||||
- changes
|
|
||||||
if: ${{ needs.changes.outputs.docs == 'true' }}
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
container:
|
|
||||||
image: catthehacker/ubuntu:act-22.04
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Set up Python
|
|
||||||
uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
python-version: "3.13.2"
|
|
||||||
- name: Setup uv
|
|
||||||
uses: astral-sh/setup-uv@v5
|
|
||||||
with:
|
|
||||||
version: "0.6.3"
|
|
||||||
enable-cache: true
|
|
||||||
cache-dependency-glob: |
|
|
||||||
requirements**.txt
|
|
||||||
pyproject.toml
|
|
||||||
- name: Install docs extras
|
|
||||||
run: |
|
|
||||||
uv venv
|
|
||||||
uv pip install mkdocs-material
|
|
||||||
- name: Build Docs
|
|
||||||
run: .venv/bin/mkdocs build
|
|
||||||
- name: Build Docker Image
|
|
||||||
run: |
|
|
||||||
docker build -f Dockerfile.docs-site -t ${{ vars.STACK_NAME }} .
|
|
||||||
docker tag ${{ vars.STACK_NAME }}:latest registry.botforge.biz/${{ vars.STACK_NAME }}
|
|
||||||
docker login registry.botforge.biz -u ${{ secrets.REGISTRY_USER }} -p ${{ secrets.REGISTRY_PASSWORD }}
|
|
||||||
docker push registry.botforge.biz/${{ vars.STACK_NAME }}
|
|
||||||
|
|
||||||
deploy-docs:
|
|
||||||
needs:
|
|
||||||
- build-docs
|
|
||||||
runs-on: staging
|
|
||||||
env:
|
|
||||||
STACK_NAME: ${{ vars.STACK_NAME }}
|
|
||||||
DOMAIN: ${{ vars.DOMAIN }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Copy to working directory
|
|
||||||
run: |
|
|
||||||
mkdir -p ~/${{ vars.STACK_NAME }}
|
|
||||||
cp docker-compose.docs-site.yml ~/${{ vars.STACK_NAME }}/docker-compose.yml
|
|
||||||
- name: Start Docs Site
|
|
||||||
run: |
|
|
||||||
cd ~/${{ vars.STACK_NAME }}
|
|
||||||
docker compose up -d
|
|
||||||
|
|
||||||
+2
-6
@@ -1,10 +1,6 @@
|
|||||||
__pycache__
|
__pycache__
|
||||||
|
/backend/.venv
|
||||||
.env
|
.env
|
||||||
.pytest_cache
|
.pytest_cache
|
||||||
|
/backend/logs
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.venv
|
|
||||||
.python-version
|
|
||||||
uv.lock
|
|
||||||
dist
|
|
||||||
*.egg-info
|
|
||||||
site
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
3.13
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
FROM nginx
|
|
||||||
|
|
||||||
WORKDIR /usr/share/nginx/html
|
|
||||||
|
|
||||||
RUN rm -rf ./*
|
|
||||||
COPY ./site/ ./
|
|
||||||
|
|
||||||
CMD ["nginx", "-g", "daemon off;"]
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2025 Alexander Kalinovsky
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
<p align="center">
|
|
||||||
<a href="https://qbot.botforge.biz"><img src="https://qbot.botforge.biz/img/qbot.svg" alt="QBot"></a>
|
|
||||||
</p>
|
|
||||||
<p align="center">
|
|
||||||
<em>Telegram Bots Rapid Application Development (RAD) Framework.</em>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
**QBot** is a library for fast development of Telegram bots and mini-apps following the **RAD (Rapid Application Development)** principle in a **declarative style**.
|
|
||||||
|
|
||||||
## Key Features
|
|
||||||
|
|
||||||
- **Automatic CRUD Interface Generation** – Manage objects effortlessly without manual UI coding.
|
|
||||||
- **Built-in Field Editors** – Includes text inputs, date/time pickers, boolean switches, enums, and entity selectors.
|
|
||||||
- **Advanced Pagination & Filtering** – Easily organize and navigate large datasets.
|
|
||||||
- **Authentication & Authorization** – Role-based access control for secure interactions.
|
|
||||||
- **Context Preservation** – Store navigation stacks and user interaction states in the database.
|
|
||||||
- **Internationalization Support** – Localizable UI and string fields for multilingual bots.
|
|
||||||
|
|
||||||
**QBot** powered by **[FastAPI](https://fastapi.tiangolo.com)**, **[SQLModel](https://sqlmodel.tiangolo.com)** & **[aiogram](https://aiogram.dev)** – Leverage the full capabilities of these frameworks for high performance and flexibility.
|
|
||||||
|
|
||||||
## Benefits
|
|
||||||
|
|
||||||
- **Faster Development** – Automate repetitive tasks and build bots in record time.
|
|
||||||
- **Highly Modular** – Easily extend and customize functionality.
|
|
||||||
- **Less Code Duplication** – Focus on core features while QBot handles the rest.
|
|
||||||
- **Enterprise-Grade Structure** – Scalable, maintainable, and optimized for real-world usage.
|
|
||||||
|
|
||||||
## Example
|
|
||||||
```python
|
|
||||||
class AppEntity(BotEntity):
|
|
||||||
"""
|
|
||||||
BotEntity - business entity. Based on SQLModel, which provides ORM functionality,
|
|
||||||
basic CRUD functions and additional metadata, describing entities view and behaviour
|
|
||||||
"""
|
|
||||||
bot_entity_descriptor = Entity( # metadata attribute
|
|
||||||
name = "bot_entity", # Entity - descriptor for entity metadata
|
|
||||||
...
|
|
||||||
)
|
|
||||||
|
|
||||||
name: str # entity field with default sqlmodel's FieldInfo descriptor
|
|
||||||
# and default qbot's field descriptor
|
|
||||||
|
|
||||||
description: str | None = Field( # field with sqlmodel's descriptor
|
|
||||||
sa_type = String, index = True) # and default qbot's descriptor
|
|
||||||
|
|
||||||
age: int = EntityField( # field with qbot's descriptor
|
|
||||||
caption = "Age",
|
|
||||||
)
|
|
||||||
|
|
||||||
user_id: int | None = EntityField( # using both descriptors
|
|
||||||
sm_descriptor=Field(
|
|
||||||
sa_type=BigInteger,
|
|
||||||
foreign_key="user.id",
|
|
||||||
ondelete="RESTRICT",
|
|
||||||
nullable=True,
|
|
||||||
),
|
|
||||||
is_visible=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
user: Optional[User] = EntityField( # using Relationship as sqlmodel descriptor
|
|
||||||
sm_descriptor=Relationship(
|
|
||||||
back_populates="entities",
|
|
||||||
sa_relationship_kwargs={
|
|
||||||
"lazy": "selectin",
|
|
||||||
"foreign_keys": "Entity.user_id",
|
|
||||||
}
|
|
||||||
),
|
|
||||||
caption="User",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
app = QBotApp() # bot application based on FastAPI application
|
|
||||||
# providing Telegram API webhook handler
|
|
||||||
|
|
||||||
@app.command( # decorator for bot commands definition
|
|
||||||
name="menu",
|
|
||||||
caption="Main menu",
|
|
||||||
show_in_bot_commands=True, # shows command in bot's main menu
|
|
||||||
clear_navigation=True, # resets navigation stack between bot dialogues
|
|
||||||
)
|
|
||||||
async def menu(context: CommandCallbackContext):
|
|
||||||
context.message_text = "Main menu" # message text
|
|
||||||
context.keyboard_builder.row( # and buttons will be shown to user
|
|
||||||
InlineKeyboardButton(
|
|
||||||
text="Entities",
|
|
||||||
callback_data=ContextData( # callback query dataclass representing
|
|
||||||
command=CallbackCommand.ENTITY_LIST, # navigation context and its parameters
|
|
||||||
entity_name="bot_entity"
|
|
||||||
).pack(),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
```
|
|
||||||
## Result
|
|
||||||
|
|
||||||
<iframe width="100%" height="691" src="https://www.youtube.com/embed/ptTnoppkYfM" title="QBot Framework – The Open-Source RAD Tool for Telegram Bots" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
|
|
||||||
|
|
||||||
Here you can see the result - [YouTube Video with Bot](https://www.youtube.com/shorts/ptTnoppkYfM)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -12,10 +12,8 @@ from .model.descriptors import (
|
|||||||
EntityField as EntityField,
|
EntityField as EntityField,
|
||||||
EntityForm as EntityForm,
|
EntityForm as EntityForm,
|
||||||
EntityList as EntityList,
|
EntityList as EntityList,
|
||||||
Filter as Filter,
|
|
||||||
EntityPermission as EntityPermission,
|
EntityPermission as EntityPermission,
|
||||||
CommandCallbackContext as CommandCallbackContext,
|
CommandCallbackContext as CommandCallbackContext,
|
||||||
EntityEventContext as EntityEventContext,
|
|
||||||
CommandButton as CommandButton,
|
CommandButton as CommandButton,
|
||||||
FieldEditButton as FieldEditButton,
|
FieldEditButton as FieldEditButton,
|
||||||
InlineButton as InlineButton,
|
InlineButton as InlineButton,
|
||||||
@@ -10,6 +10,7 @@ from logging import getLogger
|
|||||||
|
|
||||||
|
|
||||||
logger = getLogger(__name__)
|
logger = getLogger(__name__)
|
||||||
|
logger.setLevel("DEBUG")
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+1
-15
@@ -1,4 +1,3 @@
|
|||||||
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
|
||||||
@@ -13,7 +12,7 @@ from ..user_handlers.main import cammand_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
|
||||||
from ....model.descriptors import EntityEventContext, FieldDescriptor
|
from ....model.descriptors import FieldDescriptor
|
||||||
from ....model.language import LanguageBase
|
from ....model.language import LanguageBase
|
||||||
from ....auth import authorize_command
|
from ....auth import authorize_command
|
||||||
from ....utils.main import (
|
from ....utils.main import (
|
||||||
@@ -282,12 +281,6 @@ async def process_field_edit_callback(message: Message | CallbackQuery, **kwargs
|
|||||||
commit=True,
|
commit=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
if entity_descriptor.on_created:
|
|
||||||
if iscoroutinefunction(entity_descriptor.on_created):
|
|
||||||
await entity_descriptor.on_created(new_entity, EntityEventContext(db_session=db_session, app=app))
|
|
||||||
else:
|
|
||||||
entity_descriptor.on_created(new_entity, EntityEventContext(db_session=db_session, app=app))
|
|
||||||
|
|
||||||
form_name = (
|
form_name = (
|
||||||
callback_data.form_params.split("&")[0]
|
callback_data.form_params.split("&")[0]
|
||||||
if callback_data.form_params
|
if callback_data.form_params
|
||||||
@@ -332,13 +325,6 @@ async def process_field_edit_callback(message: Message | CallbackQuery, **kwargs
|
|||||||
setattr(entity, key, value)
|
setattr(entity, key, value)
|
||||||
|
|
||||||
await db_session.commit()
|
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(entity, EntityEventContext(db_session=db_session, app=app))
|
|
||||||
else:
|
|
||||||
entity_descriptor.on_updated(entity, EntityEventContext(db_session=db_session, app=app))
|
|
||||||
|
|
||||||
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)
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
+1
-12
@@ -1,4 +1,3 @@
|
|||||||
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 CallbackQuery, InlineKeyboardButton
|
from aiogram.types import CallbackQuery, InlineKeyboardButton
|
||||||
@@ -6,8 +5,6 @@ from aiogram.utils.keyboard import InlineKeyboardBuilder
|
|||||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from qbot.model.descriptors import EntityEventContext
|
|
||||||
|
|
||||||
from ..context import ContextData, CallbackCommand
|
from ..context import ContextData, CallbackCommand
|
||||||
from ....model.user import UserBase
|
from ....model.user import UserBase
|
||||||
from ....model.settings import Settings
|
from ....model.settings import Settings
|
||||||
@@ -50,18 +47,10 @@ async def entity_delete_callback(query: CallbackQuery, **kwargs):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if callback_data.data == "yes":
|
if callback_data.data == "yes":
|
||||||
|
await entity_descriptor.type_.remove(
|
||||||
entity = await entity_descriptor.type_.remove(
|
|
||||||
session=db_session, id=int(callback_data.entity_id), commit=True
|
session=db_session, id=int(callback_data.entity_id), commit=True
|
||||||
)
|
)
|
||||||
|
|
||||||
if entity_descriptor.on_deleted:
|
|
||||||
if iscoroutinefunction(entity_descriptor.on_created):
|
|
||||||
await entity_descriptor.on_deleted(entity, EntityEventContext(db_session=db_session, app=app))
|
|
||||||
else:
|
|
||||||
entity_descriptor.on_deleted(entity, EntityEventContext(db_session=db_session, app=app))
|
|
||||||
|
|
||||||
|
|
||||||
await route_callback(message=query, **kwargs)
|
await route_callback(message=query, **kwargs)
|
||||||
|
|
||||||
elif not callback_data.data:
|
elif not callback_data.data:
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -31,7 +31,7 @@ class Config(BaseSettings):
|
|||||||
def API_DOMAIN(self) -> str:
|
def API_DOMAIN(self) -> str:
|
||||||
if self.ENVIRONMENT == "local":
|
if self.ENVIRONMENT == "local":
|
||||||
return self.DOMAIN
|
return self.DOMAIN
|
||||||
return f"{self.DOMAIN}"
|
return f"api.{self.DOMAIN}"
|
||||||
|
|
||||||
@computed_field
|
@computed_field
|
||||||
@property
|
@property
|
||||||
@@ -39,7 +39,7 @@ class Config(BaseSettings):
|
|||||||
if self.USE_NGROK:
|
if self.USE_NGROK:
|
||||||
return self.NGROK_URL
|
return self.NGROK_URL
|
||||||
return (
|
return (
|
||||||
f"https://{self.API_DOMAIN}"
|
f"{'http' if self.ENVIRONMENT == 'local' else 'https'}://{self.API_DOMAIN}"
|
||||||
)
|
)
|
||||||
|
|
||||||
API_PORT: int = 8000
|
API_PORT: int = 8000
|
||||||
File renamed without changes.
@@ -1,20 +0,0 @@
|
|||||||
services:
|
|
||||||
qbot-docs-site:
|
|
||||||
image: 'registry.botforge.biz/${STACK_NAME?Variable not set}:${TAG-latest}'
|
|
||||||
restart: always
|
|
||||||
networks:
|
|
||||||
- traefik-public
|
|
||||||
labels:
|
|
||||||
- traefik.enable=true
|
|
||||||
- traefik.docker.network=traefik-public
|
|
||||||
- traefik.constraint-label=traefik-public
|
|
||||||
- traefik.http.services.${STACK_NAME?Variable not set}.loadbalancer.server.port=80
|
|
||||||
- traefik.http.routers.${STACK_NAME?Variable not set}.rule=Host(`${DOMAIN?Variable not set}`)
|
|
||||||
- traefik.http.routers.${STACK_NAME?Variable not set}.entrypoints=websecure
|
|
||||||
- traefik.http.routers.${STACK_NAME?Variable not set}.tls=true
|
|
||||||
- traefik.http.routers.${STACK_NAME?Variable not set}.tls.certresolver=le
|
|
||||||
- traefik.http.routers.${STACK_NAME?Variable not set}.service=${STACK_NAME?Variable not set}
|
|
||||||
|
|
||||||
networks:
|
|
||||||
traefik-public:
|
|
||||||
external: true
|
|
||||||
Whitespace-only changes.
@@ -1,65 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
||||||
|
|
||||||
<svg
|
|
||||||
width="1500"
|
|
||||||
height="500"
|
|
||||||
viewBox="0 0 1500 500"
|
|
||||||
version="1.1"
|
|
||||||
id="svg1"
|
|
||||||
xml:space="preserve"
|
|
||||||
inkscape:version="1.4 (e7c3feb1, 2024-10-09)"
|
|
||||||
sodipodi:docname="qbot.svg"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
|
||||||
id="namedview1"
|
|
||||||
pagecolor="#ffffff"
|
|
||||||
bordercolor="#000000"
|
|
||||||
borderopacity="0.25"
|
|
||||||
inkscape:showpageshadow="2"
|
|
||||||
inkscape:pageopacity="0.0"
|
|
||||||
inkscape:pagecheckerboard="0"
|
|
||||||
inkscape:deskcolor="#d1d1d1"
|
|
||||||
inkscape:document-units="px"
|
|
||||||
inkscape:zoom="0.52452521"
|
|
||||||
inkscape:cx="704.4466"
|
|
||||||
inkscape:cy="297.41182"
|
|
||||||
inkscape:window-width="1608"
|
|
||||||
inkscape:window-height="1007"
|
|
||||||
inkscape:window-x="8"
|
|
||||||
inkscape:window-y="52"
|
|
||||||
inkscape:window-maximized="0"
|
|
||||||
inkscape:current-layer="layer1" /><defs
|
|
||||||
id="defs1"><rect
|
|
||||||
x="80.81143"
|
|
||||||
y="115.09507"
|
|
||||||
width="901.16989"
|
|
||||||
height="411.40365"
|
|
||||||
id="rect3" /><rect
|
|
||||||
x="90.606755"
|
|
||||||
y="102.85091"
|
|
||||||
width="416.30131"
|
|
||||||
height="306.1039"
|
|
||||||
id="rect2" /></defs><g
|
|
||||||
inkscape:label="Слой 1"
|
|
||||||
inkscape:groupmode="layer"
|
|
||||||
id="layer1"><g
|
|
||||||
id="g18"
|
|
||||||
transform="translate(118.93657)"><path
|
|
||||||
style="font-size:266.667px;font-family:'Arial Rounded MT Bold';-inkscape-font-specification:'Arial Rounded MT Bold, Normal';fill:#404040"
|
|
||||||
d="m 685.86904,314.58341 q 8.46355,5.72918 18.4896,11.19793 10.02606,5.33854 13.28127,8.59377 3.25521,3.125 3.25521,8.98437 0,4.16668 -3.90625,8.33335 -3.77605,4.16668 -9.24481,4.16668 -4.42708,0 -10.8073,-2.8646 -6.25001,-2.86459 -14.84377,-8.33334 -8.46355,-5.46876 -18.61981,-12.76044 -18.88023,9.63543 -46.35423,9.63543 -22.26565,0 -39.974,-7.03126 -17.57815,-7.16146 -29.55733,-20.44273 -11.97918,-13.41147 -18.09898,-31.77087 -5.98959,-18.3594 -5.98959,-39.97401 0,-22.00523 6.25001,-40.36463 6.38021,-18.3594 18.35939,-31.25004 11.97918,-12.89064 29.16671,-19.66148 17.18752,-6.90105 39.06254,-6.90105 29.68754,0 50.91152,12.10939 21.3542,11.97918 32.29171,34.24483 10.93751,22.13545 10.93751,52.0834 0,45.44276 -24.6094,72.0053 z m -30.33858,-21.09377 q 8.07293,-9.2448 11.84897,-21.87503 3.90626,-12.63022 3.90626,-29.29691 0,-20.96358 -6.77084,-36.32817 -6.77085,-15.3646 -19.40107,-23.17711 -12.50002,-7.94273 -28.77608,-7.94273 -11.58855,0 -21.4844,4.42709 -9.76563,4.29688 -16.9271,12.63022 -7.03126,8.33335 -11.19793,21.3542 -4.03647,12.89064 -4.03647,29.0365 0,32.94274 15.36461,50.78131 15.3646,17.70836 38.80213,17.70836 9.63543,0 19.79169,-4.03647 -6.1198,-4.55729 -15.3646,-9.11459 -9.1146,-4.5573 -12.50002,-7.03126 -3.38542,-2.47396 -3.38542,-7.03126 0,-3.90625 3.25521,-6.90105 3.25521,-2.99479 7.16147,-2.99479 11.84897,0 39.71359,19.79169 z m 169.53146,44.79172 h -58.3334 q -12.63023,0 -18.09898,-5.59898 -5.33855,-5.72917 -5.33855,-18.09897 V 171.09366 q 0,-12.63023 5.46876,-18.09898 5.59896,-5.59897 17.96877,-5.59897 h 61.84903 q 13.67189,0 23.69795,1.69271 10.02605,1.69271 17.96877,6.51043 6.77084,4.03646 11.97918,10.28647 5.20834,6.1198 7.94272,13.67189 2.73438,7.42188 2.73438,15.75523 0,28.64586 -28.64587,41.92713 37.63025,11.97918 37.63025,46.61464 0,16.01564 -8.20313,28.90629 -8.20314,12.76043 -22.13545,18.88023 -8.72396,3.64583 -20.0521,5.20834 -11.32814,1.43229 -26.43233,1.43229 z m -2.86458,-84.76574 h -40.23443 v 55.72924 h 41.53651 q 39.19276,0 39.19276,-28.25524 0,-14.45315 -10.15627,-20.96357 -10.15626,-6.51043 -30.33857,-6.51043 z m -40.23443,-77.08343 v 49.34902 h 35.41671 q 14.45314,0 22.26565,-2.73438 7.94272,-2.73437 12.10939,-10.41667 3.25522,-5.46876 3.25522,-12.2396 0,-14.45315 -10.28647,-19.14065 -10.28648,-4.81772 -31.38025,-4.81772 z m 283.98469,92.83866 q 0,15.88544 -4.9479,29.29691 -4.9479,13.41148 -14.3229,23.0469 -9.375,9.63543 -22.3959,14.84377 -13.0208,5.07813 -29.29688,5.07813 -16.14586,0 -29.0365,-5.20834 -12.89064,-5.20834 -22.39586,-14.84377 -9.37501,-9.76563 -14.32293,-22.91669 -4.81772,-13.28127 -4.81772,-29.29691 0,-16.14585 4.94793,-29.55733 4.94792,-13.41147 14.19272,-22.91669 9.2448,-9.50522 22.39586,-14.58335 13.15106,-5.20834 29.0365,-5.20834 16.14588,0 29.29688,5.20834 13.1511,5.20834 22.5261,14.84376 9.375,9.63543 14.1927,22.9167 4.9479,13.28126 4.9479,29.29691 z m -35.6771,0 q 0,-21.74482 -9.6354,-33.85421 -9.5052,-12.10939 -25.65108,-12.10939 -10.41668,0 -18.3594,5.46876 -7.94272,5.33855 -12.2396,15.88544 -4.29688,10.54689 -4.29688,24.60941 0,13.93231 4.16667,24.34899 4.29688,10.41668 12.10939,16.01564 7.94272,5.46876 18.61982,5.46876 16.14588,0 25.65108,-12.10939 9.6354,-12.2396 9.6354,-33.72401 z m 65.6251,-69.14071 h 3.9062 v -21.35419 q 0,-8.59376 0.3906,-13.41148 0.5209,-4.94792 2.6042,-8.46355 2.0833,-3.64584 5.9896,-5.85938 3.9063,-2.34375 8.724,-2.34375 6.7708,0 12.2396,5.07813 3.6458,3.38542 4.5573,8.33334 1.0416,4.81771 1.0416,13.8021 v 24.21878 h 13.0209 q 7.5521,0 11.4583,3.64584 4.0365,3.51563 4.0365,9.11459 0,7.16147 -5.7292,10.02606 -5.5989,2.86458 -16.1458,2.86458 h -6.6407 v 65.36467 q 0,8.33334 0.5209,12.89064 0.651,4.42709 3.125,7.29167 2.6041,2.73438 8.3333,2.73438 3.125,0 8.4636,-1.04167 5.3385,-1.17187 8.3333,-1.17187 4.2969,0 7.6823,3.51563 3.5156,3.38542 3.5156,8.46355 0,8.59376 -9.375,13.15106 -9.375,4.55729 -26.9531,4.55729 -16.6667,0 -25.2605,-5.59896 -8.5937,-5.59897 -11.3281,-15.49481 -2.6042,-9.89585 -2.6042,-26.43233 v -68.22924 h -4.6875 q -7.6823,0 -11.7188,-3.64584 -4.0364,-3.64583 -4.0364,-9.2448 0,-5.59896 4.1667,-9.11459 4.2968,-3.64584 12.3698,-3.64585 z"
|
|
||||||
id="text17"
|
|
||||||
aria-label="QBot" /><g
|
|
||||||
id="g17"
|
|
||||||
transform="matrix(1.195598,0,0,1.195598,-52.1047,-71.699942)"><path
|
|
||||||
id="path17"
|
|
||||||
style="-inkscape-font-specification:'Arial Rounded MT Bold, ';opacity:1;fill:#a587c9;fill-opacity:1"
|
|
||||||
d="m 272.44727,142.82422 c -6.05914,0.0153 -11.03743,2.03697 -14.95508,6.0293 -3.98025,4.51096 -6.09057,11.66026 -6.47657,21.24218 v 3.72461 c -0.0471,-0.0706 -0.0973,-0.13474 -0.14453,-0.20508 v 52.01758 18.20703 57.11524 c 0.0477,-0.0476 0.0968,-0.0871 0.14453,-0.13477 v 64.16602 c 2e-5,8.38844 1.13695,15.11813 3.29102,20.36523 0.73739,1.28016 1.58937,2.4395 2.56445,3.46875 3.62458,3.92663 8.21734,6.0553 13.75782,6.42383 0.5993,0.0286 1.18235,0.0711 1.8125,0.0723 6.05823,-0.0157 11.0359,-2.03742 14.95312,-6.0293 3.98025,-4.51096 6.09057,-11.66026 6.47656,-21.24219 v -3.72461 c 0.0471,0.0706 0.0974,0.13474 0.14453,0.20508 v -52.01953 -18.20508 -57.11523 c -0.0477,0.0476 -0.0968,0.0871 -0.14453,0.13476 V 173.1543 c 0,-8.38703 -1.13767,-15.11654 -3.29101,-20.36328 -0.73758,-1.28076 -1.58899,-2.44105 -2.56446,-3.47071 -3.6877,-3.99502 -8.37983,-6.1256 -14.05078,-6.4375 -0.50595,-0.0202 -0.99001,-0.0576 -1.51757,-0.0586 z" /><path
|
|
||||||
d="m 250.87109,300.95508 v -57.11524 c -0.79426,8.72199 -2.70097,16.33161 -5.73632,22.8125 -4.1111,8.77777 -9.55427,15.33464 -16.33204,19.66797 -6.77777,4.22221 -14.22287,6.33203 -22.33398,6.33203 -7.44443,0 -14.38954,-2.10982 -20.83398,-6.33203 -6.44444,-4.33333 -11.66602,-10.8902 -15.66602,-19.66797 -3.88888,-8.88888 -5.83398,-19.4438 -5.83398,-31.66601 0,-18.55553 4.05686,-32.88889 12.16796,-43 8.1111,-10.1111 18.27779,-15.16602 30.5,-15.16602 6,0 11.66668,1.22158 17,3.66602 5.33333,2.33333 10.05492,5.94509 14.16602,10.83398 4.11111,4.77777 7.33269,10.83269 9.66602,18.16602 1.65444,4.96334 2.70164,10.36877 3.23632,16.14648 v -52.01758 c -6.86066,-10.22654 -15.08687,-17.82908 -24.6875,-22.79492 -9.66665,-5.11111 -20.61177,-7.66601 -32.83398,-7.66601 -14.44443,0 -27.61113,3.60982 -39.5,10.83203 -11.77777,7.22221 -20.99936,17.77909 -27.66602,31.66797 -6.66666,13.77776 -10,30.33269 -10,49.66601 0,18.88887 3.27844,35.33204 9.83399,49.33203 6.66666,13.88887 15.77648,24.55556 27.33203,32 11.66665,7.44444 24.55687,11.16797 38.66797,11.16797 11.55555,0 22.05361,-2.27843 31.49804,-6.83398 9.50365,-4.64131 18.62118,-11.32337 27.35547,-20.03125 z"
|
|
||||||
style="-inkscape-font-specification:'Arial Rounded MT Bold, ';opacity:1;fill:#fabf9c;fill-opacity:1"
|
|
||||||
id="path13" /><path
|
|
||||||
d="m 294.01562,237.18555 v 57.11523 c 0.79416,-8.72281 2.70075,-16.33307 5.73633,-22.81445 4.1111,-8.77777 9.55426,-15.33269 16.33203,-19.66602 6.77777,-4.22221 14.22288,-6.33398 22.33399,-6.33398 7.44443,0 14.38954,2.11177 20.83398,6.33398 6.44444,4.33333 11.66602,10.88825 15.66602,19.66602 3.88888,8.88888 5.83398,19.4438 5.83398,31.66601 0,18.55553 -4.05687,32.88889 -12.16797,43 -8.1111,10.1111 -18.27779,15.16797 -30.5,15.16797 -6,0 -11.66668,-1.22353 -17,-3.66797 -5.33333,-2.33333 -10.05491,-5.94314 -14.16601,-10.83203 -4.11111,-4.77777 -7.33269,-10.83464 -9.66602,-18.16797 -1.6545,-4.96351 -2.70166,-10.36855 -3.23633,-16.14648 v 52.01953 c 6.86067,10.22655 15.08688,17.82909 24.6875,22.79492 9.66665,5.11111 20.61178,7.66602 32.83399,7.66602 14.44443,0 27.61113,-3.61178 39.5,-10.83399 11.77777,-7.22221 20.99936,-17.77713 27.66601,-31.66601 6.66667,-13.77776 10,-30.3327 10,-49.66602 10e-6,-18.88887 -3.27648,-35.33399 -9.83203,-49.33398 -6.66666,-13.88887 -15.77843,-24.55556 -27.33398,-32 -11.66665,-7.44444 -24.55492,-11.16602 -38.66602,-11.16602 -11.55555,0 -22.05557,2.27844 -31.5,6.83399 -9.50364,4.64131 -18.62118,11.32337 -27.35547,20.03125 z"
|
|
||||||
style="-inkscape-font-specification:'Arial Rounded MT Bold, ';opacity:1;fill:#a7a7fa;fill-opacity:1"
|
|
||||||
id="path15" /></g></g></g></svg>
|
|
||||||
|
Before Width: | Height: | Size: 9.4 KiB |
@@ -1,59 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
||||||
|
|
||||||
<svg
|
|
||||||
width="500"
|
|
||||||
height="500"
|
|
||||||
viewBox="0 0 500 500"
|
|
||||||
version="1.1"
|
|
||||||
id="svg1"
|
|
||||||
xml:space="preserve"
|
|
||||||
inkscape:version="1.4 (e7c3feb1, 2024-10-09)"
|
|
||||||
sodipodi:docname="qbot_1_1.svg"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
|
||||||
id="namedview1"
|
|
||||||
pagecolor="#ffffff"
|
|
||||||
bordercolor="#000000"
|
|
||||||
borderopacity="0.25"
|
|
||||||
inkscape:showpageshadow="2"
|
|
||||||
inkscape:pageopacity="0.0"
|
|
||||||
inkscape:pagecheckerboard="0"
|
|
||||||
inkscape:deskcolor="#d1d1d1"
|
|
||||||
inkscape:document-units="px"
|
|
||||||
inkscape:zoom="0.52452521"
|
|
||||||
inkscape:cx="346.02722"
|
|
||||||
inkscape:cy="171.58374"
|
|
||||||
inkscape:window-width="1608"
|
|
||||||
inkscape:window-height="1007"
|
|
||||||
inkscape:window-x="8"
|
|
||||||
inkscape:window-y="52"
|
|
||||||
inkscape:window-maximized="0"
|
|
||||||
inkscape:current-layer="layer1" /><defs
|
|
||||||
id="defs1"><rect
|
|
||||||
x="80.811432"
|
|
||||||
y="115.09507"
|
|
||||||
width="901.16986"
|
|
||||||
height="411.40366"
|
|
||||||
id="rect3" /><rect
|
|
||||||
x="90.606758"
|
|
||||||
y="102.85091"
|
|
||||||
width="416.3013"
|
|
||||||
height="306.10391"
|
|
||||||
id="rect2" /></defs><g
|
|
||||||
inkscape:label="Слой 1"
|
|
||||||
inkscape:groupmode="layer"
|
|
||||||
id="layer1"><g
|
|
||||||
id="g17"
|
|
||||||
transform="matrix(1.3786092,0,0,1.3786092,-125.59292,-120.94283)"><path
|
|
||||||
id="path17"
|
|
||||||
style="-inkscape-font-specification:'Arial Rounded MT Bold, ';opacity:1;fill:#a587c9;fill-opacity:1"
|
|
||||||
d="m 272.44727,142.82422 c -6.05914,0.0153 -11.03743,2.03697 -14.95508,6.0293 -3.98025,4.51096 -6.09057,11.66026 -6.47657,21.24218 v 3.72461 c -0.0471,-0.0706 -0.0973,-0.13474 -0.14453,-0.20508 v 52.01758 18.20703 57.11524 c 0.0477,-0.0476 0.0968,-0.0871 0.14453,-0.13477 v 64.16602 c 2e-5,8.38844 1.13695,15.11813 3.29102,20.36523 0.73739,1.28016 1.58937,2.4395 2.56445,3.46875 3.62458,3.92663 8.21734,6.0553 13.75782,6.42383 0.5993,0.0286 1.18235,0.0711 1.8125,0.0723 6.05823,-0.0157 11.0359,-2.03742 14.95312,-6.0293 3.98025,-4.51096 6.09057,-11.66026 6.47656,-21.24219 v -3.72461 c 0.0471,0.0706 0.0974,0.13474 0.14453,0.20508 v -52.01953 -18.20508 -57.11523 c -0.0477,0.0476 -0.0968,0.0871 -0.14453,0.13476 V 173.1543 c 0,-8.38703 -1.13767,-15.11654 -3.29101,-20.36328 -0.73758,-1.28076 -1.58899,-2.44105 -2.56446,-3.47071 -3.6877,-3.99502 -8.37983,-6.1256 -14.05078,-6.4375 -0.50595,-0.0202 -0.99001,-0.0576 -1.51757,-0.0586 z" /><path
|
|
||||||
d="m 250.87109,300.95508 v -57.11524 c -0.79426,8.72199 -2.70097,16.33161 -5.73632,22.8125 -4.1111,8.77777 -9.55427,15.33464 -16.33204,19.66797 -6.77777,4.22221 -14.22287,6.33203 -22.33398,6.33203 -7.44443,0 -14.38954,-2.10982 -20.83398,-6.33203 -6.44444,-4.33333 -11.66602,-10.8902 -15.66602,-19.66797 -3.88888,-8.88888 -5.83398,-19.4438 -5.83398,-31.66601 0,-18.55553 4.05686,-32.88889 12.16796,-43 8.1111,-10.1111 18.27779,-15.16602 30.5,-15.16602 6,0 11.66668,1.22158 17,3.66602 5.33333,2.33333 10.05492,5.94509 14.16602,10.83398 4.11111,4.77777 7.33269,10.83269 9.66602,18.16602 1.65444,4.96334 2.70164,10.36877 3.23632,16.14648 v -52.01758 c -6.86066,-10.22654 -15.08687,-17.82908 -24.6875,-22.79492 -9.66665,-5.11111 -20.61177,-7.66601 -32.83398,-7.66601 -14.44443,0 -27.61113,3.60982 -39.5,10.83203 -11.77777,7.22221 -20.99936,17.77909 -27.66602,31.66797 -6.66666,13.77776 -10,30.33269 -10,49.66601 0,18.88887 3.27844,35.33204 9.83399,49.33203 6.66666,13.88887 15.77648,24.55556 27.33203,32 11.66665,7.44444 24.55687,11.16797 38.66797,11.16797 11.55555,0 22.05361,-2.27843 31.49804,-6.83398 9.50365,-4.64131 18.62118,-11.32337 27.35547,-20.03125 z"
|
|
||||||
style="-inkscape-font-specification:'Arial Rounded MT Bold, ';opacity:1;fill:#fabf9c;fill-opacity:1"
|
|
||||||
id="path13" /><path
|
|
||||||
d="m 294.01562,237.18555 v 57.11523 c 0.79416,-8.72281 2.70075,-16.33307 5.73633,-22.81445 4.1111,-8.77777 9.55426,-15.33269 16.33203,-19.66602 6.77777,-4.22221 14.22288,-6.33398 22.33399,-6.33398 7.44443,0 14.38954,2.11177 20.83398,6.33398 6.44444,4.33333 11.66602,10.88825 15.66602,19.66602 3.88888,8.88888 5.83398,19.4438 5.83398,31.66601 0,18.55553 -4.05687,32.88889 -12.16797,43 -8.1111,10.1111 -18.27779,15.16797 -30.5,15.16797 -6,0 -11.66668,-1.22353 -17,-3.66797 -5.33333,-2.33333 -10.05491,-5.94314 -14.16601,-10.83203 -4.11111,-4.77777 -7.33269,-10.83464 -9.66602,-18.16797 -1.6545,-4.96351 -2.70166,-10.36855 -3.23633,-16.14648 v 52.01953 c 6.86067,10.22655 15.08688,17.82909 24.6875,22.79492 9.66665,5.11111 20.61178,7.66602 32.83399,7.66602 14.44443,0 27.61113,-3.61178 39.5,-10.83399 11.77777,-7.22221 20.99936,-17.77713 27.66601,-31.66601 6.66667,-13.77776 10,-30.3327 10,-49.66602 10e-6,-18.88887 -3.27648,-35.33399 -9.83203,-49.33398 -6.66666,-13.88887 -15.77843,-24.55556 -27.33398,-32 -11.66665,-7.44444 -24.55492,-11.16602 -38.66602,-11.16602 -11.55555,0 -22.05557,2.27844 -31.5,6.83399 -9.50364,4.64131 -18.62118,11.32337 -27.35547,20.03125 z"
|
|
||||||
style="-inkscape-font-specification:'Arial Rounded MT Bold, ';opacity:1;fill:#a7a7fa;fill-opacity:1"
|
|
||||||
id="path15" /></g></g></svg>
|
|
||||||
|
Before Width: | Height: | Size: 5.1 KiB |
-101
@@ -1,101 +0,0 @@
|
|||||||
<style>
|
|
||||||
h1 { display: none; }
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<p align="center">
|
|
||||||
<a href="https://qbot.botforge.biz"><img src="img/qbot.svg" alt="QBot"></a>
|
|
||||||
</p>
|
|
||||||
<p align="center">
|
|
||||||
<em>Telegram Bots Rapid Application Development (RAD) Framework.</em>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
**QBot** is a library for fast development of Telegram bots and mini-apps following the **RAD (Rapid Application Development)** principle in a **declarative style**.
|
|
||||||
|
|
||||||
## Key Features
|
|
||||||
|
|
||||||
- **Automatic CRUD Interface Generation** – Manage objects effortlessly without manual UI coding.
|
|
||||||
- **Built-in Field Editors** – Includes text inputs, date/time pickers, boolean switches, enums, and entity selectors.
|
|
||||||
- **Advanced Pagination & Filtering** – Easily organize and navigate large datasets.
|
|
||||||
- **Authentication & Authorization** – Role-based access control for secure interactions.
|
|
||||||
- **Context Preservation** – Store navigation stacks and user interaction states in the database.
|
|
||||||
- **Internationalization Support** – Localizable UI and string fields for multilingual bots.
|
|
||||||
|
|
||||||
**QBot** powered by **[FastAPI](https://fastapi.tiangolo.com)**, **[SQLModel](https://sqlmodel.tiangolo.com)** & **[aiogram](https://aiogram.dev)** – Leverage the full capabilities of these frameworks for high performance and flexibility.
|
|
||||||
|
|
||||||
## Benefits
|
|
||||||
|
|
||||||
- **Faster Development** – Automate repetitive tasks and build bots in record time.
|
|
||||||
- **Highly Modular** – Easily extend and customize functionality.
|
|
||||||
- **Less Code Duplication** – Focus on core features while QBot handles the rest.
|
|
||||||
- **Enterprise-Grade Structure** – Scalable, maintainable, and optimized for real-world usage.
|
|
||||||
|
|
||||||
## Example
|
|
||||||
```python
|
|
||||||
class AppEntity(BotEntity):
|
|
||||||
"""
|
|
||||||
BotEntity - business entity. Based on SQLModel, which provides ORM functionality,
|
|
||||||
basic CRUD functions and additional metadata, describing entities view and behaviour
|
|
||||||
"""
|
|
||||||
bot_entity_descriptor = Entity( # metadata attribute
|
|
||||||
name = "bot_entity", # Entity - descriptor for entity metadata
|
|
||||||
...
|
|
||||||
)
|
|
||||||
|
|
||||||
name: str # entity field with default sqlmodel's FieldInfo descriptor
|
|
||||||
# and default qbot's field descriptor
|
|
||||||
|
|
||||||
description: str | None = Field( # field with sqlmodel's descriptor
|
|
||||||
sa_type = String, index = True) # and default qbot's descriptor
|
|
||||||
|
|
||||||
age: int = EntityField( # field with qbot's descriptor
|
|
||||||
caption = "Age",
|
|
||||||
)
|
|
||||||
|
|
||||||
user_id: int | None = EntityField( # using both descriptors
|
|
||||||
sm_descriptor=Field(
|
|
||||||
sa_type=BigInteger,
|
|
||||||
foreign_key="user.id",
|
|
||||||
ondelete="RESTRICT",
|
|
||||||
nullable=True,
|
|
||||||
),
|
|
||||||
is_visible=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
user: Optional[User] = EntityField( # using Relationship as sqlmodel descriptor
|
|
||||||
sm_descriptor=Relationship(
|
|
||||||
back_populates="entities",
|
|
||||||
sa_relationship_kwargs={
|
|
||||||
"lazy": "selectin",
|
|
||||||
"foreign_keys": "Entity.user_id",
|
|
||||||
}
|
|
||||||
),
|
|
||||||
caption="User",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
app = QBotApp() # bot application based on FastAPI application
|
|
||||||
# providing Telegram API webhook handler
|
|
||||||
|
|
||||||
@app.command( # decorator for bot commands definition
|
|
||||||
name="menu",
|
|
||||||
caption="Main menu",
|
|
||||||
show_in_bot_commands=True, # shows command in bot's main menu
|
|
||||||
clear_navigation=True, # resets navigation stack between bot dialogues
|
|
||||||
)
|
|
||||||
async def menu(context: CommandCallbackContext):
|
|
||||||
context.message_text = "Main menu" # message text
|
|
||||||
context.keyboard_builder.row( # and buttons will be shown to user
|
|
||||||
InlineKeyboardButton(
|
|
||||||
text="Entities",
|
|
||||||
callback_data=ContextData( # callback query dataclass representing
|
|
||||||
command=CallbackCommand.ENTITY_LIST, # navigation context and its parameters
|
|
||||||
entity_name="bot_entity"
|
|
||||||
).pack(),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
```
|
|
||||||
## Result
|
|
||||||
|
|
||||||
<iframe width="100%" height="691" src="https://www.youtube.com/embed/ptTnoppkYfM" title="QBot Framework – The Open-Source RAD Tool for Telegram Bots" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
|
|
||||||
|
|
||||||
forced_change_
|
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
-14
@@ -1,14 +0,0 @@
|
|||||||
site_name: QBot Framework
|
|
||||||
site_url: https://qbot.botforge.biz
|
|
||||||
theme:
|
|
||||||
name: material
|
|
||||||
palette:
|
|
||||||
primary: #fabf9c
|
|
||||||
accent: #a7a7fa
|
|
||||||
font:
|
|
||||||
text: 'Roboto'
|
|
||||||
code: 'Roboto Mono'
|
|
||||||
logo: 'img/qbot_1_1.svg'
|
|
||||||
favicon: 'img/qbot_1_1.svg'
|
|
||||||
repo_name: botforge/qbot
|
|
||||||
repo_url: https://git.botforge.biz/botforge/qbot
|
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -160,7 +160,7 @@ class EnumType(TypeDecorator):
|
|||||||
impl = AutoString
|
impl = AutoString
|
||||||
cache_ok = True
|
cache_ok = True
|
||||||
|
|
||||||
def __init__(self, enum_type):
|
def __init__(self, enum_type: BotEnum):
|
||||||
self._enum_type = enum_type
|
self._enum_type = enum_type
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
File renamed without changes.
@@ -163,9 +163,6 @@ class _BaseEntityDescriptor:
|
|||||||
EntityPermission.DELETE_ALL: [RoleBase.SUPER_USER],
|
EntityPermission.DELETE_ALL: [RoleBase.SUPER_USER],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
on_created: Callable[["BotEntity", "EntityEventContext"], None] | None = None
|
|
||||||
on_deleted: Callable[["BotEntity", "EntityEventContext"], None] | None = None
|
|
||||||
on_updated: Callable[["BotEntity", "EntityEventContext"], None] | None = None
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(kw_only=True)
|
@dataclass(kw_only=True)
|
||||||
@@ -200,12 +197,6 @@ class CommandCallbackContext[UT: UserBase]:
|
|||||||
kwargs: dict[str, Any] = field(default_factory=dict)
|
kwargs: dict[str, Any] = field(default_factory=dict)
|
||||||
|
|
||||||
|
|
||||||
@dataclass(kw_only=True)
|
|
||||||
class EntityEventContext:
|
|
||||||
db_session: AsyncSession
|
|
||||||
app: "QBotApp"
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(kw_only=True)
|
@dataclass(kw_only=True)
|
||||||
class BotCommand:
|
class BotCommand:
|
||||||
name: str
|
name: str
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,29 +0,0 @@
|
|||||||
[project]
|
|
||||||
name = "quickbot"
|
|
||||||
version = "0.1.0"
|
|
||||||
description = "QBot - Rapid Application Development Framework for Telegram Bots"
|
|
||||||
readme = "README.md"
|
|
||||||
requires-python = ">=3.12"
|
|
||||||
classifiers = [
|
|
||||||
"Programming Language :: Python :: 3",
|
|
||||||
"Operating System :: OS Independent",
|
|
||||||
"Development Status :: 3 - Alpha",
|
|
||||||
]
|
|
||||||
authors = [
|
|
||||||
{ name = "Alexander Kalinovsky", email = "ak@botforge.biz" },
|
|
||||||
]
|
|
||||||
license = { file = "LICENSE" }
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
"aiogram>=3.17.0",
|
|
||||||
"babel>=2.17.0",
|
|
||||||
"fastapi[standard]>=0.115.8",
|
|
||||||
"greenlet>=3.1.1",
|
|
||||||
"mkdocs-material>=9.6.5",
|
|
||||||
"pydantic-settings>=2.7.1",
|
|
||||||
"pyngrok>=7.2.3",
|
|
||||||
"pytest>=8.3.4",
|
|
||||||
"ruff>=0.9.6",
|
|
||||||
"sqlmodel>=0.0.22",
|
|
||||||
"ujson>=5.10.0",
|
|
||||||
]
|
|
||||||
File renamed without changes.
-141
@@ -1,141 +0,0 @@
|
|||||||
from qbot import (
|
|
||||||
QBotApp,
|
|
||||||
BotEntity,
|
|
||||||
Entity,
|
|
||||||
EntityForm,
|
|
||||||
EntityList,
|
|
||||||
FieldEditButton,
|
|
||||||
EntityField,
|
|
||||||
CommandCallbackContext,
|
|
||||||
ContextData,
|
|
||||||
CallbackCommand,
|
|
||||||
)
|
|
||||||
from qbot.model.user import UserBase
|
|
||||||
from qbot.model.descriptors import Filter
|
|
||||||
from qbot.model.role import RoleBase
|
|
||||||
|
|
||||||
from aiogram.types import InlineKeyboardButton
|
|
||||||
from datetime import datetime
|
|
||||||
from sqlmodel import BigInteger, Field, Relationship
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
|
|
||||||
class User(UserBase):
|
|
||||||
|
|
||||||
bot_entity_descriptor = Entity(
|
|
||||||
icon="👤",
|
|
||||||
full_name="User",
|
|
||||||
full_name_plural="Users",
|
|
||||||
item_repr=lambda d, e: e.name,
|
|
||||||
default_list=EntityList(
|
|
||||||
show_add_new_button=False,
|
|
||||||
static_filters=[Filter("roles", "contains", value=RoleBase.SUPER_USER)],
|
|
||||||
filtering=True,
|
|
||||||
filtering_fields=["name"],
|
|
||||||
),
|
|
||||||
default_form=EntityForm(
|
|
||||||
show_delete_button=False,
|
|
||||||
show_edit_button=False,
|
|
||||||
form_buttons=[
|
|
||||||
[
|
|
||||||
FieldEditButton("name"),
|
|
||||||
FieldEditButton("is_active"),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
FieldEditButton("lang"),
|
|
||||||
FieldEditButton("roles"),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
item_repr=lambda d, e: f"{e.name}\n{
|
|
||||||
'is active' if e.is_active else 'is not active'
|
|
||||||
}\nlang: {e.lang.localized()}\nroles: [{
|
|
||||||
', '.join([r.localized() for r in e.roles]) if e.roles else 'none'
|
|
||||||
}]",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Entity(BotEntity):
|
|
||||||
|
|
||||||
bot_entity_descriptor = Entity(
|
|
||||||
icon="📦",
|
|
||||||
full_name="Entity",
|
|
||||||
full_name_plural="Entities",
|
|
||||||
item_repr=lambda d, e: e.name,
|
|
||||||
default_list=EntityList(
|
|
||||||
filtering=True,
|
|
||||||
filtering_fields=["name"],
|
|
||||||
order_by="name",
|
|
||||||
),
|
|
||||||
default_form=EntityForm(
|
|
||||||
form_buttons=[
|
|
||||||
[
|
|
||||||
FieldEditButton("name"),
|
|
||||||
FieldEditButton("user"),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
FieldEditButton("creation_dt"),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
id: int = EntityField(
|
|
||||||
caption="ID",
|
|
||||||
sm_descriptor=Field(
|
|
||||||
primary_key=True,
|
|
||||||
),
|
|
||||||
is_visible=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
name: str = EntityField(
|
|
||||||
caption="Name",
|
|
||||||
)
|
|
||||||
|
|
||||||
creation_dt: datetime = EntityField(
|
|
||||||
caption="Creation date",
|
|
||||||
dt_type="datetime",
|
|
||||||
)
|
|
||||||
|
|
||||||
user_id: int | None = EntityField(
|
|
||||||
sm_descriptor=Field(
|
|
||||||
sa_type=BigInteger,
|
|
||||||
foreign_key="user.id",
|
|
||||||
ondelete="RESTRICT",
|
|
||||||
nullable=True,
|
|
||||||
),
|
|
||||||
is_visible=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
user: Optional[User] = EntityField(
|
|
||||||
sm_descriptor=Relationship(
|
|
||||||
sa_relationship_kwargs={
|
|
||||||
"lazy": "selectin",
|
|
||||||
"foreign_keys": "Entity.user_id",
|
|
||||||
}
|
|
||||||
),
|
|
||||||
caption="User",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
app = QBotApp(
|
|
||||||
user_class=User,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@app.command(
|
|
||||||
name="menu",
|
|
||||||
caption="Main menu",
|
|
||||||
show_in_bot_commands=True,
|
|
||||||
clear_navigation=True,
|
|
||||||
)
|
|
||||||
async def menu(context: CommandCallbackContext):
|
|
||||||
context.message_text = "Main menu"
|
|
||||||
context.keyboard_builder.row(
|
|
||||||
InlineKeyboardButton(
|
|
||||||
text="Entities",
|
|
||||||
callback_data=ContextData(
|
|
||||||
command=CallbackCommand.MENU_ENTRY_ENTITIES,
|
|
||||||
).pack(),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
Whitespace-only changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Reference in new issue
Block a user