Compare commits
2 Commits
97afd62c55
...
293489cd7d
| Author | SHA1 | Date |
|---|---|---|
|
|
293489cd7d | |
|
|
f89e715c80 |
|
|
@ -1,90 +1,2 @@
|
||||||
from django.contrib import admin
|
# Diese Datei ist jetzt leer, da alle Admin-Konfigurationen in webshop/admin.py zentral verwaltet werden
|
||||||
from .models import (
|
# Die Admin-Konfigurationen wurden in die neue zentrale Admin-Site verschoben
|
||||||
Product, Cart, CartItem, Order, OrderItem,
|
|
||||||
Review, UserProfile, FAQ, ContactMessage,
|
|
||||||
CustomOrder, OrderProgress, GalleryImage
|
|
||||||
)
|
|
||||||
from django.utils.safestring import mark_safe
|
|
||||||
|
|
||||||
@admin.register(Product)
|
|
||||||
class ProductAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ('name', 'price', 'stock', 'fursuit_type', 'style', 'is_featured', 'is_custom_order')
|
|
||||||
list_filter = ('fursuit_type', 'style', 'is_featured', 'is_custom_order')
|
|
||||||
search_fields = ('name', 'description')
|
|
||||||
ordering = ('-created',)
|
|
||||||
|
|
||||||
@admin.register(Order)
|
|
||||||
class OrderAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ('id', 'full_name', 'total_amount', 'status', 'payment_status', 'created')
|
|
||||||
list_filter = ('status', 'payment_status', 'payment_method')
|
|
||||||
search_fields = ('full_name', 'email')
|
|
||||||
ordering = ('-created',)
|
|
||||||
|
|
||||||
@admin.register(OrderItem)
|
|
||||||
class OrderItemAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ('order', 'product_name', 'quantity', 'price')
|
|
||||||
search_fields = ('product_name',)
|
|
||||||
|
|
||||||
@admin.register(Review)
|
|
||||||
class ReviewAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ('product', 'user', 'rating', 'created')
|
|
||||||
list_filter = ('rating',)
|
|
||||||
search_fields = ('comment', 'user__username')
|
|
||||||
|
|
||||||
@admin.register(Cart)
|
|
||||||
class CartAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ('id', 'user', 'created')
|
|
||||||
search_fields = ('user__username',)
|
|
||||||
|
|
||||||
@admin.register(CartItem)
|
|
||||||
class CartItemAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ('cart', 'product', 'quantity')
|
|
||||||
|
|
||||||
@admin.register(UserProfile)
|
|
||||||
class UserProfileAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ('user', 'phone', 'newsletter')
|
|
||||||
list_filter = ('newsletter',)
|
|
||||||
search_fields = ('user__username', 'phone', 'address')
|
|
||||||
|
|
||||||
@admin.register(FAQ)
|
|
||||||
class FAQAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ('question', 'category', 'order')
|
|
||||||
list_filter = ('category',)
|
|
||||||
search_fields = ('question', 'answer')
|
|
||||||
ordering = ('category', 'order')
|
|
||||||
|
|
||||||
@admin.register(ContactMessage)
|
|
||||||
class ContactMessageAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ('subject', 'name', 'email', 'category', 'status', 'created')
|
|
||||||
list_filter = ('category', 'status')
|
|
||||||
search_fields = ('name', 'email', 'subject', 'message')
|
|
||||||
ordering = ('-created',)
|
|
||||||
|
|
||||||
@admin.register(CustomOrder)
|
|
||||||
class CustomOrderAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ('character_name', 'user', 'fursuit_type', 'status', 'created')
|
|
||||||
list_filter = ('fursuit_type', 'style', 'status')
|
|
||||||
search_fields = ('character_name', 'user__username', 'character_description')
|
|
||||||
ordering = ('-created',)
|
|
||||||
|
|
||||||
@admin.register(OrderProgress)
|
|
||||||
class OrderProgressAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ('custom_order', 'stage', 'completed', 'created')
|
|
||||||
list_filter = ('stage', 'completed')
|
|
||||||
search_fields = ('custom_order__character_name', 'description')
|
|
||||||
ordering = ('custom_order', 'created')
|
|
||||||
|
|
||||||
@admin.register(GalleryImage)
|
|
||||||
class GalleryImageAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ('title', 'fursuit_type', 'style', 'is_featured', 'order', 'created', 'admin_image')
|
|
||||||
list_filter = ('fursuit_type', 'style', 'is_featured')
|
|
||||||
search_fields = ('title', 'description')
|
|
||||||
ordering = ('order', '-created')
|
|
||||||
list_editable = ('order', 'is_featured')
|
|
||||||
readonly_fields = ('admin_image',)
|
|
||||||
|
|
||||||
def admin_image(self, obj):
|
|
||||||
if obj.image:
|
|
||||||
return mark_safe(f'<img src="{obj.image.url}" style="max-height: 50px;" />')
|
|
||||||
return "Kein Bild"
|
|
||||||
admin_image.short_description = 'Vorschau'
|
|
||||||
|
|
|
||||||
125
shop/admin.py
125
shop/admin.py
|
|
@ -1,123 +1,2 @@
|
||||||
from django.contrib import admin
|
# Diese Datei ist jetzt leer, da alle Admin-Konfigurationen in webshop/admin.py zentral verwaltet werden
|
||||||
from django.utils.translation import gettext_lazy as _
|
# Die Admin-Konfigurationen wurden in die neue zentrale Admin-Site verschoben
|
||||||
from django.utils.html import format_html
|
|
||||||
from .models import (
|
|
||||||
Product, Category, FursuitGallery, GalleryImage,
|
|
||||||
DesignTemplate, CustomerDesign, Order, OrderProgress,
|
|
||||||
Cart, CartItem, ShippingAddress, Checkout,
|
|
||||||
ProductType, ProductImage, ProductVariant, CustomDesign,
|
|
||||||
PayPalPayment, PaymentError
|
|
||||||
)
|
|
||||||
|
|
||||||
@admin.register(Category)
|
|
||||||
class CategoryAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ['name', 'slug', 'parent']
|
|
||||||
list_filter = ['parent']
|
|
||||||
search_fields = ['name']
|
|
||||||
prepopulated_fields = {'slug': ('name',)}
|
|
||||||
|
|
||||||
@admin.register(ProductType)
|
|
||||||
class ProductTypeAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ['name', 'has_sizes', 'has_colors', 'has_custom_design', 'requires_measurements']
|
|
||||||
list_filter = ['has_sizes', 'has_colors', 'has_custom_design', 'requires_measurements']
|
|
||||||
search_fields = ['name']
|
|
||||||
|
|
||||||
class ProductImageInline(admin.TabularInline):
|
|
||||||
model = ProductImage
|
|
||||||
extra = 1
|
|
||||||
|
|
||||||
class ProductVariantInline(admin.TabularInline):
|
|
||||||
model = ProductVariant
|
|
||||||
extra = 1
|
|
||||||
|
|
||||||
@admin.register(Product)
|
|
||||||
class ProductAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ['name', 'category', 'price', 'stock', 'available', 'created']
|
|
||||||
list_filter = ['available', 'category', 'product_type', 'created']
|
|
||||||
search_fields = ['name', 'description']
|
|
||||||
prepopulated_fields = {'slug': ('name',)}
|
|
||||||
inlines = [ProductImageInline, ProductVariantInline]
|
|
||||||
date_hierarchy = 'created'
|
|
||||||
|
|
||||||
@admin.register(CustomDesign)
|
|
||||||
class CustomDesignAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ['product', 'customer', 'status', 'created']
|
|
||||||
list_filter = ['status', 'created']
|
|
||||||
search_fields = ['product__name', 'customer__username']
|
|
||||||
date_hierarchy = 'created'
|
|
||||||
|
|
||||||
@admin.register(DesignTemplate)
|
|
||||||
class DesignTemplateAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ['name', 'created_at']
|
|
||||||
search_fields = ['name', 'description']
|
|
||||||
|
|
||||||
@admin.register(CustomerDesign)
|
|
||||||
class CustomerDesignAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ['user', 'name', 'created_at']
|
|
||||||
search_fields = ['user__username', 'name']
|
|
||||||
date_hierarchy = 'created_at'
|
|
||||||
|
|
||||||
class OrderProgressInline(admin.TabularInline):
|
|
||||||
model = OrderProgress
|
|
||||||
extra = 1
|
|
||||||
|
|
||||||
@admin.register(Order)
|
|
||||||
class OrderAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ['id', 'user', 'product', 'status', 'payment_method', 'total_price', 'created_at']
|
|
||||||
list_filter = ['status', 'payment_method', 'created_at']
|
|
||||||
search_fields = ['user__username', 'product__name']
|
|
||||||
inlines = [OrderProgressInline]
|
|
||||||
date_hierarchy = 'created_at'
|
|
||||||
|
|
||||||
class CartItemInline(admin.TabularInline):
|
|
||||||
model = CartItem
|
|
||||||
extra = 1
|
|
||||||
|
|
||||||
@admin.register(Cart)
|
|
||||||
class CartAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ['user', 'created_at', 'updated_at']
|
|
||||||
search_fields = ['user__username']
|
|
||||||
inlines = [CartItemInline]
|
|
||||||
date_hierarchy = 'created_at'
|
|
||||||
|
|
||||||
@admin.register(ShippingAddress)
|
|
||||||
class ShippingAddressAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ['user', 'first_name', 'last_name', 'city', 'country']
|
|
||||||
list_filter = ['country']
|
|
||||||
search_fields = ['user__username', 'first_name', 'last_name', 'city']
|
|
||||||
|
|
||||||
@admin.register(Checkout)
|
|
||||||
class CheckoutAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ['user', 'status', 'payment_method', 'created_at']
|
|
||||||
list_filter = ['status', 'payment_method']
|
|
||||||
search_fields = ['user__username']
|
|
||||||
date_hierarchy = 'created_at'
|
|
||||||
|
|
||||||
@admin.register(PayPalPayment)
|
|
||||||
class PayPalPaymentAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ['order', 'payment_id', 'status', 'amount', 'currency', 'created_at']
|
|
||||||
list_filter = ['status', 'currency']
|
|
||||||
search_fields = ['payment_id', 'order__id']
|
|
||||||
date_hierarchy = 'created_at'
|
|
||||||
|
|
||||||
@admin.register(PaymentError)
|
|
||||||
class PaymentErrorAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ['order', 'error_code', 'created_at']
|
|
||||||
search_fields = ['order__id', 'error_code', 'error_message']
|
|
||||||
date_hierarchy = 'created_at'
|
|
||||||
|
|
||||||
@admin.register(FursuitGallery)
|
|
||||||
class FursuitGalleryAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ['name', 'created_at']
|
|
||||||
search_fields = ['name', 'description']
|
|
||||||
prepopulated_fields = {'slug': ('name',)}
|
|
||||||
|
|
||||||
class GalleryImageInline(admin.TabularInline):
|
|
||||||
model = GalleryImage
|
|
||||||
extra = 1
|
|
||||||
|
|
||||||
@admin.register(GalleryImage)
|
|
||||||
class GalleryImageAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ['gallery', 'title', 'order']
|
|
||||||
list_filter = ['gallery']
|
|
||||||
search_fields = ['gallery__name', 'title']
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,219 @@
|
||||||
|
{% extends "admin/base_site.html" %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block title %}Dashboard - Kasico Administration{% endblock %}
|
||||||
|
|
||||||
|
{% block extrastyle %}
|
||||||
|
<style>
|
||||||
|
.dashboard-stats {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card {
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1.5rem;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
border-left: 4px solid #8B5CF6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card h3 {
|
||||||
|
margin: 0 0 0.5rem 0;
|
||||||
|
color: #374151;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card .stat-value {
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #8B5CF6;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card .stat-change {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: #6B7280;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-section {
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1.5rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-section h2 {
|
||||||
|
margin: 0 0 1rem 0;
|
||||||
|
color: #374151;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recent-item {
|
||||||
|
padding: 0.75rem;
|
||||||
|
border-bottom: 1px solid #E5E7EB;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recent-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge {
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-new { background: #DBEAFE; color: #1E40AF; }
|
||||||
|
.status-pending { background: #FEF3C7; color: #92400E; }
|
||||||
|
.status-paid { background: #D1FAE5; color: #065F46; }
|
||||||
|
.status-shipped { background: #E0E7FF; color: #3730A3; }
|
||||||
|
|
||||||
|
.quick-actions {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
|
gap: 1rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-action {
|
||||||
|
background: #F3F4F6;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 1rem;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
color: #374151;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-action:hover {
|
||||||
|
background: #E5E7EB;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-action-icon {
|
||||||
|
font-size: 2rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="dashboard">
|
||||||
|
<h1 style="margin-bottom: 2rem; color: #374151;">🐾 Kasico Dashboard</h1>
|
||||||
|
|
||||||
|
<!-- Statistiken -->
|
||||||
|
<div class="dashboard-stats">
|
||||||
|
<div class="stat-card">
|
||||||
|
<h3>Gesamtbestellungen</h3>
|
||||||
|
<div class="stat-value">{{ total_orders }}</div>
|
||||||
|
<div class="stat-change">+12% diese Woche</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stat-card">
|
||||||
|
<h3>Produkte</h3>
|
||||||
|
<div class="stat-value">{{ total_products }}</div>
|
||||||
|
<div class="stat-change">{{ low_stock_products.count }} niedriger Lagerbestand</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stat-card">
|
||||||
|
<h3>Kunden</h3>
|
||||||
|
<div class="stat-value">{{ total_users }}</div>
|
||||||
|
<div class="stat-change">+5% diesen Monat</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stat-card">
|
||||||
|
<h3>Offene Anfragen</h3>
|
||||||
|
<div class="stat-value">{{ pending_contacts.count }}</div>
|
||||||
|
<div class="stat-change">Neue Nachrichten</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem;">
|
||||||
|
<!-- Letzte Bestellungen -->
|
||||||
|
<div class="dashboard-section">
|
||||||
|
<h2>📦 Letzte Bestellungen</h2>
|
||||||
|
{% for order in recent_orders %}
|
||||||
|
<div class="recent-item">
|
||||||
|
<div>
|
||||||
|
<strong>#{{ order.id }}</strong> - {{ order.full_name }}
|
||||||
|
<br>
|
||||||
|
<small>{{ order.created|date:"d.m.Y H:i" }}</small>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class="status-badge status-{{ order.payment_status }}">
|
||||||
|
{{ order.get_payment_status_display }}
|
||||||
|
</span>
|
||||||
|
<br>
|
||||||
|
<small>{{ order.total_amount }}€</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% empty %}
|
||||||
|
<p style="color: #6B7280; text-align: center; padding: 2rem;">Keine Bestellungen vorhanden</p>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Niedriger Lagerbestand -->
|
||||||
|
<div class="dashboard-section">
|
||||||
|
<h2>⚠️ Niedriger Lagerbestand</h2>
|
||||||
|
{% for product in low_stock_products %}
|
||||||
|
<div class="recent-item">
|
||||||
|
<div>
|
||||||
|
<strong>{{ product.name }}</strong>
|
||||||
|
<br>
|
||||||
|
<small>{{ product.get_fursuit_type_display }} • {{ product.get_style_display }}</small>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class="status-badge status-pending">
|
||||||
|
{{ product.stock }} Stück
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% empty %}
|
||||||
|
<p style="color: #6B7280; text-align: center; padding: 2rem;">Alle Produkte verfügbar</p>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Schnellaktionen -->
|
||||||
|
<div class="dashboard-section">
|
||||||
|
<h2>⚡ Schnellaktionen</h2>
|
||||||
|
<div class="quick-actions">
|
||||||
|
<a href="{% url 'admin:products_product_add' %}" class="quick-action">
|
||||||
|
<div class="quick-action-icon">➕</div>
|
||||||
|
<div>Neues Produkt</div>
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'admin:products_order_changelist' %}" class="quick-action">
|
||||||
|
<div class="quick-action-icon">📦</div>
|
||||||
|
<div>Bestellungen</div>
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'admin:products_contactmessage_changelist' %}" class="quick-action">
|
||||||
|
<div class="quick-action-icon">💬</div>
|
||||||
|
<div>Nachrichten</div>
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'admin:products_customorder_changelist' %}" class="quick-action">
|
||||||
|
<div class="quick-action-icon">🎨</div>
|
||||||
|
<div>Custom Orders</div>
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'admin:chat_chatroom_changelist' %}" class="quick-action">
|
||||||
|
<div class="quick-action-icon">💬</div>
|
||||||
|
<div>Chat Support</div>
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'admin:recommendations_recommendationmodel_changelist' %}" class="quick-action">
|
||||||
|
<div class="quick-action-icon">🤖</div>
|
||||||
|
<div>ML Models</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
@ -0,0 +1,467 @@
|
||||||
|
"""
|
||||||
|
Zentrale Admin-Konfiguration für Kasico Fursuit Shop
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.contrib.admin import AdminSite
|
||||||
|
from django.utils.html import format_html
|
||||||
|
from django.urls import path
|
||||||
|
from django.db.models import Count, Sum, Avg
|
||||||
|
from django.utils import timezone
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
# Import aller Modelle
|
||||||
|
from products.models import (
|
||||||
|
Product, Order, OrderItem, Review, UserProfile, FAQ,
|
||||||
|
ContactMessage, CustomOrder, OrderProgress, GalleryImage
|
||||||
|
)
|
||||||
|
from shop.models import (
|
||||||
|
Category, ProductType, ProductImage, ProductVariant,
|
||||||
|
CustomDesign, PayPalPayment, PaymentError, Cart, CartItem
|
||||||
|
)
|
||||||
|
from chat.models import (
|
||||||
|
ChatRoom, ChatMessage, UserOnlineStatus, QuickResponse, ChatAnalytics
|
||||||
|
)
|
||||||
|
from recommendations.models import (
|
||||||
|
UserBehavior, UserProfile as RecUserProfile, ProductSimilarity,
|
||||||
|
Recommendation, RecommendationModel, ABTest, RecommendationAnalytics
|
||||||
|
)
|
||||||
|
from mobile.models import MobileDevice, MobileSession
|
||||||
|
from paypal_integration.models import PayPalConfig
|
||||||
|
|
||||||
|
|
||||||
|
# Custom Admin Site
|
||||||
|
class KasicoAdminSite(AdminSite):
|
||||||
|
site_header = "🐾 Kasico Fursuit Shop Administration"
|
||||||
|
site_title = "Kasico Admin"
|
||||||
|
index_title = "Willkommen in der Kasico Administration"
|
||||||
|
|
||||||
|
def get_app_list(self, request):
|
||||||
|
"""
|
||||||
|
Gruppiere Apps in logische Kategorien
|
||||||
|
"""
|
||||||
|
app_list = super().get_app_list(request)
|
||||||
|
|
||||||
|
# Definiere App-Gruppen
|
||||||
|
app_groups = {
|
||||||
|
'Shop Management': ['products', 'shop'],
|
||||||
|
'Customer Service': ['chat'],
|
||||||
|
'Analytics & ML': ['recommendations'],
|
||||||
|
'Mobile & API': ['mobile'],
|
||||||
|
'Payment & Integration': ['paypal_integration'],
|
||||||
|
}
|
||||||
|
|
||||||
|
# Gruppiere Apps
|
||||||
|
grouped_apps = {}
|
||||||
|
for app in app_list:
|
||||||
|
app_name = app['app_label']
|
||||||
|
for group, apps in app_groups.items():
|
||||||
|
if app_name in apps:
|
||||||
|
if group not in grouped_apps:
|
||||||
|
grouped_apps[group] = []
|
||||||
|
grouped_apps[group].append(app)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# Apps die nicht in Gruppen sind
|
||||||
|
if 'Other' not in grouped_apps:
|
||||||
|
grouped_apps['Other'] = []
|
||||||
|
grouped_apps['Other'].append(app)
|
||||||
|
|
||||||
|
# Flatten und sortieren
|
||||||
|
final_app_list = []
|
||||||
|
for group in sorted(grouped_apps.keys()):
|
||||||
|
if group != 'Other':
|
||||||
|
final_app_list.extend(grouped_apps[group])
|
||||||
|
if 'Other' in grouped_apps:
|
||||||
|
final_app_list.extend(grouped_apps['Other'])
|
||||||
|
|
||||||
|
return final_app_list
|
||||||
|
|
||||||
|
|
||||||
|
# Erstelle Admin Site Instanz
|
||||||
|
admin_site = KasicoAdminSite(name='kasico_admin')
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# SHOP MANAGEMENT - PRODUCTS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
@admin.register(Product, site=admin_site)
|
||||||
|
class ProductAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('name', 'price', 'stock', 'fursuit_type', 'style', 'is_featured', 'is_custom_order', 'admin_image')
|
||||||
|
list_filter = ('fursuit_type', 'style', 'is_featured', 'is_custom_order', 'created')
|
||||||
|
search_fields = ('name', 'description')
|
||||||
|
ordering = ('-created',)
|
||||||
|
list_editable = ('is_featured', 'is_custom_order')
|
||||||
|
readonly_fields = ('admin_image',)
|
||||||
|
|
||||||
|
fieldsets = (
|
||||||
|
('Grundinformationen', {
|
||||||
|
'fields': ('name', 'description', 'price', 'stock')
|
||||||
|
}),
|
||||||
|
('Fursuit-Details', {
|
||||||
|
'fields': ('fursuit_type', 'style', 'extras_description')
|
||||||
|
}),
|
||||||
|
('Status & Features', {
|
||||||
|
'fields': ('is_featured', 'is_custom_order', 'image')
|
||||||
|
}),
|
||||||
|
('Vorschau', {
|
||||||
|
'fields': ('admin_image',),
|
||||||
|
'classes': ('collapse',)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
def admin_image(self, obj):
|
||||||
|
if obj.image:
|
||||||
|
return format_html('<img src="{}" style="max-height: 50px;" />', obj.image.url)
|
||||||
|
return "Kein Bild"
|
||||||
|
admin_image.short_description = 'Vorschau'
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Category, site=admin_site)
|
||||||
|
class CategoryAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('name', 'slug', 'parent', 'product_count')
|
||||||
|
list_filter = ('parent',)
|
||||||
|
search_fields = ('name',)
|
||||||
|
prepopulated_fields = {'slug': ('name',)}
|
||||||
|
|
||||||
|
def product_count(self, obj):
|
||||||
|
return obj.products.count()
|
||||||
|
product_count.short_description = 'Produkte'
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(GalleryImage, site=admin_site)
|
||||||
|
class GalleryImageAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('title', 'fursuit_type', 'style', 'is_featured', 'order', 'created', 'admin_image')
|
||||||
|
list_filter = ('fursuit_type', 'style', 'is_featured')
|
||||||
|
search_fields = ('title', 'description')
|
||||||
|
ordering = ('order', '-created')
|
||||||
|
list_editable = ('order', 'is_featured')
|
||||||
|
readonly_fields = ('admin_image',)
|
||||||
|
|
||||||
|
def admin_image(self, obj):
|
||||||
|
if obj.image:
|
||||||
|
return format_html('<img src="{}" style="max-height: 50px;" />', obj.image.url)
|
||||||
|
return "Kein Bild"
|
||||||
|
admin_image.short_description = 'Vorschau'
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# SHOP MANAGEMENT - ORDERS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
class OrderItemInline(admin.TabularInline):
|
||||||
|
model = OrderItem
|
||||||
|
extra = 0
|
||||||
|
readonly_fields = ('product_name', 'price')
|
||||||
|
|
||||||
|
@admin.register(Order, site=admin_site)
|
||||||
|
class OrderAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('id', 'full_name', 'total_amount', 'status', 'payment_status', 'payment_method', 'created')
|
||||||
|
list_filter = ('status', 'payment_status', 'payment_method', 'created')
|
||||||
|
search_fields = ('full_name', 'email', 'id')
|
||||||
|
ordering = ('-created',)
|
||||||
|
readonly_fields = ('id', 'created', 'total_amount')
|
||||||
|
inlines = [OrderItemInline]
|
||||||
|
|
||||||
|
fieldsets = (
|
||||||
|
('Bestellinformationen', {
|
||||||
|
'fields': ('id', 'user', 'full_name', 'email', 'phone', 'address')
|
||||||
|
}),
|
||||||
|
('Zahlung & Status', {
|
||||||
|
'fields': ('status', 'payment_status', 'payment_method', 'total_amount')
|
||||||
|
}),
|
||||||
|
('Zeitstempel', {
|
||||||
|
'fields': ('created',),
|
||||||
|
'classes': ('collapse',)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
actions = ['mark_as_paid', 'mark_as_shipped']
|
||||||
|
|
||||||
|
def mark_as_paid(self, request, queryset):
|
||||||
|
updated = queryset.update(payment_status='paid')
|
||||||
|
self.message_user(request, f'{updated} Bestellungen als bezahlt markiert.')
|
||||||
|
mark_as_paid.short_description = "Als bezahlt markieren"
|
||||||
|
|
||||||
|
def mark_as_shipped(self, request, queryset):
|
||||||
|
updated = queryset.update(status='shipped')
|
||||||
|
self.message_user(request, f'{updated} Bestellungen als versendet markiert.')
|
||||||
|
mark_as_shipped.short_description = "Als versendet markieren"
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(CustomOrder, site=admin_site)
|
||||||
|
class CustomOrderAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('character_name', 'user', 'fursuit_type', 'style', 'budget_range', 'status', 'created')
|
||||||
|
list_filter = ('fursuit_type', 'style', 'status', 'budget_range', 'created')
|
||||||
|
search_fields = ('character_name', 'user__username', 'character_description')
|
||||||
|
ordering = ('-created',)
|
||||||
|
readonly_fields = ('created',)
|
||||||
|
|
||||||
|
fieldsets = (
|
||||||
|
('Character & Design', {
|
||||||
|
'fields': ('character_name', 'character_description', 'fursuit_type', 'style')
|
||||||
|
}),
|
||||||
|
('Kunde & Budget', {
|
||||||
|
'fields': ('user', 'budget_range', 'deadline_request')
|
||||||
|
}),
|
||||||
|
('Details & Dateien', {
|
||||||
|
'fields': ('color_preferences', 'special_requests', 'reference_images')
|
||||||
|
}),
|
||||||
|
('Status', {
|
||||||
|
'fields': ('status', 'created')
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(OrderProgress, site=admin_site)
|
||||||
|
class OrderProgressAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('custom_order', 'stage', 'completed', 'created')
|
||||||
|
list_filter = ('stage', 'completed', 'created')
|
||||||
|
search_fields = ('custom_order__character_name', 'description')
|
||||||
|
ordering = ('custom_order', 'created')
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# CUSTOMER SERVICE
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
@admin.register(ContactMessage, site=admin_site)
|
||||||
|
class ContactMessageAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('subject', 'name', 'email', 'category', 'status', 'created')
|
||||||
|
list_filter = ('category', 'status', 'created')
|
||||||
|
search_fields = ('name', 'email', 'subject', 'message')
|
||||||
|
ordering = ('-created',)
|
||||||
|
readonly_fields = ('created',)
|
||||||
|
|
||||||
|
fieldsets = (
|
||||||
|
('Nachricht', {
|
||||||
|
'fields': ('subject', 'name', 'email', 'category', 'message')
|
||||||
|
}),
|
||||||
|
('Status & Notizen', {
|
||||||
|
'fields': ('status', 'staff_notes')
|
||||||
|
}),
|
||||||
|
('Zeitstempel', {
|
||||||
|
'fields': ('created',),
|
||||||
|
'classes': ('collapse',)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
actions = ['mark_as_in_progress', 'mark_as_resolved']
|
||||||
|
|
||||||
|
def mark_as_in_progress(self, request, queryset):
|
||||||
|
updated = queryset.update(status='in_progress')
|
||||||
|
self.message_user(request, f'{updated} Nachrichten als in Bearbeitung markiert.')
|
||||||
|
mark_as_in_progress.short_description = "Als in Bearbeitung markieren"
|
||||||
|
|
||||||
|
def mark_as_resolved(self, request, queryset):
|
||||||
|
updated = queryset.update(status='resolved')
|
||||||
|
self.message_user(request, f'{updated} Nachrichten als erledigt markiert.')
|
||||||
|
mark_as_resolved.short_description = "Als erledigt markieren"
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(ChatRoom, site=admin_site)
|
||||||
|
class ChatRoomAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('id', 'customer', 'admin', 'status', 'subject', 'last_message_at', 'message_count')
|
||||||
|
list_filter = ('status', 'created_at')
|
||||||
|
search_fields = ('customer__username', 'admin__username', 'subject')
|
||||||
|
ordering = ('-last_message_at',)
|
||||||
|
readonly_fields = ('id', 'created_at', 'last_message_at')
|
||||||
|
|
||||||
|
def message_count(self, obj):
|
||||||
|
return obj.messages.count()
|
||||||
|
message_count.short_description = 'Nachrichten'
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(ChatMessage, site=admin_site)
|
||||||
|
class ChatMessageAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('room', 'sender', 'message_type', 'content_preview', 'created_at')
|
||||||
|
list_filter = ('message_type', 'created_at')
|
||||||
|
search_fields = ('content', 'sender__username')
|
||||||
|
ordering = ('-created_at',)
|
||||||
|
readonly_fields = ('created_at',)
|
||||||
|
|
||||||
|
def content_preview(self, obj):
|
||||||
|
return obj.content[:50] + "..." if len(obj.content) > 50 else obj.content
|
||||||
|
content_preview.short_description = 'Inhalt'
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(QuickResponse, site=admin_site)
|
||||||
|
class QuickResponseAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('title', 'category', 'is_active', 'use_count', 'created_by')
|
||||||
|
list_filter = ('category', 'is_active', 'created_at')
|
||||||
|
search_fields = ('title', 'content')
|
||||||
|
ordering = ('category', 'title')
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# ANALYTICS & ML
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
@admin.register(UserBehavior, site=admin_site)
|
||||||
|
class UserBehaviorAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('user', 'behavior_type', 'product', 'created_at')
|
||||||
|
list_filter = ('behavior_type', 'created_at')
|
||||||
|
search_fields = ('user__username', 'product__name')
|
||||||
|
ordering = ('-created_at',)
|
||||||
|
readonly_fields = ('created_at',)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Recommendation, site=admin_site)
|
||||||
|
class RecommendationAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('user', 'product', 'recommendation_type', 'confidence_score', 'is_clicked', 'is_purchased')
|
||||||
|
list_filter = ('recommendation_type', 'is_clicked', 'is_purchased', 'created_at')
|
||||||
|
search_fields = ('user__username', 'product__name')
|
||||||
|
ordering = ('-created_at',)
|
||||||
|
readonly_fields = ('created_at',)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(RecommendationModel, site=admin_site)
|
||||||
|
class RecommendationModelAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('model_name', 'model_type', 'model_version', 'accuracy_score', 'is_active', 'trained_at')
|
||||||
|
list_filter = ('model_type', 'is_active', 'trained_at')
|
||||||
|
search_fields = ('model_name',)
|
||||||
|
readonly_fields = ('created_at', 'trained_at')
|
||||||
|
|
||||||
|
fieldsets = (
|
||||||
|
('Model Info', {
|
||||||
|
'fields': ('model_type', 'model_name', 'model_version', 'model_file')
|
||||||
|
}),
|
||||||
|
('Performance', {
|
||||||
|
'fields': ('training_data_size', 'accuracy_score', 'precision_score', 'recall_score', 'f1_score')
|
||||||
|
}),
|
||||||
|
('Status', {
|
||||||
|
'fields': ('is_active', 'created_at', 'trained_at')
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# MOBILE & API
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
@admin.register(MobileDevice, site=admin_site)
|
||||||
|
class MobileDeviceAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('user', 'device_type', 'device_name', 'is_active', 'last_seen')
|
||||||
|
list_filter = ('device_type', 'is_active', 'created_at')
|
||||||
|
search_fields = ('user__username', 'device_name', 'device_token')
|
||||||
|
ordering = ('-created_at',)
|
||||||
|
readonly_fields = ('created_at', 'last_seen')
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# PAYMENT & INTEGRATION
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
@admin.register(PayPalConfig, site=admin_site)
|
||||||
|
class PayPalConfigAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('get_mode', 'client_id', 'is_sandbox', 'updated_at')
|
||||||
|
readonly_fields = ('created_at', 'updated_at')
|
||||||
|
|
||||||
|
fieldsets = (
|
||||||
|
('API Konfiguration', {
|
||||||
|
'fields': ('client_id', 'client_secret', 'is_sandbox')
|
||||||
|
}),
|
||||||
|
('Zeitstempel', {
|
||||||
|
'fields': ('created_at', 'updated_at'),
|
||||||
|
'classes': ('collapse',)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# USERS & PROFILES
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
@admin.register(UserProfile, site=admin_site)
|
||||||
|
class UserProfileAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('user', 'phone', 'newsletter', 'created')
|
||||||
|
list_filter = ('newsletter', 'created')
|
||||||
|
search_fields = ('user__username', 'phone', 'address')
|
||||||
|
ordering = ('-created',)
|
||||||
|
readonly_fields = ('created',)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Review, site=admin_site)
|
||||||
|
class ReviewAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('product', 'user', 'rating', 'created')
|
||||||
|
list_filter = ('rating', 'created')
|
||||||
|
search_fields = ('comment', 'user__username', 'product__name')
|
||||||
|
ordering = ('-created',)
|
||||||
|
readonly_fields = ('created',)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(FAQ, site=admin_site)
|
||||||
|
class FAQAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('question', 'category', 'order')
|
||||||
|
list_filter = ('category',)
|
||||||
|
search_fields = ('question', 'answer')
|
||||||
|
ordering = ('category', 'order')
|
||||||
|
list_editable = ('order',)
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# DASHBOARD & STATISTICS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
class DashboardAdmin(admin.ModelAdmin):
|
||||||
|
def get_urls(self):
|
||||||
|
urls = super().get_urls()
|
||||||
|
custom_urls = [
|
||||||
|
path('dashboard/', self.admin_site.admin_view(self.dashboard_view), name='dashboard'),
|
||||||
|
]
|
||||||
|
return custom_urls + urls
|
||||||
|
|
||||||
|
def dashboard_view(self, request):
|
||||||
|
# Dashboard Statistiken
|
||||||
|
context = {
|
||||||
|
'total_orders': Order.objects.count(),
|
||||||
|
'total_products': Product.objects.count(),
|
||||||
|
'total_users': UserProfile.objects.count(),
|
||||||
|
'recent_orders': Order.objects.order_by('-created')[:5],
|
||||||
|
'low_stock_products': Product.objects.filter(stock__lte=5),
|
||||||
|
'pending_contacts': ContactMessage.objects.filter(status='new'),
|
||||||
|
}
|
||||||
|
return render(request, 'admin/dashboard.html', context)
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# ADMIN ACTIONS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
@admin.action(description="Produkte als hervorgehoben markieren")
|
||||||
|
def mark_featured(modeladmin, request, queryset):
|
||||||
|
updated = queryset.update(is_featured=True)
|
||||||
|
modeladmin.message_user(request, f'{updated} Produkte als hervorgehoben markiert.')
|
||||||
|
|
||||||
|
|
||||||
|
@admin.action(description="Lagerbestand auf 0 setzen")
|
||||||
|
def set_stock_zero(modeladmin, request, queryset):
|
||||||
|
updated = queryset.update(stock=0)
|
||||||
|
modeladmin.message_user(request, f'{updated} Produkte auf Lagerbestand 0 gesetzt.')
|
||||||
|
|
||||||
|
|
||||||
|
# Füge Actions zu Admin-Klassen hinzu
|
||||||
|
ProductAdmin.actions = [mark_featured, set_stock_zero]
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# ADMIN SITE REGISTRATION
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# Registriere nur Modelle ohne eigene Admin-Klasse
|
||||||
|
admin_site.register(OrderItem)
|
||||||
|
admin_site.register(Cart)
|
||||||
|
admin_site.register(CartItem)
|
||||||
|
admin_site.register(ProductType)
|
||||||
|
admin_site.register(ProductImage)
|
||||||
|
admin_site.register(ProductVariant)
|
||||||
|
admin_site.register(CustomDesign)
|
||||||
|
admin_site.register(PayPalPayment)
|
||||||
|
admin_site.register(PaymentError)
|
||||||
|
admin_site.register(UserOnlineStatus)
|
||||||
|
admin_site.register(ChatAnalytics)
|
||||||
|
admin_site.register(ProductSimilarity)
|
||||||
|
admin_site.register(ABTest)
|
||||||
|
admin_site.register(RecommendationAnalytics)
|
||||||
|
admin_site.register(MobileSession)
|
||||||
|
|
@ -22,9 +22,10 @@ from django.contrib.auth import views as auth_views
|
||||||
from shop import views as shop_views
|
from shop import views as shop_views
|
||||||
from products.forms import CustomAuthenticationForm
|
from products.forms import CustomAuthenticationForm
|
||||||
from rest_framework.documentation import include_docs_urls
|
from rest_framework.documentation import include_docs_urls
|
||||||
|
from .admin import admin_site
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin_site.urls), # Neue Admin-Site
|
||||||
path('', include('shop.urls')),
|
path('', include('shop.urls')),
|
||||||
path('products/', include('products.urls')),
|
path('products/', include('products.urls')),
|
||||||
path('paypal/', include('paypal_integration.urls')),
|
path('paypal/', include('paypal_integration.urls')),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue