This commit is contained in:
ilkeral
2025-08-08 07:24:25 +03:00
parent 342f1314c7
commit f4ee7a2d0b
29 changed files with 5189 additions and 1140 deletions

View File

@ -3,6 +3,7 @@ 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__)
@ -104,9 +105,10 @@ class Project(models.Model):
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.CharField(max_length=255, 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ü')
@ -124,19 +126,21 @@ class Project(models.Model):
if not self.meta_key:
self.generate_meta_key()
self.save()
return f'<meta name="site-verification" content="{self.meta_key}">'
# Farklı meta tag formatları
meta_tags = [
f'<meta name="site-verification" content="{self.meta_key}">',
f'<meta name="site-verify" content="{self.meta_key}">',
f'<meta name="verification" content="{self.meta_key}">'
]
# HTML yorum içinde meta key
comment_tag = f'<!-- site-verification: {self.meta_key} -->'
# Tüm formatları birleştir
return meta_tags[0], meta_tags, comment_tag
def clean(self):
# URL formatını kontrol et
if self.url:
# URL'den http/https ve www. kısımlarını temizle
cleaned_url = self.url.lower()
for prefix in ['http://', 'https://', 'www.']:
if cleaned_url.startswith(prefix):
cleaned_url = cleaned_url[len(prefix):]
# Sondaki / işaretini kaldır
cleaned_url = cleaned_url.rstrip('/')
self.url = cleaned_url
def save(self, *args, **kwargs):
self.clean()
@ -175,6 +179,131 @@ class Project(models.Model):
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='ı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ü'),
@ -195,4 +324,65 @@ class SSHLog(models.Model):
return f"{self.ssh_credential.hostname} - {self.log_type} - {self.created_at}"
class Meta:
ordering = ['-created_at']
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']