комментарии на инструментах

This commit is contained in:
2025-12-20 23:22:51 +03:00
parent d7bfdb69a6
commit 83ecf65abf
11 changed files with 336 additions and 65 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
+26 -1
View File
@@ -1,5 +1,6 @@
from datetime import datetime
from db.handlers.stock import StockHandler
from db.handlers.user import UserHandler
from utils import logger, saveImage, safeFilename
from db import CRUD
from db.schemas.toolkit import Toolkit
@@ -175,7 +176,12 @@ class ToolkitHandler:
if not toolkit:
logger.error("Инструмент не найден")
return {}
return toolkit.toDict()
if toolkit.comment_user_id:
user_data = await UserHandler.get(toolkit.comment_user_id)
data = toolkit.toDict()
data["comment_user_data"] = user_data
logger.info(data)
return data
async def getSeveral(toolkitIds: list[int]) -> list[dict]:
query = select(Toolkit).where(Toolkit.id.in_(toolkitIds))
@@ -211,6 +217,25 @@ class ToolkitHandler:
)
return {"status": "ok"} if result else {"errorMessage": "Инструмент не удален"}
async def addComment(toolkitId: int, user_id: int, comment: str):
logger.info(f"Добавление комментария к инструменту {toolkitId}...")
logger.info(f"Комментарий: {comment}")
logger.info(f"Пользователь: {user_id}")
query = select(Toolkit).where(Toolkit.id == toolkitId)
toolkit = await CRUD.read(query)
if not toolkit:
logger.error("Инструмент не найден")
return {"errorMessage": "Инструмент не найден"}
try:
await toolkit.edit(
comment_text=comment, comment_user_id=user_id, comment_at=datetime.now()
)
except Exception as e:
logger.error(f"Ошибка добавления комментария: {str(e)}")
return {"errorMessage": f"Ошибка добавления комментария: {str(e)}"}
logger.info(f"Комментарий к инструменту {toolkit.title} успешно добавлен")
return {"status": "ok"}
async def initialize():
from .categories import CategoryHandler
+140 -57
View File
@@ -1,3 +1,12 @@
from typing import Optional
from sqlalchemy import inspect, text
from sqlalchemy.ext.asyncio import create_async_engine, AsyncEngine
from sqlalchemy.schema import CreateTable
from sqlalchemy.dialects.postgresql import dialect as pg_dialect
from utils import logger
from db.handlers.access import AccessLevelHandler
from db.handlers.user import UserHandler
from db.handlers.toolbox import ToolboxHandler
@@ -5,12 +14,6 @@ from db.handlers.categories import CategoryHandler
from db.handlers.toolkit import ToolkitHandler
from db.handlers.actions import StocksActions
from typing import Optional
from sqlalchemy import inspect, text
from sqlalchemy.ext.asyncio import create_async_engine, AsyncEngine
from sqlalchemy.schema import CreateTable
from utils import logger
class DatabaseInitializer:
existing_tables: Optional[list[str]] = None
@@ -22,6 +25,10 @@ class DatabaseInitializer:
self.engine: Optional[AsyncEngine] = None
self.metadata = Base.metadata
# ==========================================================
# PUBLIC
# ==========================================================
async def initialize(self, force: bool = False, reNewDB: bool = False):
"""Main database initialization method"""
try:
@@ -29,32 +36,39 @@ class DatabaseInitializer:
async with self.engine.begin() as conn:
if force:
logger.info("Принудительное удаление и создание баз...")
logger.warning("Принудительное удаление и создание БД...")
await self._drop_all()
await self._create_tables_directly()
await self._initialize_data(reNewDB)
elif not await self._check_tables_exist(conn):
logger.info("Не все необходимые таблицы существуют. Создаем...")
return
tables_exist = await self._check_tables_exist(conn)
if not tables_exist:
logger.warning("Не все таблицы существуют. Создаем недостающие...")
await self._create_tables_directly()
# Проверяем после создания
async with self.engine.begin() as conn:
if not await self._check_tables_exist(conn):
raise RuntimeError("Не все необходимые таблицы существуют!")
# 🔥 СИНХРОНИЗАЦИЯ СХЕМЫ
logger.info("Синхронизация схемы БД...")
await self._sync_schema(conn)
if reNewDB:
logger.info("Принудительная загрузка данных...")
await self._initialize_data(reNewDB)
else:
logger.info("Все необходимые таблицы существуют. Пропускаем...")
if reNewDB:
logger.warning("Принудительная загрузка данных...")
await self._initialize_data(reNewDB)
logger.info("Инициализация БД завершена успешно")
except Exception as e:
logger.error(f"Инициализация базы завершилась ошибкой: {str(e)}")
logger.exception("Инициализация базы завершилась ошибкой")
raise
finally:
if self.engine:
await self.engine.dispose()
# ==========================================================
# CHECK
# ==========================================================
async def _check_tables_exist(self, conn) -> bool:
"""Check if all tables from metadata exist"""
try:
@@ -70,12 +84,8 @@ class DatabaseInitializer:
missing_tables = required_tables - set(DatabaseInitializer.existing_tables)
if missing_tables:
logger.warning("Существующие таблицы:")
logger.info(DatabaseInitializer.existing_tables)
logger.warning("Необходимые таблицы:")
logger.info(required_tables)
logger.warning("Отсутствующие таблицы:")
logger.info(missing_tables)
logger.warning(missing_tables)
return False
return True
@@ -83,62 +93,135 @@ class DatabaseInitializer:
logger.warning(f"Проверка таблиц завершилась ошибкой: {str(e)}")
return False
# ==========================================================
# CREATE / DROP
# ==========================================================
async def _create_tables_directly(self):
"""Create tables directly using SQLAlchemy (bypass Alembic)"""
async with self.engine.begin() as conn:
# Создаем все таблицы из метаданных
for table in self.metadata.sorted_tables:
try:
if (
DatabaseInitializer.existing_tables
and table.name in DatabaseInitializer.existing_tables
):
logger.debug(
f"Таблица {table.name} уже существует. Пропускаем..."
)
continue
create_stmt = CreateTable(table)
logger.info(f"Создаем таблицу: {table.name}")
await conn.execute(create_stmt)
except Exception as e:
logger.error(f"Ошибка создания таблицы: {str(e)}")
logger.warning("Все таблицы успешно созданы")
if (
DatabaseInitializer.existing_tables
and table.name in DatabaseInitializer.existing_tables
):
logger.debug(f"Таблица {table.name} уже существует")
continue
logger.info(f"Создаем таблицу: {table.name}")
await conn.execute(CreateTable(table))
async def _drop_all(self):
"""Drop all tables"""
async with self.engine.begin() as conn:
# Получаем список всех таблиц
existing_tables = await conn.run_sync(
lambda sync_conn: inspect(sync_conn).get_table_names()
)
for table in existing_tables:
drop_stmt = text(
f'DROP TABLE "{table}" CASCADE'
) # Кавычки на случай спец. символов
logger.info(f"Удаляем таблицу: {table}")
await conn.execute(drop_stmt)
logger.warning(f"Удаляем таблицу: {table}")
await conn.execute(text(f'DROP TABLE "{table}" CASCADE'))
logger.warning("Все таблицы успешно удалены")
# ==========================================================
# SCHEMA SYNC
# ==========================================================
async def _sync_schema(self, conn):
await self._add_missing_columns(conn)
await self._add_missing_foreign_keys(conn)
async def _add_missing_columns(self, conn):
for table_name, table in self.metadata.tables.items():
db_columns = await conn.run_sync(
lambda sync_conn: {
col["name"] for col in inspect(sync_conn).get_columns(table_name)
}
)
for column in table.columns:
if column.name in db_columns:
continue
ddl = (
f"ALTER TABLE {table_name} "
f"ADD COLUMN {self._compile_column(column)}"
)
logger.warning(f"[ADD COLUMN] {table_name}.{column.name}")
await conn.execute(text(ddl))
async def _add_missing_foreign_keys(self, conn):
for table_name, table in self.metadata.tables.items():
db_fks = await conn.run_sync(
lambda sync_conn: inspect(sync_conn).get_foreign_keys(table_name)
)
db_fk_pairs = {
(
tuple(fk["constrained_columns"]),
fk["referred_table"],
)
for fk in db_fks
}
for fk in table.foreign_keys:
col = fk.parent.name
ref_table = fk.column.table.name
ref_col = fk.column.name
key = ((col,), ref_table)
if key in db_fk_pairs:
continue
constraint_name = f"fk_{table_name}_{col}"
ddl = f"""
ALTER TABLE {table_name}
ADD CONSTRAINT {constraint_name}
FOREIGN KEY ({col})
REFERENCES {ref_table}({ref_col})
"""
if fk.ondelete:
ddl += f" ON DELETE {fk.ondelete}"
logger.warning(f"[ADD FK] {table_name}.{col}{ref_table}.{ref_col}")
await conn.execute(text(ddl))
# ==========================================================
# HELPERS
# ==========================================================
def _compile_column(self, column) -> str:
"""Compile SQLAlchemy Column to SQL"""
ddl = f"{column.name} {column.type.compile(dialect=pg_dialect())}"
if not column.nullable:
ddl += " NULL" # безопасно, NOT NULL позже вручную
if column.server_default is not None:
ddl += f" DEFAULT {column.server_default.arg}"
return ddl
# ==========================================================
# DATA INIT
# ==========================================================
async def _initialize_data(self, waiting: bool = False):
"""Initialize required data"""
try:
logger.info("Инициализация данных...")
logger.warning("Инициализация Прав доступа...")
await AccessLevelHandler.initialize()
logger.warning("Инициализация Пользователей...")
await UserHandler.initialize()
logger.warning("Инициализация Туллбоксов...")
await ToolboxHandler.initialize()
logger.warning("Инициализация Категорий...")
await CategoryHandler.initialize()
logger.warning("Инициализация Инструментов...")
await ToolkitHandler.initialize()
logger.warning("Инициализация Складов...")
await StocksActions.initialize()
logger.info("Данные успешно инициализированы")
except Exception as e:
logger.error(f"Инициализация данных завершилась ошибкой: {str(e)}")
except Exception:
logger.exception("Ошибка инициализации данных")
raise
Binary file not shown.
+5 -7
View File
@@ -14,18 +14,16 @@ class Toolkit(Base):
description = Column(Text, nullable=True)
specifications = Column(JSONB, default={})
category_id = Column(Integer, ForeignKey("categories.id", ondelete="CASCADE"))
category_data = relationship(
"Category",
cascade="all, delete-orphan",
lazy="joined",
uselist=False,
single_parent=True,
)
image = Column(JSONB)
quantity_min = Column(Integer, nullable=True)
quantity_min_extra = Column(Integer, nullable=True)
external_link = Column(String, nullable=True)
hidden = Column(Boolean, default=False)
comment_text = Column(Text, nullable=True)
comment_user_id = Column(
Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=True
)
comment_at = Column(DateTime, nullable=True)
created_at = Column(DateTime, default=datetime.now)
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)
refilled_at = Column(DateTime, default=datetime.now)