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']