furry/auction/models.py

316 lines
10 KiB
Python

"""
Auction Models für Custom Order Biet-System
"""
from django.db import models
from django.contrib.auth.models import User
from django.utils import timezone
from django.core.validators import MinValueValidator
import uuid
class Auction(models.Model):
"""Auktion für Custom Order"""
AUCTION_STATUS_CHOICES = [
('draft', 'Entwurf'),
('active', 'Aktiv'),
('paused', 'Pausiert'),
('ended', 'Beendet'),
('cancelled', 'Storniert'),
]
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
title = models.CharField(max_length=200)
description = models.TextField()
# Custom Order Details
fursuit_type = models.CharField(max_length=50, choices=[
('fullsuit', 'Fullsuit'),
('partial', 'Partial'),
('head', 'Head Only'),
('handpaws', 'Handpaws'),
('feetpaws', 'Feetpaws'),
('tail', 'Tail'),
('custom', 'Custom'),
])
character_description = models.TextField()
reference_images = models.JSONField(default=list) # URLs zu Referenzbildern
special_requirements = models.TextField(blank=True)
# Auction Settings
starting_bid = models.DecimalField(max_digits=10, decimal_places=2, validators=[MinValueValidator(0)])
reserve_price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)
current_bid = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)
# Time Settings
start_time = models.DateTimeField()
end_time = models.DateTimeField()
duration_days = models.IntegerField(default=7)
# Status & Tracking
status = models.CharField(max_length=20, choices=AUCTION_STATUS_CHOICES, default='draft')
winner = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='won_auctions')
total_bids = models.IntegerField(default=0)
total_bidders = models.IntegerField(default=0)
# Metadata
created_by = models.ForeignKey(User, on_delete=models.CASCADE, related_name='created_auctions')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
# SEO & Visibility
is_featured = models.BooleanField(default=False)
view_count = models.IntegerField(default=0)
class Meta:
ordering = ['-created_at']
verbose_name = 'Auktion'
verbose_name_plural = 'Auktionen'
def __str__(self):
return f"{self.title} - {self.get_status_display()}"
@property
def is_active(self):
"""Prüfen ob Auktion aktiv ist"""
now = timezone.now()
return (
self.status == 'active' and
self.start_time <= now <= self.end_time
)
@property
def time_remaining(self):
"""Verbleibende Zeit"""
if not self.is_active:
return None
remaining = self.end_time - timezone.now()
return remaining
@property
def time_remaining_formatted(self):
"""Formatierte verbleibende Zeit"""
if not self.time_remaining:
return "Beendet"
days = self.time_remaining.days
hours = self.time_remaining.seconds // 3600
minutes = (self.time_remaining.seconds % 3600) // 60
if days > 0:
return f"{days}d {hours}h {minutes}m"
elif hours > 0:
return f"{hours}h {minutes}m"
else:
return f"{minutes}m"
@property
def has_reserve(self):
"""Prüfen ob Reserve Price gesetzt ist"""
return self.reserve_price is not None
@property
def reserve_met(self):
"""Prüfen ob Reserve Price erreicht wurde"""
if not self.has_reserve:
return True
return self.current_bid and self.current_bid >= self.reserve_price
def get_highest_bid(self):
"""Höchstes Gebot abrufen"""
return self.bids.order_by('-amount').first()
def place_bid(self, user, amount):
"""Gebot platzieren"""
if not self.is_active:
raise ValueError("Auktion ist nicht aktiv")
if amount <= self.current_bid:
raise ValueError("Gebot muss höher als aktuelles Gebot sein")
# Gebot erstellen
bid = Bid.objects.create(
auction=self,
bidder=user,
amount=amount
)
# Auktion aktualisieren
self.current_bid = amount
self.total_bids += 1
self.save()
return bid
def end_auction(self):
"""Auktion beenden"""
if self.status != 'active':
return
highest_bid = self.get_highest_bid()
if highest_bid and self.reserve_met:
self.winner = highest_bid.bidder
self.status = 'ended'
else:
self.status = 'ended' # Kein Gewinner wenn Reserve nicht erreicht
self.save()
# Notification an Gewinner senden
if self.winner:
self.send_winner_notification()
def send_winner_notification(self):
"""Benachrichtigung an Gewinner senden"""
# Email Notification
from django.core.mail import send_mail
from django.template.loader import render_to_string
subject = f"Gratulation! Du hast die Auktion '{self.title}' gewonnen!"
message = render_to_string('auction/emails/winner_notification.html', {
'auction': self,
'user': self.winner
})
send_mail(
subject,
message,
'noreply@kasico.de',
[self.winner.email],
html_message=message
)
class Bid(models.Model):
"""Gebot in einer Auktion"""
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
auction = models.ForeignKey(Auction, on_delete=models.CASCADE, related_name='bids')
bidder = models.ForeignKey(User, on_delete=models.CASCADE, related_name='bids')
amount = models.DecimalField(max_digits=10, decimal_places=2, validators=[MinValueValidator(0)])
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['-created_at']
verbose_name = 'Gebot'
verbose_name_plural = 'Gebote'
unique_together = ['auction', 'bidder', 'amount'] # Verhindert doppelte Gebote
def __str__(self):
return f"{self.bidder.username} - €{self.amount} - {self.auction.title}"
def save(self, *args, **kwargs):
"""Gebot speichern und Auktion aktualisieren"""
is_new = self.pk is None
super().save(*args, **kwargs)
if is_new:
# Auktion aktualisieren
self.auction.current_bid = self.amount
self.auction.total_bids += 1
# Unique bidders zählen
unique_bidders = self.auction.bids.values('bidder').distinct().count()
self.auction.total_bidders = unique_bidders
self.auction.save()
# Outbid Notifications senden
self.send_outbid_notifications()
def send_outbid_notifications(self):
"""Benachrichtigungen an überbotene Bieter senden"""
previous_bidders = self.auction.bids.filter(
created_at__lt=self.created_at
).exclude(bidder=self.bidder).values_list('bidder', flat=True).distinct()
for bidder_id in previous_bidders:
bidder = User.objects.get(id=bidder_id)
self.send_outbid_notification(bidder)
def send_outbid_notification(self, bidder):
"""Einzelne Outbid-Benachrichtigung senden"""
from django.core.mail import send_mail
from django.template.loader import render_to_string
subject = f"Du wurdest überboten - {self.auction.title}"
message = render_to_string('auction/emails/outbid_notification.html', {
'auction': self.auction,
'bid': self,
'user': bidder
})
send_mail(
subject,
message,
'noreply@kasico.de',
[bidder.email],
html_message=message
)
class AuctionWatch(models.Model):
"""Auktion Watchlist"""
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='watched_auctions')
auction = models.ForeignKey(Auction, on_delete=models.CASCADE, related_name='watchers')
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
unique_together = ['user', 'auction']
verbose_name = 'Auktion Watchlist'
verbose_name_plural = 'Auktion Watchlists'
def __str__(self):
return f"{self.user.username} beobachtet {self.auction.title}"
class AuctionAnalytics(models.Model):
"""Auktion Analytics"""
auction = models.OneToOneField(Auction, on_delete=models.CASCADE, related_name='analytics')
total_views = models.IntegerField(default=0)
unique_visitors = models.IntegerField(default=0)
total_bids = models.IntegerField(default=0)
unique_bidders = models.IntegerField(default=0)
average_bid_amount = models.DecimalField(max_digits=10, decimal_places=2, default=0)
highest_bid_amount = models.DecimalField(max_digits=10, decimal_places=2, default=0)
conversion_rate = models.FloatField(default=0) # Views zu Bids
engagement_score = models.FloatField(default=0) # Kombinierter Score
class Meta:
verbose_name = 'Auktion Analytics'
verbose_name_plural = 'Auktion Analytics'
def __str__(self):
return f"Analytics für {self.auction.title}"
def update_analytics(self):
"""Analytics aktualisieren"""
bids = self.auction.bids.all()
self.total_bids = bids.count()
self.unique_bidders = bids.values('bidder').distinct().count()
if self.total_bids > 0:
self.average_bid_amount = bids.aggregate(
avg=models.Avg('amount')
)['avg']
self.highest_bid_amount = bids.aggregate(
max=models.Max('amount')
)['max']
if self.total_views > 0:
self.conversion_rate = (self.total_bids / self.total_views) * 100
# Engagement Score berechnen
self.engagement_score = (
self.total_views * 0.3 +
self.total_bids * 0.4 +
self.unique_bidders * 0.3
)
self.save()