pgsql_mysql_docker #4
3
.gitignore
vendored
3
.gitignore
vendored
@@ -5,4 +5,5 @@ env
|
||||
__pycache__
|
||||
instance/
|
||||
uploads/
|
||||
.DS_Store
|
||||
.DS_Store
|
||||
db/*
|
||||
|
@@ -1,26 +1,38 @@
|
||||
Scenariusz migracji
|
||||
python3 -m venv venv_migrate
|
||||
source venv_migrate/bin/activate
|
||||
pip install sqlalchemy psycopg2-binary dotenv
|
||||
docker compose --profile pgsql up -d --build
|
||||
PYTHONPATH=. python3 _tools/db/migrate_sqlite_to_pgsql.py
|
||||
rm -rf venv_migrate
|
||||
|
||||
Zatrzymaj obecny kontener:
|
||||
# reset wszystkich sekwencji w pgsql
|
||||
docker exec -it pgsql-db psql -U lista -d lista
|
||||
|
||||
|
||||
docker-compose down
|
||||
Ustaw w .env:
|
||||
|
||||
.env
|
||||
|
||||
DB_ENGINE=pgsql
|
||||
DB_NAME=myapp
|
||||
DB_USER=user
|
||||
DB_PASSWORD=pass
|
||||
Uruchom bazę i aplikację:
|
||||
|
||||
docker-compose --profile pgsql up -d
|
||||
Wykonaj migrację danych:
|
||||
|
||||
|
||||
bash _tools/db/migrate_to_new_db.sh pgsql
|
||||
|
||||
|
||||
|
||||
Sprawdź działanie aplikacji.
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
r RECORD;
|
||||
BEGIN
|
||||
FOR r IN
|
||||
SELECT
|
||||
c.relname AS seq_name,
|
||||
t.relname AS table_name,
|
||||
a.attname AS column_name
|
||||
FROM
|
||||
pg_class c
|
||||
JOIN
|
||||
pg_depend d ON d.objid = c.oid
|
||||
JOIN
|
||||
pg_class t ON d.refobjid = t.oid
|
||||
JOIN
|
||||
pg_attribute a ON a.attrelid = t.oid AND a.attnum = d.refobjsubid
|
||||
WHERE
|
||||
c.relkind = 'S'
|
||||
AND d.deptype = 'a'
|
||||
LOOP
|
||||
EXECUTE format(
|
||||
'SELECT setval(%L, COALESCE((SELECT MAX(%I) FROM %I), 1), true)',
|
||||
r.seq_name, r.column_name, r.table_name
|
||||
);
|
||||
END LOOP;
|
||||
END$$;
|
||||
|
@@ -1,17 +1,22 @@
|
||||
import sys
|
||||
import os
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../")))
|
||||
|
||||
from sqlalchemy import create_engine, MetaData
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from config import Config
|
||||
from dotenv import load_dotenv
|
||||
load_dotenv()
|
||||
|
||||
# Źródło: SQLite
|
||||
sqlite_engine = create_engine("sqlite:///instance/shopping.db")
|
||||
sqlite_meta = MetaData(bind=sqlite_engine)
|
||||
sqlite_meta.reflect()
|
||||
sqlite_meta = MetaData()
|
||||
sqlite_meta.reflect(bind=sqlite_engine)
|
||||
|
||||
# Cel: PostgreSQL (czytany z .env przez Config)
|
||||
# Cel: PostgreSQL
|
||||
pg_engine = create_engine(Config.SQLALCHEMY_DATABASE_URI)
|
||||
pg_meta = MetaData(bind=pg_engine)
|
||||
pg_meta.reflect()
|
||||
pg_meta = MetaData()
|
||||
pg_meta.reflect(bind=pg_engine)
|
||||
|
||||
# Sesje
|
||||
SQLiteSession = sessionmaker(bind=sqlite_engine)
|
||||
@@ -21,29 +26,36 @@ sqlite_session = SQLiteSession()
|
||||
pg_session = PGSession()
|
||||
|
||||
def migrate_table(table_name):
|
||||
print(f"Migruję tabelę: {table_name}")
|
||||
print("➡️ Używana baza docelowa:", Config.SQLALCHEMY_DATABASE_URI)
|
||||
print(f"\n➡️ Migruję tabelę: {table_name}")
|
||||
source_table = sqlite_meta.tables.get(table_name)
|
||||
target_table = pg_meta.tables.get(table_name)
|
||||
|
||||
if not source_table or not target_table:
|
||||
print(f"Pominięto: {table_name} (brak w jednej z baz)")
|
||||
if source_table is None or target_table is None:
|
||||
print(f"⚠️ Pominięto: {table_name} (brak w jednej z baz)")
|
||||
return
|
||||
|
||||
rows = sqlite_session.execute(source_table.select()).fetchall()
|
||||
if not rows:
|
||||
print("Brak danych do migracji.")
|
||||
print("ℹ️ Brak danych do migracji.")
|
||||
return
|
||||
|
||||
insert_data = [dict(row._mapping) for row in rows]
|
||||
with pg_engine.begin() as conn:
|
||||
conn.execute(target_table.insert(), insert_data)
|
||||
|
||||
print(f"✅ Przeniesiono: {len(rows)} rekordów")
|
||||
try:
|
||||
with pg_engine.begin() as conn:
|
||||
conn.execute(target_table.delete())
|
||||
conn.execute(target_table.insert(), insert_data)
|
||||
print(f"✅ Przeniesiono: {len(rows)} rekordów")
|
||||
except Exception as e:
|
||||
print(f"❌ Błąd przy migracji {table_name}: {e}")
|
||||
|
||||
|
||||
def main():
|
||||
tables = ["user", "shopping_list", "item", "expense", "receipt", "suggested_product"]
|
||||
for table in tables:
|
||||
migrate_table(table)
|
||||
print("\n🎉 Migracja zakończona pomyślnie.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
@@ -1,14 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
APP_CONTAINER=web
|
||||
DB_ENGINE=${1:-pgsql} # domyślnie pgsql
|
||||
|
||||
echo "Migracja z SQLite do $DB_ENGINE..."
|
||||
|
||||
docker-compose run --rm $APP_CONTAINER bash -c "
|
||||
export FLASK_APP=app.py
|
||||
flask db upgrade
|
||||
"
|
||||
|
||||
echo "Migracja zakończona."
|
12
app.py
12
app.py
@@ -103,7 +103,7 @@ def utcnow():
|
||||
class User(UserMixin, db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
username = db.Column(db.String(150), unique=True, nullable=False)
|
||||
password_hash = db.Column(db.String(150), nullable=False)
|
||||
password_hash = db.Column(db.String(512), nullable=False)
|
||||
is_admin = db.Column(db.Boolean, default=False)
|
||||
|
||||
|
||||
@@ -162,6 +162,7 @@ class Receipt(db.Model):
|
||||
file_hash = db.Column(db.String(64), nullable=True, unique=True)
|
||||
|
||||
|
||||
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
|
||||
@@ -1088,9 +1089,11 @@ def all_products():
|
||||
top_products_query = top_products_query.filter(
|
||||
SuggestedProduct.name.ilike(f"%{query}%")
|
||||
)
|
||||
|
||||
top_products = (
|
||||
top_products_query.order_by(
|
||||
SuggestedProduct.usage_count.desc(), SuggestedProduct.name.asc()
|
||||
SuggestedProduct.name.asc(), # musi być pierwsze
|
||||
SuggestedProduct.usage_count.desc()
|
||||
)
|
||||
.distinct(SuggestedProduct.name)
|
||||
.limit(20)
|
||||
@@ -1125,8 +1128,9 @@ def all_products():
|
||||
def upload_receipt(list_id):
|
||||
|
||||
l = db.session.get(ShoppingList, list_id)
|
||||
if l is None or l.owner_id != current_user.id:
|
||||
return _receipt_error("Nie masz uprawnień do tej listy.")
|
||||
|
||||
#if l is None or l.owner_id != current_user.id:
|
||||
# return _receipt_error("Nie masz uprawnień do tej listy.")
|
||||
|
||||
if "receipt" not in request.files:
|
||||
return _receipt_error("Brak pliku")
|
||||
|
@@ -1,12 +1,9 @@
|
||||
import os
|
||||
|
||||
|
||||
class Config:
|
||||
SECRET_KEY = os.environ.get("SECRET_KEY", "D8pceNZ8q%YR7^7F&9wAC2")
|
||||
|
||||
|
||||
DB_ENGINE = os.environ.get("DB_ENGINE", "sqlite").lower()
|
||||
|
||||
if DB_ENGINE == "sqlite":
|
||||
SQLALCHEMY_DATABASE_URI = "sqlite:///instance/shopping.db"
|
||||
elif DB_ENGINE == "pgsql":
|
||||
@@ -18,9 +15,6 @@ class Config:
|
||||
else:
|
||||
raise ValueError("Nieobsługiwany typ bazy danych.")
|
||||
|
||||
|
||||
|
||||
|
||||
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||
SYSTEM_PASSWORD = os.environ.get("SYSTEM_PASSWORD", "admin")
|
||||
DEFAULT_ADMIN_USERNAME = os.environ.get("DEFAULT_ADMIN_USERNAME", "admin")
|
||||
|
@@ -1,15 +1,28 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
PROFILE=$1
|
||||
|
||||
if [[ -z "$PROFILE" ]]; then
|
||||
echo "Użycie: $0 {pgsql|mysql|firebird|sqlite}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Zatrzymuję i usuwam stare kontenery..."
|
||||
docker compose down --rmi all
|
||||
if [[ "$PROFILE" == "sqlite" ]]; then
|
||||
docker compose down --rmi all
|
||||
else
|
||||
docker compose --profile "$PROFILE" down --rmi all
|
||||
fi
|
||||
|
||||
echo "Pobieram najnowszy kod z repozytorium..."
|
||||
git pull
|
||||
|
||||
echo "Buduję obrazy i uruchamiam kontenery..."
|
||||
#docker compose up -d --build
|
||||
DB_ENGINE=pgsql docker compose --profile pgsql up -d --build
|
||||
|
||||
if [[ "$PROFILE" == "sqlite" ]]; then
|
||||
docker compose up -d --build
|
||||
else
|
||||
DB_ENGINE="$PROFILE" docker compose --profile "$PROFILE" up -d --build
|
||||
fi
|
||||
|
||||
echo "Gotowe!"
|
||||
|
@@ -17,8 +17,6 @@ services:
|
||||
- ./uploads:/app/uploads
|
||||
- ./instance:/app/instance
|
||||
restart: unless-stopped
|
||||
# depends_on:
|
||||
# - ${DB_ENGINE:-sqlite}
|
||||
|
||||
pgsql:
|
||||
image: postgres:17
|
||||
@@ -32,6 +30,7 @@ services:
|
||||
ports:
|
||||
- "5432:5432"
|
||||
restart: unless-stopped
|
||||
hostname: db
|
||||
profiles: ["pgsql"]
|
||||
|
||||
mysql:
|
||||
@@ -47,6 +46,7 @@ services:
|
||||
ports:
|
||||
- "3306:3306"
|
||||
restart: unless-stopped
|
||||
hostname: db
|
||||
profiles: ["mysql"]
|
||||
|
||||
firebird:
|
||||
@@ -59,4 +59,5 @@ services:
|
||||
ports:
|
||||
- "3050:3050"
|
||||
restart: unless-stopped
|
||||
hostname: db
|
||||
profiles: ["firebird"]
|
||||
|
@@ -11,3 +11,4 @@ pillow-heif
|
||||
|
||||
pytesseract
|
||||
opencv-python-headless
|
||||
psycopg2-binary
|
||||
|
Reference in New Issue
Block a user