Как стать автором
Обновить

backup Django

Уровень сложностиПростой
Время на прочтение4 мин
Количество просмотров1K

Резервное копирование Django

Короче, начал делать проект на Django с нуля, и вообще впервые с ним работаю (noob). Соответственно с нейронками в паре, так как сам в Django не особо шарю, и они делают почти всё за меня (условно). И поскольку нейросети любят переписывать код по-своему, периодически всё ломается: и код, и база, и остальная разная нечисть🌚

*Примечание: обработка для db.sqlite3 (что стандарт для Dj, и бывает многое), но с лёгкостью можете изменить под другую бд

В итоге решил делать бэкапы вместе с нейронками — может, кому-то пригодится.

Да, я в курсе, что есть django-dbbackup, контроль версий и куча других тем, но мне пока так удобнее. Дальше — больше. Делюсь этим скорее для таких же новичков, как я. Вдруг будет полезно⤵️:

Что делаем

  1. Легко и изящно пишем названием при сохранение

  2. Если всё пошло не тем путём куда хотели прийти, восстанавливаем всё обратно в 1 секунду

import os
import shutil
import sqlite3
import datetime
import zipfile
import argparse


# === Конфигурация для бэкапа ===

# Папка, в которую будут сохраняться резервные копии
BACKUP_DIR = "backups"

# Папки, которые нужно исключить из бэкапа
# Обычно сюда попадают временные, кэшированные или неважные данные
EXCLUDE_DIRS = ['__pycache__', '.git', 'venv']  # __pycache__ — кэш Python, .git — история гита, venv — виртуальное окружение

# Отдельные файлы, которые обязательно нужно включить в бэкап
# Например, база данных SQLite и список зависимостей
INCLUDE_FILES = ['db.sqlite3', 'requirements.txt']

# Папки проекта, которые нужно включить в бэкап
# Указываем те директории (папки noob), в которых находится основной код
INCLUDE_DIRS = ['fish1', 'fish2']


def create_backup():
    """Создание резервной копии проекта с возможностью задать имя"""
    user_input = input("Введите название резервной копии (оставьте пустым для имени по умолчанию): ").strip()
    custom_name = user_input.strip("()") if user_input else None

    timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
    base_name = custom_name if custom_name else f"backup"
    backup_name = f"{base_name}_{timestamp}"
    backup_path = os.path.join(BACKUP_DIR, backup_name)
    os.makedirs(backup_path, exist_ok=True)

    print(f"Создание резервной копии в {backup_path}...")

    # Копируем файлы
    for item in INCLUDE_FILES:
        if os.path.exists(item):
            shutil.copy2(item, backup_path)

    # Копируем папки
    for dir_name in INCLUDE_DIRS:
        if os.path.exists(dir_name):
            shutil.copytree(
                dir_name,
                os.path.join(backup_path, dir_name),
                ignore=shutil.ignore_patterns(*EXCLUDE_DIRS)
            )

    # Дамп SQLite
    if os.path.exists('db.sqlite3'):
        db_backup_path = os.path.join(backup_path, 'db_dump.sql')
        with sqlite3.connect('db.sqlite3') as conn:
            with open(db_backup_path, 'w', encoding='utf-8') as f:
                for line in conn.iterdump():
                    f.write(f'{line}\n')

    # ZIP архив
    zip_path = f"{backup_path}.zip"
    with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
        for root, dirs, files in os.walk(backup_path):
            for file in files:
                file_path = os.path.join(root, file)
                arcname = os.path.relpath(file_path, os.path.dirname(backup_path))
                zipf.write(file_path, arcname)

    shutil.rmtree(backup_path)
    print(f"✔ Резервная копия создана: {zip_path}")


