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