furry/recommendations/views.py

442 lines
15 KiB
Python

"""
Recommendation Engine Views für ML-basierte Empfehlungen
"""
from django.shortcuts import render
from django.http import JsonResponse
from django.contrib.auth.decorators import login_required
from django.utils import timezone
from django.db.models import Q, Count, Avg, Sum
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from .models import UserBehavior, UserProfile, ProductSimilarity, Recommendation, RecommendationModel, ABTest, RecommendationAnalytics
from .services import RecommendationService
import json
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def get_recommendations(request):
"""Personalisiertes Empfehlungen für User"""
try:
# Get user profile
profile, created = UserProfile.objects.get_or_create(user=request.user)
# Get recommendations
recommendation_service = RecommendationService()
recommendations = recommendation_service.get_recommendations_for_user(request.user)
# Track recommendation view
UserBehavior.objects.create(
user=request.user,
behavior_type='view',
metadata={'recommendation_count': len(recommendations)}
)
return Response({
'recommendations': recommendations,
'user_profile': {
'engagement_score': profile.engagement_score,
'loyalty_level': profile.loyalty_level,
'preferred_categories': profile.preferred_categories,
}
})
except Exception as e:
return Response({'error': str(e)}, status=500)
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def get_similar_products(request, product_id):
"""Ähnliche Produkte für ein spezifisches Produkt"""
try:
from products.models import Product
product = Product.objects.get(id=product_id)
# Get similar products
similar_products = ProductSimilarity.objects.filter(
product=product
).select_related('similar_product').order_by('-similarity_score')[:10]
recommendations = []
for similarity in similar_products:
recommendations.append({
'product': {
'id': similarity.similar_product.id,
'name': similarity.similar_product.name,
'price': float(similarity.similar_product.price),
'image_url': similarity.similar_product.image.url if similarity.similar_product.image else None,
},
'similarity_score': similarity.similarity_score,
'similarity_type': similarity.similarity_type,
'reason': f"Ähnlich zu {product.name}"
})
return Response({'similar_products': recommendations})
except Product.DoesNotExist:
return Response({'error': 'Product not found'}, status=404)
except Exception as e:
return Response({'error': str(e)}, status=500)
@api_view(['POST'])
@permission_classes([IsAuthenticated])
def track_behavior(request):
"""User Behavior tracken"""
try:
data = request.data
behavior_type = data.get('behavior_type')
product_id = data.get('product_id')
auction_id = data.get('auction_id')
metadata = data.get('metadata', {})
if not behavior_type:
return Response({'error': 'Behavior type required'}, status=400)
# Create behavior record
behavior = UserBehavior.objects.create(
user=request.user,
behavior_type=behavior_type,
product_id=product_id,
auction_id=auction_id,
session_id=data.get('session_id', ''),
ip_address=request.META.get('REMOTE_ADDR'),
user_agent=request.META.get('HTTP_USER_AGENT', ''),
metadata=metadata
)
# Update user profile
profile, created = UserProfile.objects.get_or_create(user=request.user)
profile.update_engagement_score()
return Response({'behavior_id': str(behavior.id)})
except Exception as e:
return Response({'error': str(e)}, status=500)
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def get_user_profile(request):
"""User Profile für Empfehlungen abrufen"""
try:
profile, created = UserProfile.objects.get_or_create(user=request.user)
return Response({
'engagement_score': profile.engagement_score,
'loyalty_level': profile.loyalty_level,
'preferred_categories': profile.preferred_categories,
'preferred_fursuit_types': profile.preferred_fursuit_types,
'preferred_price_range': profile.preferred_price_range,
'total_purchases': profile.total_purchases,
'total_views': profile.total_views,
'last_active': profile.last_active,
})
except Exception as e:
return Response({'error': str(e)}, status=500)
@api_view(['POST'])
@permission_classes([IsAuthenticated])
def update_user_preferences(request):
"""User Preferences aktualisieren"""
try:
data = request.data
profile, created = UserProfile.objects.get_or_create(user=request.user)
# Update preferences
if 'preferred_categories' in data:
profile.preferred_categories = data['preferred_categories']
if 'preferred_fursuit_types' in data:
profile.preferred_fursuit_types = data['preferred_fursuit_types']
if 'preferred_price_range' in data:
profile.preferred_price_range = data['preferred_price_range']
if 'preferred_colors' in data:
profile.preferred_colors = data['preferred_colors']
profile.save()
return Response({'message': 'Preferences updated'})
except Exception as e:
return Response({'error': str(e)}, status=500)
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def get_popular_products(request):
"""Beliebte Produkte basierend auf Verhalten"""
try:
# Get popular products based on behavior
popular_products = Product.objects.annotate(
view_count=Count('behaviors', filter=Q(behaviors__behavior_type='view')),
purchase_count=Count('behaviors', filter=Q(behaviors__behavior_type='purchase')),
cart_count=Count('behaviors', filter=Q(behaviors__behavior_type='cart_add')),
).filter(
is_active=True
).order_by('-purchase_count', '-view_count')[:10]
recommendations = []
for product in popular_products:
recommendations.append({
'product': {
'id': product.id,
'name': product.name,
'price': float(product.price),
'image_url': product.image.url if product.image else None,
'category': product.category,
},
'popularity_score': product.purchase_count + (product.view_count * 0.1),
'reason': 'Beliebt bei anderen Kunden'
})
return Response({'popular_products': recommendations})
except Exception as e:
return Response({'error': str(e)}, status=500)
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def get_trending_products(request):
"""Trending Produkte (recent activity)"""
try:
# Get trending products (recent activity)
from datetime import timedelta
recent_date = timezone.now() - timedelta(days=7)
trending_products = Product.objects.annotate(
recent_views=Count('behaviors', filter=Q(
behaviors__behavior_type='view',
behaviors__created_at__gte=recent_date
)),
recent_purchases=Count('behaviors', filter=Q(
behaviors__behavior_type='purchase',
behaviors__created_at__gte=recent_date
)),
).filter(
is_active=True,
recent_views__gt=0
).order_by('-recent_purchases', '-recent_views')[:10]
recommendations = []
for product in trending_products:
recommendations.append({
'product': {
'id': product.id,
'name': product.name,
'price': float(product.price),
'image_url': product.image.url if product.image else None,
'category': product.category,
},
'trending_score': product.recent_purchases + (product.recent_views * 0.1),
'reason': 'Trending diese Woche'
})
return Response({'trending_products': recommendations})
except Exception as e:
return Response({'error': str(e)}, status=500)
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def get_frequently_bought_together(request, product_id):
"""Häufig zusammen gekaufte Produkte"""
try:
from products.models import Product
product = Product.objects.get(id=product_id)
# Get users who bought this product
buyers = UserBehavior.objects.filter(
product=product,
behavior_type='purchase'
).values_list('user', flat=True).distinct()
# Get other products bought by same users
frequently_bought = Product.objects.filter(
behaviors__user__in=buyers,
behaviors__behavior_type='purchase'
).exclude(id=product_id).annotate(
bought_together_count=Count('behaviors', filter=Q(
behaviors__behavior_type='purchase',
behaviors__user__in=buyers
))
).filter(
bought_together_count__gt=0,
is_active=True
).order_by('-bought_together_count')[:5]
recommendations = []
for product in frequently_bought:
recommendations.append({
'product': {
'id': product.id,
'name': product.name,
'price': float(product.price),
'image_url': product.image.url if product.image else None,
},
'bought_together_count': product.bought_together_count,
'reason': 'Häufig zusammen gekauft'
})
return Response({'frequently_bought_together': recommendations})
except Product.DoesNotExist:
return Response({'error': 'Product not found'}, status=404)
except Exception as e:
return Response({'error': str(e)}, status=500)
@api_view(['POST'])
@permission_classes([IsAuthenticated])
def track_recommendation_click(request):
"""Recommendation Click tracken"""
try:
data = request.data
product_id = data.get('product_id')
recommendation_type = data.get('recommendation_type')
if not product_id or not recommendation_type:
return Response({'error': 'Product ID and recommendation type required'}, status=400)
# Update recommendation
recommendation = Recommendation.objects.filter(
user=request.user,
product_id=product_id,
recommendation_type=recommendation_type
).first()
if recommendation:
recommendation.is_clicked = True
recommendation.save()
# Track behavior
UserBehavior.objects.create(
user=request.user,
behavior_type='view',
product_id=product_id,
metadata={'recommendation_type': recommendation_type}
)
return Response({'success': True})
except Exception as e:
return Response({'error': str(e)}, status=500)
@api_view(['POST'])
@permission_classes([IsAuthenticated])
def track_recommendation_purchase(request):
"""Recommendation Purchase tracken"""
try:
data = request.data
product_id = data.get('product_id')
recommendation_type = data.get('recommendation_type')
if not product_id or not recommendation_type:
return Response({'error': 'Product ID and recommendation type required'}, status=400)
# Update recommendation
recommendation = Recommendation.objects.filter(
user=request.user,
product_id=product_id,
recommendation_type=recommendation_type
).first()
if recommendation:
recommendation.is_purchased = True
recommendation.save()
# Track behavior
UserBehavior.objects.create(
user=request.user,
behavior_type='purchase',
product_id=product_id,
metadata={'recommendation_type': recommendation_type}
)
return Response({'success': True})
except Exception as e:
return Response({'error': str(e)}, status=500)
# Admin Views
@login_required
def recommendation_dashboard(request):
"""Admin Dashboard für Empfehlungen"""
if not request.user.is_staff:
return JsonResponse({'error': 'Access denied'}, status=403)
# Get analytics
today_analytics = RecommendationAnalytics.get_or_create_today()
# Get recent behaviors
recent_behaviors = UserBehavior.objects.select_related('user', 'product').order_by('-created_at')[:20]
# Get active models
active_models = RecommendationModel.objects.filter(is_active=True)
# Get A/B tests
active_tests = ABTest.objects.filter(status='running')
context = {
'today_analytics': today_analytics,
'recent_behaviors': recent_behaviors,
'active_models': active_models,
'active_tests': active_tests,
}
return render(request, 'recommendations/dashboard.html', context)
@login_required
def recommendation_analytics(request):
"""Recommendation Analytics"""
if not request.user.is_staff:
return JsonResponse({'error': 'Access denied'}, status=403)
# Get analytics for last 30 days
from datetime import timedelta
analytics = RecommendationAnalytics.objects.filter(
date__gte=timezone.now().date() - timedelta(days=30)
).order_by('date')
# Calculate totals
total_recommendations = sum(a.total_recommendations for a in analytics)
total_clicks = sum(a.total_clicks for a in analytics)
total_purchases = sum(a.total_purchases for a in analytics)
total_revenue = sum(a.revenue_from_recommendations for a in analytics)
context = {
'analytics': analytics,
'totals': {
'recommendations': total_recommendations,
'clicks': total_clicks,
'purchases': total_purchases,
'revenue': total_revenue,
'ctr': (total_clicks / total_recommendations * 100) if total_recommendations > 0 else 0,
'conversion_rate': (total_purchases / total_clicks * 100) if total_clicks > 0 else 0,
}
}
return render(request, 'recommendations/analytics.html', context)