372 lines
11 KiB
Python
372 lines
11 KiB
Python
"""
|
|
Auction Views für Biet-System
|
|
"""
|
|
|
|
from django.shortcuts import render, get_object_or_404, redirect
|
|
from django.contrib.auth.decorators import login_required
|
|
from django.contrib import messages
|
|
from django.http import JsonResponse
|
|
from django.utils import timezone
|
|
from django.db.models import Q, Count, Max
|
|
from django.core.paginator import Paginator
|
|
from django.views.decorators.http import require_POST
|
|
from django.views.decorators.csrf import csrf_exempt
|
|
from .models import Auction, Bid, AuctionWatch, AuctionAnalytics
|
|
from .forms import AuctionForm, BidForm
|
|
import json
|
|
|
|
|
|
def auction_list(request):
|
|
"""Liste aller Auktionen"""
|
|
# Filter
|
|
status_filter = request.GET.get('status', '')
|
|
fursuit_type_filter = request.GET.get('fursuit_type', '')
|
|
search_query = request.GET.get('search', '')
|
|
|
|
auctions = Auction.objects.select_related('created_by', 'winner').prefetch_related('bids')
|
|
|
|
if status_filter:
|
|
auctions = auctions.filter(status=status_filter)
|
|
|
|
if fursuit_type_filter:
|
|
auctions = auctions.filter(fursuit_type=fursuit_type_filter)
|
|
|
|
if search_query:
|
|
auctions = auctions.filter(
|
|
Q(title__icontains=search_query) |
|
|
Q(description__icontains=search_query) |
|
|
Q(character_description__icontains=search_query)
|
|
)
|
|
|
|
# Nur aktive Auktionen anzeigen (außer für Admins)
|
|
if not request.user.is_staff:
|
|
auctions = auctions.filter(status='active')
|
|
|
|
# Sortierung
|
|
sort_by = request.GET.get('sort', '-created_at')
|
|
auctions = auctions.order_by(sort_by)
|
|
|
|
# Pagination
|
|
paginator = Paginator(auctions, 12)
|
|
page_number = request.GET.get('page')
|
|
page_obj = paginator.get_page(page_number)
|
|
|
|
# Statistiken
|
|
total_auctions = auctions.count()
|
|
active_auctions = auctions.filter(status='active').count()
|
|
total_bids = sum(auction.total_bids for auction in page_obj)
|
|
|
|
context = {
|
|
'page_obj': page_obj,
|
|
'status_filter': status_filter,
|
|
'fursuit_type_filter': fursuit_type_filter,
|
|
'search_query': search_query,
|
|
'sort_by': sort_by,
|
|
'total_auctions': total_auctions,
|
|
'active_auctions': active_auctions,
|
|
'total_bids': total_bids,
|
|
'status_choices': Auction.AUCTION_STATUS_CHOICES,
|
|
'fursuit_type_choices': Auction._meta.get_field('fursuit_type').choices,
|
|
}
|
|
|
|
return render(request, 'auction/auction_list.html', context)
|
|
|
|
|
|
def auction_detail(request, auction_id):
|
|
"""Auktion Detail-Ansicht"""
|
|
auction = get_object_or_404(Auction, id=auction_id)
|
|
|
|
# View Count erhöhen
|
|
auction.view_count += 1
|
|
auction.save()
|
|
|
|
# Analytics aktualisieren
|
|
analytics, created = AuctionAnalytics.objects.get_or_create(auction=auction)
|
|
analytics.total_views += 1
|
|
analytics.save()
|
|
|
|
# Bid History
|
|
bid_history = auction.bids.select_related('bidder').order_by('-created_at')[:10]
|
|
|
|
# User's current bid
|
|
user_bid = None
|
|
if request.user.is_authenticated:
|
|
user_bid = auction.bids.filter(bidder=request.user).order_by('-amount').first()
|
|
|
|
# Watchlist Status
|
|
is_watching = False
|
|
if request.user.is_authenticated:
|
|
is_watching = auction.watchers.filter(user=request.user).exists()
|
|
|
|
# Similar Auctions
|
|
similar_auctions = Auction.objects.filter(
|
|
fursuit_type=auction.fursuit_type,
|
|
status='active'
|
|
).exclude(id=auction.id)[:3]
|
|
|
|
context = {
|
|
'auction': auction,
|
|
'bid_history': bid_history,
|
|
'user_bid': user_bid,
|
|
'is_watching': is_watching,
|
|
'similar_auctions': similar_auctions,
|
|
'bid_form': BidForm() if auction.is_active else None,
|
|
}
|
|
|
|
return render(request, 'auction/auction_detail.html', context)
|
|
|
|
|
|
@login_required
|
|
def auction_create(request):
|
|
"""Neue Auktion erstellen"""
|
|
if request.method == 'POST':
|
|
form = AuctionForm(request.POST, request.FILES)
|
|
if form.is_valid():
|
|
auction = form.save(commit=False)
|
|
auction.created_by = request.user
|
|
|
|
# Start- und Endzeit setzen
|
|
if not auction.start_time:
|
|
auction.start_time = timezone.now()
|
|
if not auction.end_time:
|
|
auction.end_time = auction.start_time + timezone.timedelta(days=auction.duration_days)
|
|
|
|
auction.save()
|
|
|
|
messages.success(request, 'Auktion erfolgreich erstellt!')
|
|
return redirect('auction:auction_detail', auction_id=auction.id)
|
|
else:
|
|
form = AuctionForm()
|
|
|
|
context = {
|
|
'form': form,
|
|
'title': 'Neue Auktion erstellen',
|
|
}
|
|
|
|
return render(request, 'auction/auction_form.html', context)
|
|
|
|
|
|
@login_required
|
|
def auction_edit(request, auction_id):
|
|
"""Auktion bearbeiten"""
|
|
auction = get_object_or_404(Auction, id=auction_id, created_by=request.user)
|
|
|
|
if request.method == 'POST':
|
|
form = AuctionForm(request.POST, request.FILES, instance=auction)
|
|
if form.is_valid():
|
|
form.save()
|
|
messages.success(request, 'Auktion erfolgreich aktualisiert!')
|
|
return redirect('auction:auction_detail', auction_id=auction.id)
|
|
else:
|
|
form = AuctionForm(instance=auction)
|
|
|
|
context = {
|
|
'form': form,
|
|
'auction': auction,
|
|
'title': 'Auktion bearbeiten',
|
|
}
|
|
|
|
return render(request, 'auction/auction_form.html', context)
|
|
|
|
|
|
@login_required
|
|
@require_POST
|
|
def place_bid(request, auction_id):
|
|
"""Gebot platzieren"""
|
|
auction = get_object_or_404(Auction, id=auction_id)
|
|
|
|
if not auction.is_active:
|
|
return JsonResponse({'error': 'Auktion ist nicht aktiv'}, status=400)
|
|
|
|
form = BidForm(request.POST)
|
|
if form.is_valid():
|
|
amount = form.cleaned_data['amount']
|
|
|
|
try:
|
|
bid = auction.place_bid(request.user, amount)
|
|
|
|
return JsonResponse({
|
|
'success': True,
|
|
'bid_id': str(bid.id),
|
|
'amount': float(bid.amount),
|
|
'current_bid': float(auction.current_bid),
|
|
'total_bids': auction.total_bids,
|
|
'message': 'Gebot erfolgreich platziert!'
|
|
})
|
|
except ValueError as e:
|
|
return JsonResponse({'error': str(e)}, status=400)
|
|
else:
|
|
return JsonResponse({'error': 'Ungültige Eingabe'}, status=400)
|
|
|
|
|
|
@login_required
|
|
@require_POST
|
|
def toggle_watch(request, auction_id):
|
|
"""Auktion zur Watchlist hinzufügen/entfernen"""
|
|
auction = get_object_or_404(Auction, id=auction_id)
|
|
|
|
watch, created = AuctionWatch.objects.get_or_create(
|
|
user=request.user,
|
|
auction=auction
|
|
)
|
|
|
|
if not created:
|
|
watch.delete()
|
|
is_watching = False
|
|
message = 'Von Watchlist entfernt'
|
|
else:
|
|
is_watching = True
|
|
message = 'Zur Watchlist hinzugefügt'
|
|
|
|
return JsonResponse({
|
|
'success': True,
|
|
'is_watching': is_watching,
|
|
'message': message
|
|
})
|
|
|
|
|
|
@login_required
|
|
def my_auctions(request):
|
|
"""Eigene Auktionen"""
|
|
auctions = Auction.objects.filter(created_by=request.user).order_by('-created_at')
|
|
|
|
# Filter
|
|
status_filter = request.GET.get('status', '')
|
|
if status_filter:
|
|
auctions = auctions.filter(status=status_filter)
|
|
|
|
context = {
|
|
'auctions': auctions,
|
|
'status_choices': Auction.AUCTION_STATUS_CHOICES,
|
|
'status_filter': status_filter,
|
|
}
|
|
|
|
return render(request, 'auction/my_auctions.html', context)
|
|
|
|
|
|
@login_required
|
|
def my_bids(request):
|
|
"""Eigene Gebote"""
|
|
bids = Bid.objects.filter(bidder=request.user).select_related('auction').order_by('-created_at')
|
|
|
|
context = {
|
|
'bids': bids,
|
|
}
|
|
|
|
return render(request, 'auction/my_bids.html', context)
|
|
|
|
|
|
@login_required
|
|
def watchlist(request):
|
|
"""Watchlist"""
|
|
watched_auctions = Auction.objects.filter(
|
|
watchers__user=request.user
|
|
).order_by('-created_at')
|
|
|
|
context = {
|
|
'watched_auctions': watched_auctions,
|
|
}
|
|
|
|
return render(request, 'auction/watchlist.html', context)
|
|
|
|
|
|
# API Views für AJAX
|
|
@csrf_exempt
|
|
def auction_api(request, auction_id):
|
|
"""API für Auktion-Details"""
|
|
auction = get_object_or_404(Auction, id=auction_id)
|
|
|
|
data = {
|
|
'id': str(auction.id),
|
|
'title': auction.title,
|
|
'description': auction.description,
|
|
'current_bid': float(auction.current_bid) if auction.current_bid else None,
|
|
'starting_bid': float(auction.starting_bid),
|
|
'reserve_price': float(auction.reserve_price) if auction.reserve_price else None,
|
|
'status': auction.status,
|
|
'is_active': auction.is_active,
|
|
'time_remaining': auction.time_remaining_formatted,
|
|
'total_bids': auction.total_bids,
|
|
'total_bidders': auction.total_bidders,
|
|
'winner': auction.winner.username if auction.winner else None,
|
|
'created_at': auction.created_at.isoformat(),
|
|
'end_time': auction.end_time.isoformat(),
|
|
}
|
|
|
|
return JsonResponse(data)
|
|
|
|
|
|
@csrf_exempt
|
|
def bid_history_api(request, auction_id):
|
|
"""API für Bid-History"""
|
|
auction = get_object_or_404(Auction, id=auction_id)
|
|
bids = auction.bids.select_related('bidder').order_by('-created_at')[:20]
|
|
|
|
bid_list = []
|
|
for bid in bids:
|
|
bid_list.append({
|
|
'id': str(bid.id),
|
|
'bidder': bid.bidder.username,
|
|
'amount': float(bid.amount),
|
|
'created_at': bid.created_at.isoformat(),
|
|
})
|
|
|
|
return JsonResponse({'bids': bid_list})
|
|
|
|
|
|
@csrf_exempt
|
|
def place_bid_api(request, auction_id):
|
|
"""API für Gebot platzieren"""
|
|
if request.method != 'POST':
|
|
return JsonResponse({'error': 'Nur POST erlaubt'}, status=405)
|
|
|
|
if not request.user.is_authenticated:
|
|
return JsonResponse({'error': 'Nicht eingeloggt'}, status=401)
|
|
|
|
try:
|
|
data = json.loads(request.body)
|
|
amount = float(data.get('amount', 0))
|
|
|
|
auction = get_object_or_404(Auction, id=auction_id)
|
|
|
|
if not auction.is_active:
|
|
return JsonResponse({'error': 'Auktion ist nicht aktiv'}, status=400)
|
|
|
|
bid = auction.place_bid(request.user, amount)
|
|
|
|
return JsonResponse({
|
|
'success': True,
|
|
'bid_id': str(bid.id),
|
|
'amount': float(bid.amount),
|
|
'current_bid': float(auction.current_bid),
|
|
'total_bids': auction.total_bids,
|
|
})
|
|
|
|
except (json.JSONDecodeError, ValueError) as e:
|
|
return JsonResponse({'error': str(e)}, status=400)
|
|
|
|
|
|
def auction_analytics(request, auction_id):
|
|
"""Auktion Analytics"""
|
|
auction = get_object_or_404(Auction, id=auction_id)
|
|
analytics = get_object_or_404(AuctionAnalytics, auction=auction)
|
|
|
|
# Bid Distribution
|
|
bid_distribution = auction.bids.extra(
|
|
select={'hour': "EXTRACT(hour FROM created_at)"}
|
|
).values('hour').annotate(count=Count('id')).order_by('hour')
|
|
|
|
# Top Bidders
|
|
top_bidders = auction.bids.values('bidder__username').annotate(
|
|
total_bids=Count('id'),
|
|
max_bid=Max('amount')
|
|
).order_by('-max_bid')[:5]
|
|
|
|
context = {
|
|
'auction': auction,
|
|
'analytics': analytics,
|
|
'bid_distribution': bid_distribution,
|
|
'top_bidders': top_bidders,
|
|
}
|
|
|
|
return render(request, 'auction/analytics.html', context) |