""" 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()