first
This commit is contained in:
64
scripts/init_db.py
Normal file
64
scripts/init_db.py
Normal file
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Скрипт для инициализации базы данных
|
||||
"""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.append(str(Path(__file__).parent.parent))
|
||||
|
||||
from sqlalchemy import create_engine, text
|
||||
from app.config import settings
|
||||
from app.models.base import Base
|
||||
import logging
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def init_database():
|
||||
"""Синхронная инициализация БД"""
|
||||
try:
|
||||
logger.info(f"Connecting to: {settings.DATABASE_URL}")
|
||||
|
||||
# Создаем синхронный движок
|
||||
engine = create_engine(
|
||||
settings.DATABASE_URL,
|
||||
echo=settings.DEBUG,
|
||||
pool_pre_ping=True,
|
||||
)
|
||||
|
||||
# Создаем таблицы
|
||||
logger.info("Creating database tables...")
|
||||
Base.metadata.create_all(bind=engine)
|
||||
logger.info("✅ Database tables created successfully!")
|
||||
|
||||
# Проверяем подключение - используем text()
|
||||
with engine.connect() as conn:
|
||||
result = conn.execute(text("SELECT version()"))
|
||||
version = result.scalar()
|
||||
logger.info(f"✅ PostgreSQL version: {version[:50]}...")
|
||||
|
||||
# Показываем созданные таблицы
|
||||
result = conn.execute(text("""
|
||||
SELECT table_name
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'public'
|
||||
ORDER BY table_name
|
||||
"""))
|
||||
tables = [row[0] for row in result]
|
||||
if tables:
|
||||
logger.info(f"✅ Created tables: {', '.join(tables)}")
|
||||
else:
|
||||
logger.warning("No tables found")
|
||||
|
||||
engine.dispose()
|
||||
logger.info("Database initialization completed successfully!")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Failed to initialize database: {e}")
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
init_database()
|
||||
31
scripts/init_db.sql
Normal file
31
scripts/init_db.sql
Normal file
@@ -0,0 +1,31 @@
|
||||
-- Включение расширений PostgreSQL
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||
CREATE EXTENSION IF NOT EXISTS "pg_trgm"; -- для поиска по шаблону
|
||||
CREATE EXTENSION IF NOT EXISTS "btree_gin"; -- для составных индексов
|
||||
|
||||
-- Создание кастомных индексов для JSONB
|
||||
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_submissions_email
|
||||
ON submissions ((data->>'email')) WHERE data ? 'email';
|
||||
|
||||
-- Функция для валидации JSONB схемы
|
||||
CREATE OR REPLACE FUNCTION validate_submission_data()
|
||||
RETURNS trigger AS $$
|
||||
BEGIN
|
||||
-- Проверяем, что data не пустой
|
||||
IF NEW.data IS NULL OR NEW.data = '{}'::jsonb THEN
|
||||
RAISE EXCEPTION 'Submission data cannot be empty';
|
||||
END IF;
|
||||
|
||||
-- Проверяем, что все обязательные поля присутствуют
|
||||
IF NEW.form_id IS NOT NULL THEN
|
||||
-- Дополнительная валидация через триггер
|
||||
END IF;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER validate_submission
|
||||
BEFORE INSERT OR UPDATE ON submissions
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION validate_submission_data();
|
||||
93
scripts/init_db_with_create.py
Normal file
93
scripts/init_db_with_create.py
Normal file
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Скрипт для инициализации базы данных с автоматическим созданием
|
||||
"""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.append(str(Path(__file__).parent.parent))
|
||||
|
||||
from sqlalchemy import create_engine, text
|
||||
from sqlalchemy.exc import OperationalError
|
||||
from app.config import settings
|
||||
from app.models.base import Base
|
||||
import logging
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def create_database_if_not_exists():
|
||||
"""Создает базу данных если она не существует"""
|
||||
# Подключаемся к default database (postgres)
|
||||
default_db_url = settings.DATABASE_URL.replace(f"/{settings.DB_NAME}", "/postgres")
|
||||
|
||||
try:
|
||||
engine = create_engine(default_db_url, isolation_level="AUTOCOMMIT")
|
||||
with engine.connect() as conn:
|
||||
# Проверяем существует ли база
|
||||
result = conn.execute(
|
||||
text(f"SELECT 1 FROM pg_database WHERE datname = '{settings.DB_NAME}'")
|
||||
)
|
||||
exists = result.fetchone()
|
||||
|
||||
if not exists:
|
||||
logger.info(f"Creating database '{settings.DB_NAME}'...")
|
||||
conn.execute(text(f"CREATE DATABASE {settings.DB_NAME}"))
|
||||
logger.info(f"✅ Database '{settings.DB_NAME}' created successfully!")
|
||||
else:
|
||||
logger.info(f"✅ Database '{settings.DB_NAME}' already exists")
|
||||
|
||||
engine.dispose()
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Failed to create database: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def init_database():
|
||||
"""Инициализация таблиц в БД"""
|
||||
try:
|
||||
# Сначала создаем БД если нужно
|
||||
if not create_database_if_not_exists():
|
||||
logger.error("Cannot proceed without database")
|
||||
return False
|
||||
|
||||
logger.info(f"Connecting to: {settings.DATABASE_URL}")
|
||||
|
||||
# Создаем движок для основной БД
|
||||
engine = create_engine(
|
||||
settings.DATABASE_URL,
|
||||
echo=settings.DEBUG,
|
||||
pool_pre_ping=True,
|
||||
)
|
||||
|
||||
# Создаем таблицы
|
||||
logger.info("Creating database tables...")
|
||||
Base.metadata.create_all(bind=engine)
|
||||
logger.info("✅ Database tables created successfully!")
|
||||
|
||||
# Проверяем созданные таблицы
|
||||
with engine.connect() as conn:
|
||||
result = conn.execute(text("""
|
||||
SELECT table_name
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'public'
|
||||
ORDER BY table_name
|
||||
"""))
|
||||
tables = [row[0] for row in result]
|
||||
logger.info(f"Created tables: {', '.join(tables)}")
|
||||
|
||||
engine.dispose()
|
||||
logger.info("Database initialization completed successfully!")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Failed to initialize database: {e}")
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
success = init_database()
|
||||
sys.exit(0 if success else 1)
|
||||
90
scripts/seed_data.py
Normal file
90
scripts/seed_data.py
Normal file
@@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Скрипт для заполнения базы данных тестовыми данными
|
||||
"""
|
||||
import asyncio
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
sys.path.append(str(Path(__file__).parent.parent))
|
||||
|
||||
from app.database import AsyncSessionLocal
|
||||
from app.models.form import Form, Field, FormField
|
||||
from app.models.submission import Submission
|
||||
from app.schemas.field import FieldType
|
||||
import logging
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def seed_database():
|
||||
"""Заполнение тестовыми данными"""
|
||||
async with AsyncSessionLocal() as db:
|
||||
try:
|
||||
# Создаем тестовые поля
|
||||
fields_data = [
|
||||
{"name": "full_name", "label": "Full Name", "field_type": FieldType.TEXT},
|
||||
{"name": "email", "label": "Email Address", "field_type": FieldType.EMAIL},
|
||||
{"name": "age", "label": "Age", "field_type": FieldType.NUMBER},
|
||||
{"name": "country", "label": "Country", "field_type": FieldType.SELECT,
|
||||
"options": {"options": ["USA", "UK", "Canada", "Australia"]}},
|
||||
]
|
||||
|
||||
fields = []
|
||||
for field_data in fields_data:
|
||||
field = Field(**field_data)
|
||||
db.add(field)
|
||||
fields.append(field)
|
||||
|
||||
await db.flush()
|
||||
|
||||
# Создаем тестовую форму
|
||||
form = Form(
|
||||
name="Test Registration Form",
|
||||
description="Test form for development",
|
||||
is_active=True,
|
||||
is_published=True
|
||||
)
|
||||
db.add(form)
|
||||
await db.flush()
|
||||
|
||||
# Связываем поля с формой
|
||||
for i, field in enumerate(fields):
|
||||
form_field = FormField(
|
||||
form_id=form.id,
|
||||
field_id=field.id,
|
||||
order=i,
|
||||
is_required=True if i < 2 else False
|
||||
)
|
||||
db.add(form_field)
|
||||
|
||||
# Создаем тестовые submission'ы
|
||||
test_data = [
|
||||
{"full_name": "John Doe", "email": "john@example.com", "age": 30, "country": "USA"},
|
||||
{"full_name": "Jane Smith", "email": "jane@example.com", "age": 25, "country": "UK"},
|
||||
{"full_name": "Bob Johnson", "email": "bob@example.com", "age": 35, "country": "Canada"},
|
||||
]
|
||||
|
||||
for data in test_data:
|
||||
submission = Submission(
|
||||
form_id=form.id,
|
||||
data=data,
|
||||
status="completed",
|
||||
submitted_at=datetime.utcnow()
|
||||
)
|
||||
db.add(submission)
|
||||
|
||||
await db.commit()
|
||||
logger.info(
|
||||
f"Test data seeded successfully! Created form '{form.name}' with {len(fields)} fields and {len(test_data)} submissions")
|
||||
|
||||
except Exception as e:
|
||||
await db.rollback()
|
||||
logger.error(f"Failed to seed database: {e}")
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(seed_database())
|
||||
15
scripts/test_models.py
Normal file
15
scripts/test_models.py
Normal file
@@ -0,0 +1,15 @@
|
||||
# test_models.py
|
||||
from app.database import SessionLocal
|
||||
from app.models.form import Form, Field, FormField
|
||||
|
||||
db = SessionLocal()
|
||||
|
||||
# Создаем тестовую форму
|
||||
form = Form(name="Test Form", description="Test")
|
||||
db.add(form)
|
||||
db.commit()
|
||||
|
||||
print(f"Created form with id: {form.id}")
|
||||
print("Database is working correctly!")
|
||||
|
||||
db.close()
|
||||
Reference in New Issue
Block a user