def restore_backup(backup_file):
    """Восстановление проекта из резервной копии"""
    if not os.path.exists(backup_file):
        print(f"Файл {backup_file} не найден!")
        return

    print(f"Восстановление из {backup_file}...")

    # Удаляем текущие папки
    for dir_name in INCLUDE_DIRS:
        if os.path.exists(dir_name):
            shutil.rmtree(dir_name)

    # Распаковываем архив
    temp_dir = "temp_restore"
    with zipfile.ZipFile(backup_file, 'r') as zip_ref:
        zip_ref.extractall(temp_dir)

    # Определяем путь к данным
    backup_name = os.path.splitext(os.path.basename(backup_file))[0]
    source_path = os.path.join(temp_dir, backup_name)

    # Копируем файлы обратно
    for item in os.listdir(source_path):
        src = os.path.join(source_path, item)
        dst = os.path.join('.', item)
        if os.path.isdir(src):
            shutil.copytree(src, dst)
        else:
            shutil.copy2(src, dst)

    # Восстанавливаем базу данных
    db_dump = os.path.join(source_path, 'db_dump.sql')
    if os.path.exists(db_dump):
        if os.path.exists('db.sqlite3'):
            os.remove('db.sqlite3')
        with sqlite3.connect('db.sqlite3') as conn:
            with open(db_dump, 'r', encoding='utf-8') as f:
                sql = f.read()
            conn.executescript(sql)

    shutil.rmtree(temp_dir)
    print("✔ Восстановление завершено!")


def list_backups():
    """Список доступных резервных копий"""
    if not os.path.exists(BACKUP_DIR):
        print("Папка с резервными копиями пуста!")
        return

    backups = sorted(
        [f for f in os.listdir(BACKUP_DIR) if f.endswith('.zip')],
        reverse=True
    )

    if not backups:
        print("Резервные копии не найдены!")
        return

    print("📦 Доступные резервные копии:")
    for i, backup in enumerate(backups, 1):
        print(f"{i}. {backup}")


def main():
    parser = argparse.ArgumentParser(description="Резервное копирование Django-проекта")
    subparsers = parser.add_subparsers(dest='command')

    subparsers.add_parser('backup', help='Создать резервную копию')

    restore_parser = subparsers.add_parser('restore', help='Восстановить из резервной копии')
    restore_parser.add_argument('backup', nargs='?', help='Имя zip-файла резервной копии')

    subparsers.add_parser('list', help='Показать список резервных копий')

    args = parser.parse_args()

    os.makedirs(BACKUP_DIR, exist_ok=True)

    if args.command == 'backup':
        create_backup()
    elif args.command == 'restore':
        if args.backup:
            restore_backup(os.path.join(BACKUP_DIR, args.backup))
        else:
            list_backups()
            choice = input("Введите номер резервной копии для восстановления: ")
            try:
                backups = sorted(
                    [f for f in os.listdir(BACKUP_DIR) if f.endswith('.zip')],
                    reverse=True
                )
                selected = backups[int(choice) - 1]
                restore_backup(os.path.join(BACKUP_DIR, selected))
            except (IndexError, ValueError):
                print("Неверный выбор!")
    elif args.command == 'list':
        list_backups()
    else:
        parser.print_help()


if __name__ == "__main__":
    main()


"""
Создать резервную копию:
python backup_restore.py backup

Показать список бэкапов:
python backup_restore.py list

Восстановить проект (по номеру или имени):
python backup_restore.py restore backup_20240515_143022.zip  
python backup_restore.py restore backup_20250417_212905.zip
python backup_restore.py restore backup_20250417_212905.zip

или
python backup_restore.py restore
# Затем выберите номер из списка

"""

Вкратце и в грации❤️‍🔥, делаем файл рядом с manage.py (например с названием «backup_restore.py»), далее:

1. Сделали топ рабочую версию: python backup_restore.py backup (название файла по примеру, называйте файл любым другим словом, как хотите, и backup делаем по названию файла)
2. Сделали после топ версии — хавно (где всё ломается), откат в секунду: python backup_restore.py restore
2.1. Работаем дальше🎯

Мой канал: https://t.me/jollyPython
Maximum free skill Python (глубина Python, до которой не дотянется ни одна система): https://t.me/pythonNb

Теги:
Хабы:
-4
Комментарии10

Публикации

Работа

Data Scientist
42 вакансии

Ближайшие события