267 lines
9.4 KiB
Python
267 lines
9.4 KiB
Python
from django.contrib import admin
|
||
from django.utils.translation import gettext_lazy as _
|
||
from .models import (
|
||
CardTemplate, Card, CardBatch, CardUsageLog,
|
||
ClothingAttributes, PropAttributes, SongAttributes,
|
||
DanceAttributes, FurnitureAttributes, DecorationAttributes
|
||
)
|
||
from django.utils import timezone
|
||
import uuid
|
||
import secrets
|
||
import string
|
||
|
||
# Register your models here.
|
||
|
||
class ClothingAttributesInline(admin.StackedInline):
|
||
model = ClothingAttributes
|
||
can_delete = False
|
||
verbose_name = '服装属性'
|
||
verbose_name_plural = '服装属性'
|
||
|
||
class PropAttributesInline(admin.StackedInline):
|
||
model = PropAttributes
|
||
can_delete = False
|
||
verbose_name = '道具属性'
|
||
verbose_name_plural = '道具属性'
|
||
|
||
class SongAttributesInline(admin.StackedInline):
|
||
model = SongAttributes
|
||
can_delete = False
|
||
verbose_name = '音乐属性'
|
||
verbose_name_plural = '音乐属性'
|
||
|
||
class DanceAttributesInline(admin.StackedInline):
|
||
model = DanceAttributes
|
||
can_delete = False
|
||
verbose_name = '舞蹈属性'
|
||
verbose_name_plural = '舞蹈属性'
|
||
|
||
class FurnitureAttributesInline(admin.StackedInline):
|
||
model = FurnitureAttributes
|
||
can_delete = False
|
||
verbose_name = '家具属性'
|
||
verbose_name_plural = '家具属性'
|
||
|
||
class DecorationAttributesInline(admin.StackedInline):
|
||
model = DecorationAttributes
|
||
can_delete = False
|
||
verbose_name = '装饰属性'
|
||
verbose_name_plural = '装饰属性'
|
||
|
||
@admin.register(CardTemplate)
|
||
class CardTemplateAdmin(admin.ModelAdmin):
|
||
list_display = ('name', 'category', 'rarity', 'card_type', 'status', 'price', 'created_at')
|
||
list_filter = ('category', 'rarity', 'card_type', 'status')
|
||
search_fields = ('name', 'description')
|
||
ordering = ('-created_at',)
|
||
readonly_fields = ('published_at',)
|
||
|
||
def get_inlines(self, request, obj=None):
|
||
if obj is None:
|
||
return []
|
||
|
||
category_to_inline = {
|
||
'clothing': ClothingAttributesInline,
|
||
'prop': PropAttributesInline,
|
||
'song': SongAttributesInline,
|
||
'dance': DanceAttributesInline,
|
||
'furniture': FurnitureAttributesInline,
|
||
'decoration': DecorationAttributesInline,
|
||
}
|
||
|
||
if obj.category in category_to_inline:
|
||
return [category_to_inline[obj.category]]
|
||
return []
|
||
|
||
fieldsets = (
|
||
('基本信息', {
|
||
'fields': ('name', 'category', 'description')
|
||
}),
|
||
('类型和稀有度', {
|
||
'fields': ('card_type', 'rarity')
|
||
}),
|
||
('资源信息', {
|
||
'fields': ('image', 'model_url', 'model_version')
|
||
}),
|
||
('发布信息', {
|
||
'fields': ('status', 'published_at', 'price')
|
||
}),
|
||
)
|
||
|
||
actions = ['publish_templates', 'archive_templates']
|
||
|
||
class Meta:
|
||
verbose_name = '卡片模板'
|
||
verbose_name_plural = '卡片模板管理'
|
||
|
||
def publish_templates(self, request, queryset):
|
||
count = 0
|
||
for template in queryset.filter(status='draft'):
|
||
template.publish()
|
||
count += 1
|
||
self.message_user(request, f'成功发布 {count} 个卡片模板')
|
||
publish_templates.short_description = '发布选中的卡片模板'
|
||
|
||
def archive_templates(self, request, queryset):
|
||
count = 0
|
||
for template in queryset.filter(status='published'):
|
||
template.archive()
|
||
count += 1
|
||
self.message_user(request, f'成功归档 {count} 个卡片模板')
|
||
archive_templates.short_description = '归档选中的卡片模板'
|
||
|
||
|
||
class CardUsageLogInline(admin.TabularInline):
|
||
model = CardUsageLog
|
||
extra = 0
|
||
readonly_fields = ('user', 'action', 'details', 'old_status', 'new_status', 'created_at', 'ip_address')
|
||
can_delete = False
|
||
max_num = 10
|
||
verbose_name = _("使用记录")
|
||
verbose_name_plural = _("使用记录")
|
||
|
||
def has_add_permission(self, request, obj=None):
|
||
return False
|
||
|
||
|
||
@admin.register(Card)
|
||
class CardAdmin(admin.ModelAdmin):
|
||
list_display = ('unique_id', 'name', 'template', 'category', 'status', 'user', 'used_at', 'manufactured')
|
||
list_filter = ('template', 'category', 'status', 'manufactured')
|
||
search_fields = ('unique_id', 'name', 'user__username')
|
||
ordering = ('-created_at',)
|
||
readonly_fields = ('unique_id', 'manufactured_at', 'used_at', 'batch')
|
||
inlines = [CardUsageLogInline]
|
||
|
||
fieldsets = (
|
||
('基本信息', {
|
||
'fields': ('unique_id', 'template', 'name', 'category', 'description', 'price', 'batch')
|
||
}),
|
||
('状态信息', {
|
||
'fields': ('status', 'manufactured', 'manufactured_at', 'user', 'used_at')
|
||
}),
|
||
)
|
||
|
||
class Meta:
|
||
verbose_name = '卡片'
|
||
verbose_name_plural = '卡片管理'
|
||
|
||
def save_model(self, request, obj, form, change):
|
||
# Log changes to the card
|
||
if change: # If this is an update
|
||
old_obj = self.model.objects.get(pk=obj.pk)
|
||
if old_obj.status != obj.status:
|
||
CardUsageLog.objects.create(
|
||
card=obj,
|
||
user=request.user if hasattr(request, 'user') else None,
|
||
action='update',
|
||
details=f"状态从 {old_obj.get_status_display()} 变更为 {obj.get_status_display()}",
|
||
old_status=old_obj.status,
|
||
new_status=obj.status,
|
||
ip_address=self.get_client_ip(request)
|
||
)
|
||
else: # If this is a new card
|
||
# 如果是新卡片且没有unique_id,生成一个
|
||
if not obj.unique_id:
|
||
obj.unique_id = self.generate_unique_id()
|
||
|
||
CardUsageLog.objects.create(
|
||
card=obj,
|
||
user=request.user if hasattr(request, 'user') else None,
|
||
action='create',
|
||
details="卡片创建",
|
||
old_status='',
|
||
new_status=obj.status,
|
||
ip_address=self.get_client_ip(request)
|
||
)
|
||
|
||
super().save_model(request, obj, form, change)
|
||
|
||
def get_client_ip(self, request):
|
||
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
|
||
if x_forwarded_for:
|
||
ip = x_forwarded_for.split(',')[0]
|
||
else:
|
||
ip = request.META.get('REMOTE_ADDR')
|
||
return ip
|
||
|
||
def generate_unique_id(self, length=16):
|
||
"""
|
||
生成一个唯一、不可预测的卡片标识
|
||
"""
|
||
alphabet = string.ascii_letters + string.digits
|
||
while True:
|
||
# 生成一个随机字符串
|
||
unique_id = ''.join(secrets.choice(alphabet) for _ in range(length))
|
||
|
||
# 确保唯一
|
||
if not Card.objects.filter(unique_id=unique_id).exists():
|
||
return unique_id
|
||
|
||
|
||
@admin.register(CardBatch)
|
||
class CardBatchAdmin(admin.ModelAdmin):
|
||
list_display = ('batch_number', 'template', 'category', 'quantity', 'status', 'exported', 'sent_to_production', 'published')
|
||
list_filter = ('template', 'category', 'status', 'exported', 'sent_to_production', 'published')
|
||
search_fields = ('batch_number', 'description')
|
||
ordering = ('-created_at',)
|
||
readonly_fields = ('batch_number', 'exported_at', 'production_date', 'published_at')
|
||
|
||
fieldsets = (
|
||
('基本信息', {
|
||
'fields': ('batch_number', 'template', 'category', 'quantity', 'description')
|
||
}),
|
||
('状态信息', {
|
||
'fields': ('status', 'exported', 'exported_at', 'sent_to_production', 'production_date', 'published', 'published_at', 'excel_file')
|
||
}),
|
||
)
|
||
|
||
actions = ['mark_batches_manufactured', 'publish_batches']
|
||
|
||
class Meta:
|
||
verbose_name = '卡片批次'
|
||
verbose_name_plural = '卡片批次管理'
|
||
|
||
def save_model(self, request, obj, form, change):
|
||
if not change: # If this is a new batch
|
||
# Generate batch number if not provided
|
||
if not obj.batch_number:
|
||
obj.batch_number = f"B{timezone.now().strftime('%Y%m%d%H%M%S')}"
|
||
|
||
super().save_model(request, obj, form, change)
|
||
|
||
def mark_batches_manufactured(self, request, queryset):
|
||
count = 0
|
||
for batch in queryset.filter(sent_to_production=False, exported=True):
|
||
batch.mark_produced()
|
||
count += 1
|
||
self.message_user(request, f'成功标记 {count} 个批次为已生产')
|
||
mark_batches_manufactured.short_description = '标记选中的批次为已生产'
|
||
|
||
def publish_batches(self, request, queryset):
|
||
count = 0
|
||
for batch in queryset.filter(published=False, sent_to_production=True):
|
||
batch.publish()
|
||
count += 1
|
||
self.message_user(request, f'成功发布 {count} 个批次')
|
||
publish_batches.short_description = '发布选中的批次'
|
||
|
||
|
||
@admin.register(CardUsageLog)
|
||
class CardUsageLogAdmin(admin.ModelAdmin):
|
||
list_display = ('card', 'user', 'action', 'created_at', 'ip_address')
|
||
list_filter = ('action', 'created_at')
|
||
search_fields = ('card__unique_id', 'user__username', 'ip_address')
|
||
ordering = ('-created_at',)
|
||
readonly_fields = ('card', 'user', 'action', 'details', 'old_status', 'new_status', 'ip_address', 'created_at')
|
||
|
||
class Meta:
|
||
verbose_name = '卡片使用记录'
|
||
verbose_name_plural = '卡片使用记录管理'
|
||
|
||
def has_add_permission(self, request):
|
||
return False
|
||
|
||
def has_change_permission(self, request, obj=None):
|
||
return False
|