from django.db import models import logging from .ssh_client import SSHManager from django.core.validators import URLValidator from django.core.exceptions import ValidationError from .system_settings import SystemSettings logger = logging.getLogger(__name__) def validate_domain(value): """Domain formatını kontrol et""" if ' ' in value: # Boşluk kontrolü raise ValidationError('Domain adında boşluk olamaz') if not any(char.isalpha() for char in value): # En az bir harf kontrolü raise ValidationError('Domain adında en az bir harf olmalıdır') if not all(c.isalnum() or c in '-.' for c in value): # Geçerli karakter kontrolü raise ValidationError('Domain adında sadece harf, rakam, tire (-) ve nokta (.) olabilir') class Customer(models.Model): CUSTOMER_TYPES = ( ('individual', 'Bireysel'), ('corporate', 'Kurumsal'), ) customer_type = models.CharField(max_length=20, choices=CUSTOMER_TYPES, verbose_name='Müşteri Tipi') # Ortak alanlar name = models.CharField(max_length=200, verbose_name='Ad/Firma Adı') email = models.EmailField(verbose_name='E-posta') phone = models.CharField(max_length=20, blank=True, null=True, verbose_name='Telefon') address = models.TextField(blank=True, null=True, verbose_name='Adres') # Bireysel müşteri alanları surname = models.CharField(max_length=100, blank=True, null=True, verbose_name='Soyad') birth_date = models.DateField(blank=True, null=True, verbose_name='Doğum Tarihi') tc_number = models.CharField(max_length=11, blank=True, null=True, verbose_name='TC Kimlik No') # Kurumsal müşteri alanları company_name = models.CharField(max_length=200, blank=True, null=True, verbose_name='Şirket Adı') tax_number = models.CharField(max_length=20, blank=True, null=True, verbose_name='Vergi No') tax_office = models.CharField(max_length=100, blank=True, null=True, verbose_name='Vergi Dairesi') authorized_person = models.CharField(max_length=200, blank=True, null=True, verbose_name='Yetkili Kişi') # Genel bilgiler notes = models.TextField(blank=True, null=True, verbose_name='Notlar') created_at = models.DateTimeField(auto_now_add=True, verbose_name='Oluşturma Tarihi') updated_at = models.DateTimeField(auto_now=True, verbose_name='Güncelleme Tarihi') is_active = models.BooleanField(default=True, verbose_name='Aktif') def __str__(self): if self.customer_type == 'corporate': return f"{self.company_name or self.name} (Kurumsal)" else: return f"{self.name} {self.surname or ''} (Bireysel)".strip() def get_display_name(self): """Görüntüleme için uygun isim döndür""" if self.customer_type == 'corporate': return self.company_name or self.name else: return f"{self.name} {self.surname or ''}".strip() def clean(self): """Model validasyonu""" if self.customer_type == 'individual': if not self.surname: raise ValidationError('Bireysel müşteriler için soyad zorunludur.') elif self.customer_type == 'corporate': if not self.company_name: raise ValidationError('Kurumsal müşteriler için şirket adı zorunludur.') class Meta: verbose_name = "Müşteri" verbose_name_plural = "Müşteriler" ordering = ['-created_at'] class SSHCredential(models.Model): name = models.CharField(max_length=100, verbose_name='Host Adı', default='Varsayılan Host') hostname = models.CharField(max_length=255) username = models.CharField(max_length=100) password = models.CharField(max_length=100) port = models.IntegerField(default=22) base_path = models.CharField(max_length=500, help_text="Projelerin oluşturulacağı ana dizin") # Yeni alan is_default = models.BooleanField(default=False, verbose_name='Varsayılan Host') connection_status = models.CharField(max_length=20, default='unknown', choices=[ ('connected', 'Bağlı'), ('failed', 'Başarısız'), ('unknown', 'Bilinmiyor') ]) is_online = models.BooleanField(default=False) # Bağlantı durumu last_check = models.DateTimeField(auto_now=True) # Son kontrol zamanı last_checked = models.DateTimeField(null=True, blank=True, verbose_name='Son Kontrol') disk_usage = models.FloatField(null=True, blank=True, verbose_name='Disk Kullanımı (%)') created_at = models.DateTimeField(auto_now_add=True) def get_manager(self): """SSHManager instance'ı döndür""" return SSHManager(self) def __str__(self): return f"{self.username}@{self.hostname}" class Project(models.Model): name = models.CharField(max_length=100, verbose_name='Proje Adı') folder_name = models.CharField(max_length=100, verbose_name='Klasör Adı') ssh_credential = models.ForeignKey(SSHCredential, on_delete=models.CASCADE) customer = models.ForeignKey(Customer, on_delete=models.CASCADE, verbose_name='Müşteri', null=True, blank=True) url = models.TextField(null=True, blank=True, verbose_name='Site URL') disk_usage = models.CharField(max_length=20, null=True, blank=True) last_backup = models.DateTimeField(null=True, blank=True, verbose_name='Son Yedekleme') host_renewal_date = models.DateField(null=True, blank=True, verbose_name='Host Yenileme Tarihi') meta_key = models.CharField(max_length=32, null=True, blank=True, verbose_name='Meta Key', help_text='Site aktiflik kontrolü için benzersiz anahtar') is_site_active = models.BooleanField(default=False, verbose_name='Site Aktif') last_site_check = models.DateTimeField(null=True, blank=True, verbose_name='Son Site Kontrolü') created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) def generate_meta_key(self): """Benzersiz meta key oluştur""" import uuid self.meta_key = uuid.uuid4().hex[:32] return self.meta_key def get_meta_tag(self): """HTML meta tag'ı döndür""" if not self.meta_key: self.generate_meta_key() self.save() # Farklı meta tag formatları meta_tags = [ f'', f'', f'' ] # HTML yorum içinde meta key comment_tag = f'' # Tüm formatları birleştir return meta_tags[0], meta_tags, comment_tag def save(self, *args, **kwargs): self.clean() super().save(*args, **kwargs) def get_full_path(self): """Projenin tam yolunu döndür""" return f"{self.ssh_credential.base_path}/{self.folder_name}" @property def has_requirements(self): """Projenin req.txt dosyası var mı kontrol et""" try: ssh_manager = self.ssh_credential.get_manager() check_cmd = f'test -f "{self.get_full_path()}/req.txt" && echo "exists"' stdout, stderr, status = ssh_manager.execute_command(check_cmd) # Debug için log ekle logger.info(f"has_requirements check for project {self.id}") logger.info(f"Command: {check_cmd}") logger.info(f"Status: {status}, Stdout: {stdout}, Stderr: {stderr}") return status and stdout.strip() == "exists" except Exception as e: logger.exception(f"Error checking requirements for project {self.id}") return False finally: if 'ssh_manager' in locals(): ssh_manager.close() def __str__(self): return self.name class Meta: verbose_name = "Proje" verbose_name_plural = "Projeler" ordering = ['-created_at'] class Invoice(models.Model): INVOICE_STATUS = ( ('draft', 'Taslak'), ('sent', 'Gönderildi'), ('paid', 'Ödendi'), ('overdue', 'Gecikti'), ('cancelled', 'İptal Edildi'), ) PAYMENT_METHODS = ( ('bank_transfer', 'Banka Havalesi'), ('credit_card', 'Kredi Kartı'), ('cash', 'Nakit'), ('other', 'Diğer'), ) INVOICE_TYPES = ( ('income', 'Gelir'), ('expense', 'Gider'), ) customer = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name='invoices', verbose_name='Müşteri') invoice_number = models.CharField(max_length=50, unique=True, verbose_name='Fatura No') invoice_type = models.CharField(max_length=10, choices=INVOICE_TYPES, default='income', verbose_name='Fatura Tipi') issue_date = models.DateField(verbose_name='Düzenleme Tarihi') due_date = models.DateField(verbose_name='Son Ödeme Tarihi') status = models.CharField(max_length=20, choices=INVOICE_STATUS, default='draft', verbose_name='Durum') payment_method = models.CharField(max_length=20, choices=PAYMENT_METHODS, default='bank_transfer', verbose_name='Ödeme Yöntemi') total_amount = models.DecimalField(max_digits=10, decimal_places=2, default=0, verbose_name='Toplam Tutar') notes = models.TextField(blank=True, null=True, verbose_name='Notlar') payment_notes = models.TextField(blank=True, null=True, verbose_name='Ödeme Notları') created_at = models.DateTimeField(auto_now_add=True, verbose_name='Oluşturma Tarihi') updated_at = models.DateTimeField(auto_now=True, verbose_name='Güncelleme Tarihi') def __str__(self): return f"Fatura #{self.invoice_number} - {self.customer.get_display_name()}" def save(self, *args, **kwargs): if not self.invoice_number: # Otomatik fatura numarası oluştur (F-YIL-AYGUN-XXXX) from datetime import datetime year = datetime.now().strftime('%Y') day_month = datetime.now().strftime('%d%m') # Son fatura numarasını bul last_invoice = Invoice.objects.filter( invoice_number__startswith=f'F-{year}' ).order_by('-invoice_number').first() if last_invoice: try: last_num = int(last_invoice.invoice_number.split('-')[-1]) new_num = last_num + 1 except (ValueError, IndexError): new_num = 1 else: new_num = 1 self.invoice_number = f'F-{year}-{day_month}-{new_num:04d}' super().save(*args, **kwargs) class Meta: verbose_name = "Fatura" verbose_name_plural = "Faturalar" ordering = ['-issue_date', '-id'] class InvoiceItem(models.Model): invoice = models.ForeignKey(Invoice, related_name='items', on_delete=models.CASCADE, verbose_name='Fatura') project = models.ForeignKey(Project, on_delete=models.SET_NULL, null=True, blank=True, verbose_name='İlişkili Proje') description = models.CharField(max_length=255, verbose_name='Açıklama') amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='Tutar') def __str__(self): return f"{self.description} - {self.invoice.invoice_number}" def save(self, *args, **kwargs): super().save(*args, **kwargs) self.update_invoice_total() def delete(self, *args, **kwargs): invoice = self.invoice super().delete(*args, **kwargs) # Fatura silindiğinde tutarı güncelle self.update_invoice_total() def update_invoice_total(self): """Fatura toplam tutarını güncelle""" from decimal import Decimal, InvalidOperation invoice = self.invoice items = invoice.items.all() # Toplam tutar - güvenli dönüşümle hesapla total = Decimal('0.00') for item in items: try: # Eğer item.amount None ise veya geçersizse, 0 olarak kabul et if item.amount is None: continue # String ise decimal'e çevir if isinstance(item.amount, str): if ',' in item.amount: item.amount = item.amount.replace(',', '.') item_amount = Decimal(item.amount) else: item_amount = Decimal(str(item.amount)) total += item_amount except (InvalidOperation, ValueError, TypeError) as e: print(f"Invoice total hesaplama hatası: {str(e)}") # Hatalı öğeyi atla continue # Fatura tutarını güncelle invoice.total_amount = total invoice.save() class Meta: verbose_name = "Fatura Kalemi" verbose_name_plural = "Fatura Kalemleri" ordering = ['id'] class SSHLog(models.Model): LOG_TYPES = ( ('connection', 'Bağlantı Kontrolü'), ('command', 'Komut Çalıştırma'), ('folder', 'Klasör İşlemi'), ('file', 'Dosya İşlemi'), ('backup', 'Yedekleme'), # Yeni tip ekleyelim ) ssh_credential = models.ForeignKey(SSHCredential, on_delete=models.CASCADE) log_type = models.CharField(max_length=20, choices=LOG_TYPES) command = models.TextField() output = models.TextField() status = models.BooleanField(default=True) created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return f"{self.ssh_credential.hostname} - {self.log_type} - {self.created_at}" class Meta: ordering = ['-created_at'] class Backup(models.Model): BACKUP_STATUS = ( ('running', 'Devam Ediyor'), ('completed', 'Tamamlandı'), ('failed', 'Başarısız'), ('cancelled', 'İptal Edildi'), ) BACKUP_TYPE = ( ('manual', 'Manuel'), ('auto', 'Otomatik'), ('scheduled', 'Zamanlanmış'), ) project = models.ForeignKey(Project, on_delete=models.CASCADE, verbose_name='Proje') backup_type = models.CharField(max_length=20, choices=BACKUP_TYPE, default='manual', verbose_name='Yedekleme Tipi') status = models.CharField(max_length=20, choices=BACKUP_STATUS, default='running', verbose_name='Durum') start_time = models.DateTimeField(auto_now_add=True, verbose_name='Başlangıç Zamanı') end_time = models.DateTimeField(null=True, blank=True, verbose_name='Bitiş Zamanı') file_path = models.CharField(max_length=500, null=True, blank=True, verbose_name='Dosya Yolu') file_size = models.BigIntegerField(null=True, blank=True, verbose_name='Dosya Boyutu') # BigIntegerField kullanarak büyük dosya boyutlarını destekle error_message = models.TextField(null=True, blank=True, verbose_name='Hata Mesajı') notes = models.TextField(null=True, blank=True, verbose_name='Notlar') def __str__(self): return f"{self.project.name} - {self.start_time.strftime('%d.%m.%Y %H:%M')}" @property def duration(self): """Yedekleme süresini hesapla""" if self.end_time: duration = self.end_time - self.start_time total_seconds = int(duration.total_seconds()) hours = total_seconds // 3600 minutes = (total_seconds % 3600) // 60 seconds = total_seconds % 60 if hours > 0: return f"{hours}s {minutes}d {seconds}s" elif minutes > 0: return f"{minutes}d {seconds}s" else: return f"{seconds}s" return "-" @property def status_badge_class(self): """Durum badge CSS sınıfı""" status_classes = { 'running': 'bg-info', 'completed': 'bg-success', 'failed': 'bg-danger', 'cancelled': 'bg-warning' } return status_classes.get(self.status, 'bg-secondary') class Meta: verbose_name = "Yedekleme" verbose_name_plural = "Yedeklemeler" ordering = ['-start_time']