furry/products/auction.py

187 lines
6.4 KiB
Python

from django.db import models
from django.contrib.auth.models import User
from django.utils import timezone
from decimal import Decimal
class Auction(models.Model):
"""Auktion für Custom-Design-Fursuits"""
STATUS_CHOICES = [
('draft', 'Entwurf'),
('active', 'Aktiv'),
('bidding', 'Bietphase'),
('ended', 'Beendet'),
('cancelled', 'Storniert'),
]
title = models.CharField(max_length=200)
description = models.TextField()
starting_price = models.DecimalField(max_digits=10, decimal_places=2)
current_price = models.DecimalField(max_digits=10, decimal_places=2)
reserve_price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)
min_bid_increment = models.DecimalField(max_digits=10, decimal_places=2, default=10.00)
# Custom Order Details
fursuit_type = models.CharField(max_length=20, choices=[
('partial', 'Partial'),
('fullsuit', 'Fullsuit'),
('head', 'Head Only'),
('paws', 'Paws'),
('tail', 'Tail'),
('other', 'Other'),
])
style = models.CharField(max_length=20, choices=[
('toony', 'Toony'),
('semi_realistic', 'Semi-Realistic'),
('realistic', 'Realistic'),
('anime', 'Anime'),
('chibi', 'Chibi'),
])
# Timing
start_time = models.DateTimeField()
end_time = models.DateTimeField()
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
# Status
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='draft')
winner = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='won_auctions')
# Images
reference_images = models.FileField(upload_to='auctions/references/', null=True, blank=True)
preview_image = models.ImageField(upload_to='auctions/previews/', null=True, blank=True)
class Meta:
ordering = ['-created']
def __str__(self):
return f"Auktion: {self.title} - {self.current_price}"
@property
def is_active(self):
now = timezone.now()
return self.status == 'active' and self.start_time <= now <= self.end_time
@property
def time_remaining(self):
if not self.is_active:
return None
return self.end_time - timezone.now()
@property
def bid_count(self):
return self.bids.count()
@property
def highest_bid(self):
return self.bids.order_by('-amount').first()
class AuctionBid(models.Model):
"""Gebot bei einer Auktion"""
auction = models.ForeignKey(Auction, on_delete=models.CASCADE, related_name='bids')
bidder = models.ForeignKey(User, on_delete=models.CASCADE)
amount = models.DecimalField(max_digits=10, decimal_places=2)
timestamp = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['-timestamp']
unique_together = ['auction', 'bidder']
def __str__(self):
return f"{self.bidder.username} - {self.amount}"
def save(self, *args, **kwargs):
# Aktualisiere den aktuellen Preis der Auktion
if self.amount > self.auction.current_price:
self.auction.current_price = self.amount
self.auction.save()
super().save(*args, **kwargs)
class AuctionWatchlist(models.Model):
"""Beobachtungsliste für Auktionen"""
user = models.ForeignKey(User, on_delete=models.CASCADE)
auction = models.ForeignKey(Auction, on_delete=models.CASCADE)
added = models.DateTimeField(auto_now_add=True)
class Meta:
unique_together = ['user', 'auction']
def __str__(self):
return f"{self.user.username} beobachtet {self.auction.title}"
class AuctionService:
"""Service-Klasse für Auktion-Logik"""
@staticmethod
def place_bid(auction, user, amount):
"""Gebot platzieren"""
if not auction.is_active:
raise ValueError("Auktion ist nicht aktiv")
if amount <= auction.current_price:
raise ValueError("Gebot muss höher als der aktuelle Preis sein")
if auction.min_bid_increment and amount < auction.current_price + auction.min_bid_increment:
raise ValueError(f"Mindestgebot: {auction.current_price + auction.min_bid_increment}")
# Erstelle oder aktualisiere Gebot
bid, created = AuctionBid.objects.get_or_create(
auction=auction,
bidder=user,
defaults={'amount': amount}
)
if not created:
bid.amount = amount
bid.save()
return bid
@staticmethod
def end_auction(auction):
"""Auktion beenden"""
if auction.status != 'active':
return
highest_bid = auction.highest_bid
if highest_bid:
# Prüfe Reserve-Preis
if auction.reserve_price and highest_bid.amount < auction.reserve_price:
auction.status = 'ended'
auction.save()
return None # Reserve-Preis nicht erreicht
auction.winner = highest_bid.bidder
auction.status = 'ended'
auction.save()
# Erstelle Custom Order
custom_order = CustomOrder.objects.create(
user=highest_bid.bidder,
fursuit_type=auction.fursuit_type,
style=auction.style,
character_name=f"Auktion: {auction.title}",
character_description=auction.description,
budget_range=f"Gewonnen für {highest_bid.amount}",
status='approved',
quoted_price=highest_bid.amount
)
return custom_order
else:
auction.status = 'ended'
auction.save()
return None
@staticmethod
def get_auction_stats(auction):
"""Statistiken für eine Auktion"""
return {
'total_bids': auction.bid_count,
'unique_bidders': auction.bids.values('bidder').distinct().count(),
'current_price': auction.current_price,
'time_remaining': auction.time_remaining,
'is_active': auction.is_active,
'highest_bidder': auction.highest_bid.bidder.username if auction.highest_bid else None
}