Files
dj52/backup/views.py
Beyhan Oğur ec28a2024d first commit
2026-04-26 22:22:29 +03:00

329 lines
14 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import os
from datetime import datetime
from django.conf import settings
from django.utils import timezone
from .models import DatabaseBackup
try:
import psycopg2
from psycopg2 import sql
PSYCOPG2_AVAILABLE = True
except ImportError:
PSYCOPG2_AVAILABLE = False
class BackupManager:
"""PostgreSQL veritabanı yedekleme işlemlerini yönetir - Sadece psycopg2 kullanarak"""
def __init__(self):
self.backup_dir = os.path.join(settings.BASE_DIR, 'backups')
if not os.path.exists(self.backup_dir):
os.makedirs(self.backup_dir)
def get_db_config(self):
"""Veritabanı yapılandırmasını alır"""
db_config = settings.DATABASES['default']
return {
'dbname': db_config.get('NAME'),
'user': db_config.get('USER'),
'password': db_config.get('PASSWORD'),
'host': db_config.get('HOST', 'localhost'),
'port': db_config.get('PORT', '5432'),
}
def get_connection(self):
"""PostgreSQL bağlantısı oluşturur"""
if not PSYCOPG2_AVAILABLE:
raise Exception("psycopg2 kütüphanesi yüklü değil")
db_config = self.get_db_config()
return psycopg2.connect(
dbname=db_config['dbname'],
user=db_config['user'],
password=db_config['password'],
host=db_config['host'],
port=db_config['port']
)
#@task
def create_backup(self, backup_obj):
"""
PostgreSQL veritabanının yedeğini oluşturur
Sadece psycopg2 kullanarak SQL dump oluşturur
"""
try:
backup_obj.status = 'in_progress'
backup_obj.save()
db_config = self.get_db_config()
# Yedek dosyası adını oluştur
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
backup_filename = f"backup_{db_config['dbname']}_{timestamp}.sql"
backup_path = os.path.join(self.backup_dir, backup_filename)
# Veritabanına bağlan
conn = self.get_connection()
cursor = conn.cursor()
with open(backup_path, 'w', encoding='utf-8') as f:
# Header
f.write("-- PostgreSQL Database Backup\n")
f.write(f"-- Database: {db_config['dbname']}\n")
f.write(f"-- Date: {datetime.now()}\n")
f.write("-- Created by Django Backup System using psycopg2\n\n")
f.write("SET client_encoding = 'UTF8';\n")
f.write("SET standard_conforming_strings = on;\n")
f.write("SET check_function_bodies = false;\n")
f.write("SET client_min_messages = warning;\n\n")
# Tüm tabloları al
cursor.execute("""
SELECT tablename FROM pg_tables
WHERE schemaname = 'public'
ORDER BY tablename;
""")
tables = cursor.fetchall()
for (table_name,) in tables:
f.write(f"\n-- Table: {table_name}\n")
# Tablo yapısını al - kolon bilgilerini çek
cursor.execute("""
SELECT
column_name,
data_type,
character_maximum_length,
is_nullable,
column_default,
is_identity
FROM information_schema.columns
WHERE table_schema = 'public' AND table_name = %s
ORDER BY ordinal_position;
""", [table_name])
columns_info = cursor.fetchall()
if columns_info:
f.write(f"DROP TABLE IF EXISTS \"{table_name}\" CASCADE;\n")
f.write(f"CREATE TABLE \"{table_name}\" (\n")
col_defs = []
for col_name, data_type, max_length, is_nullable, col_default, is_identity in columns_info:
col_def = f" \"{col_name}\" "
# Serial kontrolü (Nextval veya Identity)
is_serial = False
if (col_default and 'nextval' in col_default) or is_identity == 'YES':
if data_type == 'integer':
col_def += "SERIAL"
is_serial = True
elif data_type == 'bigint':
col_def += "BIGSERIAL"
is_serial = True
if not is_serial:
# Veri tipini ekle
if max_length and data_type == 'character varying':
col_def += f"VARCHAR({max_length})"
elif max_length and data_type == 'character':
col_def += f"CHAR({max_length})"
else:
col_def += data_type.upper()
# NOT NULL
if is_nullable == 'NO':
col_def += " NOT NULL"
# DEFAULT değer
if col_default:
col_def += f" DEFAULT {col_default}"
col_defs.append(col_def)
f.write(",\n".join(col_defs))
f.write("\n);\n\n")
# Veriyi al ve INSERT komutları oluştur
# Kolon isimlerini al
cursor.execute(sql.SQL("""
SELECT column_name
FROM information_schema.columns
WHERE table_name = %s
ORDER BY ordinal_position;
"""), [table_name])
columns = [row[0] for row in cursor.fetchall()]
if not columns:
continue
cursor.execute(sql.SQL("SELECT * FROM {}").format(sql.Identifier(table_name)))
rows = cursor.fetchall()
if rows:
f.write(f"-- Data for table: {table_name}\n")
# INSERT şablonu hazırla
cols_str = ', '.join([f'"{c}"' for c in columns]) # Identifier quoting
placeholders = ', '.join(['%s'] * len(columns))
insert_template = f"INSERT INTO \"{table_name}\" ({cols_str}) VALUES ({placeholders})"
for row in rows:
# mogrify kullanarak güvenli SQL oluştur
try:
# mogrify bytes döndürür, decode etmemiz lazım
safe_sql = cursor.mogrify(insert_template, row).decode('utf-8')
f.write(f"{safe_sql};\n")
except Exception as row_err:
print(f"Row error in {table_name}: {row_err}")
continue
f.write("\n")
# Sequence'leri sıfırla
f.write("\n-- Reset sequences\n")
cursor.execute("""
SELECT
c.relname as sequence_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 t.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'public');
""")
sequences = cursor.fetchall()
for seq_name, tbl_name, col_name in sequences:
f.write(f"SELECT setval('{seq_name}', (SELECT COALESCE(MAX(\"{col_name}\"), 1) FROM \"{tbl_name}\"));\n")
cursor.close()
conn.close()
# Başarılı
file_size = os.path.getsize(backup_path)
backup_obj.file_path = backup_path
backup_obj.file_size = file_size
backup_obj.status = 'completed'
backup_obj.completed_at = timezone.now()
backup_obj.save()
return True, f"Yedekleme başarıyla tamamlandı: {backup_filename}"
except Exception as e:
backup_obj.status = 'failed'
backup_obj.error_message = str(e)
backup_obj.save()
return False, f"Yedekleme hatası: {str(e)}"
def restore_backup(self, backup_path):
"""
TAMAMEN OTOMATIK FULL RESTORE
Manuel işlem gerektirmez!
"""
try:
if not os.path.exists(backup_path):
return False, "Yedek dosyası bulunamadı"
with open(backup_path, 'r', encoding='utf-8') as f:
sql_content = f.read()
# HOTFIX 1: 'order' gibi keywordlerin tırnak içine alınmaması sorununu düzelt
import re
sql_content = re.sub(r'(\s+)order(\s+[A-Z]+)', r'\1"order"\2', sql_content)
# HOTFIX 2: SERIAL/Sequence düzeltmesi
# "id INTEGER NOT NULL DEFAULT nextval(...)" -> "id SERIAL"
sql_content = re.sub(r'INTEGER\s+NOT\s+NULL\s+DEFAULT\s+nextval\(\'[^\']+\'(:?::regclass)?\)', 'SERIAL', sql_content)
sql_content = re.sub(r'BIGINT\s+NOT\s+NULL\s+DEFAULT\s+nextval\(\'[^\']+\'(:?::regclass)?\)', 'BIGSERIAL', sql_content)
# HOTFIX 3: "id" kolonları INTEGER/BIGINT NOT NULL ise (ve default yoksa) SERIAL yap
# Bu durum Identity kolonlarının yanlış yedeklenmesi sonucu oluşur
sql_content = re.sub(r'"id"\s+INTEGER\s+NOT\s+NULL(?!(\s+DEFAULT))', '"id" SERIAL', sql_content)
sql_content = re.sub(r'"id"\s+BIGINT\s+NOT\s+NULL(?!(\s+DEFAULT))', '"id" BIGSERIAL', sql_content)
# HOTFIX 4: setval satırlarını kaldır (çünkü biz kendimiz yeniden ayarlıyoruz ve isimler değişmiş olabilir)
# Lines starting with SELECT setval...
sql_lines = []
for line in sql_content.split('\n'):
if 'SELECT setval' in line and 'django_migrations' in line or 'SELECT setval' in line:
continue # Skip setvals from file
sql_lines.append(line)
sql_content = '\n'.join(sql_lines)
conn = self.get_connection()
conn.autocommit = True
cursor = conn.cursor()
try:
print("=" * 60)
print("TAM OTOMATIK RESTORE (YENI VERSIYON)")
print("=" * 60)
# 1. DROP tüm tablolar
print("\n1. Temizleniyor...")
cursor.execute("SELECT tablename FROM pg_tables WHERE schemaname = 'public';")
tables = cursor.fetchall()
for (t,) in tables:
print(f" Dropping {t}...")
cursor.execute(f'DROP TABLE IF EXISTS "{t}" CASCADE;')
print(" ✓ Temizlendi")
# 2. SQL Execution - Tek seferde çalıştır
print("\n2. SQL Dosyası Çalıştırılıyor...")
# execute() methodu çoklu sorguları çalıştırabilir (psycopg2 özelliği)
try:
cursor.execute(sql_content)
print(" ✓ SQL Script çalıştırıldı")
except Exception as sql_err:
print(f" SQL HATA: {sql_err}")
raise sql_err
print(" ✓ SQL Script çalıştırıldı")
# 3. Sequence'ler (SQL script içinde genelde vardır ama garanti olsun)
print("\n3. Sequence'ler Kontrol Ediliyor...")
cursor.execute("""
SELECT c.relname, t.relname, a.attname
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 t.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'public');
""")
for seq, tbl, col in cursor.fetchall():
try:
cursor.execute(f"SELECT setval('{seq}', COALESCE((SELECT MAX({col}) FROM {tbl}), 1));")
except:
pass
print(" ✓ Ayarlandı")
cursor.close()
conn.close()
print("\n" + "=" * 60)
print("RESTORE TAMAMLANDI!")
print("=" * 60)
return True, "Restore başarıyla tamamlandı!"
except Exception as e:
print(f"\nHATA: {e}")
cursor.close()
conn.close()
raise
except Exception as e:
return False, f"Geri yükleme hatası: {str(e)}"
def delete_backup_file(self, backup_path):
"""Yedek dosyasını fiziksel olarak siler"""
try:
if os.path.exists(backup_path):
os.remove(backup_path)
return True, "Yedek dosyası silindi"
else:
return False, "Yedek dosyası bulunamadı"
except Exception as e:
return False, f"Dosya silme hatası: {str(e)}"