from django.contrib import admin
from django.utils.html import format_html
from django.contrib import messages
from django.utils import timezone
from django.http import FileResponse, HttpResponse
from django.shortcuts import get_object_or_404, render, redirect
from django.urls import path, reverse
from django.conf import settings
import os
from datetime import datetime
from .models import DatabaseBackup
from .views import BackupManager
@admin.register(DatabaseBackup)
class DatabaseBackupAdmin(admin.ModelAdmin):
list_display = ['name', 'status_badge', 'backup_type', 'file_size_display', 'download_link', 'created_by', 'created_at', 'completed_at']
list_filter = ['status', 'backup_type', 'created_at']
search_fields = ['name', 'notes', 'error_message']
readonly_fields = ['file_path', 'file_size', 'status', 'created_by', 'created_at', 'completed_at', 'error_message', 'file_size_display_field']
fieldsets = (
('Temel Bilgiler', {
'fields': ('name', 'backup_type', 'status', 'notes')
}),
('Yedek Dosya Bilgileri', {
'fields': ('file_path', 'file_size_display_field')
}),
('Zaman Bilgileri', {
'fields': ('created_by', 'created_at', 'completed_at')
}),
('Hata Bilgileri', {
'fields': ('error_message',),
'classes': ('collapse',)
}),
)
actions = ['create_new_backup', 'restore_selected_backup', 'download_backup', 'delete_backup_files']
def status_badge(self, obj):
"""Durum için renkli badge gösterir"""
colors = {
'pending': '#FFA500',
'in_progress': '#2196F3',
'completed': '#4CAF50',
'failed': '#F44336',
}
color = colors.get(obj.status, '#999')
return format_html(
'{}',
color,
obj.get_status_display()
)
status_badge.short_description = 'Durum'
def file_size_display(self, obj):
"""Dosya boyutunu gösterir"""
return obj.get_file_size_display()
file_size_display.short_description = 'Dosya Boyutu'
def file_size_display_field(self, obj):
"""Read-only field için dosya boyutu"""
return obj.get_file_size_display()
file_size_display_field.short_description = 'Dosya Boyutu'
def download_link(self, obj):
"""İndir butonu gösterir"""
if obj.file_path and obj.status == 'completed' and os.path.isfile(obj.file_path):
url = f'/admin/backup/databasebackup/{obj.pk}/download/'
return format_html(
'📥 İndir',
url
)
return format_html('-', '#999')
download_link.short_description = 'İndir'
def get_urls(self):
"""Admin için özel URL'ler ekler"""
urls = super().get_urls()
custom_urls = [
path('create-backup/', self.admin_site.admin_view(self.create_backup_view), name='backup_create'),
path('upload-backup/', self.admin_site.admin_view(self.upload_backup_view), name='backup_upload'),
path('/download/', self.admin_site.admin_view(self.download_backup_file), name='backup_download'),
]
return custom_urls + urls
def changelist_view(self, request, extra_context=None):
"""Change list view'a ekstra context ekler"""
extra_context = extra_context or {}
extra_context['show_create_backup_button'] = True
extra_context['show_upload_backup_button'] = True
return super().changelist_view(request, extra_context=extra_context)
def create_backup_view(self, request):
"""Yeni yedek oluşturma view'i"""
from django.shortcuts import redirect
from django.urls import reverse
# Yeni bir backup objesi oluştur
timestamp = timezone.now().strftime('%Y%m%d_%H%M%S')
backup = DatabaseBackup.objects.create(
name=f"Manuel Yedek - {timezone.now().strftime('%Y-%m-%d %H:%M:%S')}",
backup_type='manual',
created_by=request.user,
status='pending'
)
# Yedekleme işlemini başlat
manager = BackupManager()
success, message = manager.create_backup(backup)
if success:
self.message_user(request, message, messages.SUCCESS)
else:
self.message_user(request, message, messages.ERROR)
# Liste sayfasına yönlendir
return redirect(reverse('admin:backup_databasebackup_changelist'))
def upload_backup_view(self, request):
"""Yedek dosyası yükleme view'i"""
if request.method == 'POST':
uploaded_file = request.FILES.get('backup_file')
backup_name = request.POST.get('backup_name', '')
if not uploaded_file:
self.message_user(request, "Lütfen bir dosya seçin", messages.ERROR)
return redirect(reverse('admin:backup_upload'))
# Dosya uzantısı kontrolü
if not uploaded_file.name.endswith('.sql'):
self.message_user(request, "Sadece .sql uzantılı dosyalar yüklenebilir", messages.ERROR)
return redirect(reverse('admin:backup_upload'))
# Dosya boyutu kontrolü (max 500MB)
max_size = 500 * 1024 * 1024 # 500MB in bytes
if uploaded_file.size > max_size:
self.message_user(request, "Dosya çok büyük. Maksimum 500MB olabilir", messages.ERROR)
return redirect(reverse('admin:backup_upload'))
try:
# Backup klasörünü kontrol et
manager = BackupManager()
backup_dir = manager.backup_dir
# Dosya adını oluştur (timestamp ekle)
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
original_name = os.path.splitext(uploaded_file.name)[0]
filename = f"uploaded_{original_name}_{timestamp}.sql"
file_path = os.path.join(backup_dir, filename)
# Dosyayı kaydet
with open(file_path, 'wb+') as destination:
for chunk in uploaded_file.chunks():
destination.write(chunk)
# Veritabanı kaydı oluştur
if not backup_name:
backup_name = f"Yüklenen Yedek - {uploaded_file.name}"
backup = DatabaseBackup.objects.create(
name=backup_name,
file_path=file_path,
file_size=uploaded_file.size,
status='completed',
backup_type='manual',
created_by=request.user,
completed_at=timezone.now(),
notes=f"Dosya yüklendi: {uploaded_file.name}"
)
self.message_user(
request,
f"Yedek dosyası başarıyla yüklendi: {uploaded_file.name} ({backup.get_file_size_display()})",
messages.SUCCESS
)
return redirect(reverse('admin:backup_databasebackup_changelist'))
except Exception as e:
self.message_user(request, f"Dosya yüklenirken hata oluştu: {str(e)}", messages.ERROR)
return redirect(reverse('admin:backup_upload'))
# GET request - form göster
context = {
**self.admin_site.each_context(request),
'title': 'Yedek Dosyası Yükle',
'opts': self.model._meta,
'has_view_permission': self.has_view_permission(request),
}
return render(request, 'admin/backup/upload_backup.html', context)
def download_backup_file(self, request, backup_id):
"""Yedek dosyasını indirir"""
backup = get_object_or_404(DatabaseBackup, pk=backup_id)
if not backup.file_path:
self.message_user(request, "Yedek dosyası bulunamadı", messages.ERROR)
return HttpResponse("Dosya bulunamadı", status=404)
if not os.path.isfile(backup.file_path):
self.message_user(request, "Yedek dosyası disk üzerinde bulunamadı", messages.ERROR)
return HttpResponse("Dosya disk üzerinde bulunamadı", status=404)
# Dosyayı indir
try:
response = FileResponse(open(backup.file_path, 'rb'), content_type='application/sql')
filename = os.path.basename(backup.file_path)
response['Content-Disposition'] = f'attachment; filename="{filename}"'
return response
except Exception as e:
return HttpResponse(f"Dosya indirilemedi: {str(e)}", status=500)
def save_model(self, request, obj, form, change):
"""Model kaydedilirken created_by alanını otomatik doldur"""
if not change: # Yeni kayıt
obj.created_by = request.user
super().save_model(request, obj, form, change)
def create_new_backup(self, request, queryset):
"""Yeni bir yedek oluşturur"""
# Yeni bir backup objesi oluştur
timestamp = timezone.now().strftime('%Y-%m-%d %H:%M:%S')
backup = DatabaseBackup.objects.create(
name=f"Manuel Yedek - {timestamp}",
backup_type='manual',
created_by=request.user,
status='pending'
)
# Yedekleme işlemini başlat
manager = BackupManager()
success, message = manager.create_backup(backup)
if success:
self.message_user(request, message, messages.SUCCESS)
else:
self.message_user(request, message, messages.ERROR)
create_new_backup.short_description = "Yeni Yedek Oluştur"
def restore_selected_backup(self, request, queryset):
"""Seçili yedeği geri yükler"""
if queryset.count() != 1:
self.message_user(
request,
"Lütfen geri yüklemek için sadece bir yedek seçin",
messages.WARNING
)
return
backup = queryset.first()
if backup.status != 'completed':
self.message_user(
request,
"Sadece tamamlanmış yedekler geri yüklenebilir",
messages.WARNING
)
return
if not backup.file_path:
self.message_user(
request,
"Yedek dosya yolu bulunamadı",
messages.ERROR
)
return
# Restore işlemi (migration'lar da dahil)
manager = BackupManager()
success, message = manager.restore_backup(backup.file_path)
if success:
# Otomatik migration çalıştır
try:
from django.core.management import call_command
import io
call_command('migrate', '--noinput', stdout=io.StringIO(), stderr=io.StringIO())
self.message_user(request, f"{message} Migration'lar uygulandı. Sayfayı yenileyin.", messages.SUCCESS)
except:
self.message_user(request, f"{message} Sayfayı yenileyin.", messages.SUCCESS)
else:
self.message_user(request, message, messages.ERROR)
restore_selected_backup.short_description = "Seçili Yedeği Geri Yükle"
def download_backup(self, request, queryset):
"""Seçili yedeği indirir"""
if queryset.count() != 1:
self.message_user(
request,
"Lütfen indirmek için sadece bir yedek seçin",
messages.WARNING
)
return
backup = queryset.first()
if backup.status != 'completed':
self.message_user(
request,
"Sadece tamamlanmış yedekler indirilebilir",
messages.WARNING
)
return
if not backup.file_path or not os.path.isfile(backup.file_path):
self.message_user(
request,
"Yedek dosyası bulunamadı",
messages.ERROR
)
return
# Dosyayı indir
try:
response = FileResponse(open(backup.file_path, 'rb'), content_type='application/sql')
filename = os.path.basename(backup.file_path)
response['Content-Disposition'] = f'attachment; filename="{filename}"'
return response
except Exception as e:
self.message_user(
request,
f"Dosya indirilemedi: {str(e)}",
messages.ERROR
)
return
download_backup.short_description = "Seçili Yedeği İndir"
def delete_backup_files(self, request, queryset):
"""Seçili yedeklerin dosyalarını siler"""
deleted_count = 0
error_count = 0
manager = BackupManager()
for backup in queryset:
if backup.file_path:
success, message = manager.delete_backup_file(backup.file_path)
if success:
backup.file_path = None
backup.file_size = None
backup.save()
deleted_count += 1
else:
error_count += 1
if deleted_count > 0:
self.message_user(
request,
f"{deleted_count} yedek dosyası silindi",
messages.SUCCESS
)
if error_count > 0:
self.message_user(
request,
f"{error_count} yedek dosyası silinemedi",
messages.WARNING
)
delete_backup_files.short_description = "Yedek Dosyalarını Sil"
def has_delete_permission(self, request, obj=None):
"""Silme iznini kontrol et - Tüm admin kullanıcıları silebilir"""
return request.user.is_staff