342 lines
11 KiB
Python
342 lines
11 KiB
Python
"""
|
||
Админ-панель для реферальной системы.
|
||
"""
|
||
from django.contrib import admin
|
||
from django.utils.html import format_html
|
||
from .models import (
|
||
ReferralSettings,
|
||
ReferralLevel,
|
||
UserReferralProfile,
|
||
BonusAccount,
|
||
ReferralEarning,
|
||
PointsTransaction,
|
||
BonusTransaction,
|
||
PromoCode,
|
||
PromoCodeUsage
|
||
)
|
||
|
||
|
||
@admin.register(ReferralSettings)
|
||
class ReferralSettingsAdmin(admin.ModelAdmin):
|
||
"""Админ для настроек реферальной программы."""
|
||
|
||
list_display = [
|
||
'level1_commission',
|
||
'level2_commission',
|
||
'points_direct_referral',
|
||
'points_indirect_referral',
|
||
'updated_at'
|
||
]
|
||
|
||
fieldsets = (
|
||
('Комиссии', {
|
||
'fields': ('level1_commission', 'level2_commission')
|
||
}),
|
||
('Очки', {
|
||
'fields': ('points_direct_referral', 'points_indirect_referral')
|
||
}),
|
||
)
|
||
|
||
|
||
@admin.register(ReferralLevel)
|
||
class ReferralLevelAdmin(admin.ModelAdmin):
|
||
"""Админ для уровней реферальной программы."""
|
||
|
||
list_display = ['level', 'name', 'points_required', 'bonus_payment_percent', 'icon']
|
||
list_editable = ['bonus_payment_percent']
|
||
ordering = ['level']
|
||
|
||
|
||
@admin.register(UserReferralProfile)
|
||
class UserReferralProfileAdmin(admin.ModelAdmin):
|
||
"""Админ для реферальных профилей."""
|
||
|
||
list_display = [
|
||
'user_email',
|
||
'referral_code',
|
||
'referred_by_email',
|
||
'current_level',
|
||
'total_points',
|
||
'direct_referrals_count',
|
||
'total_earned',
|
||
'created_at'
|
||
]
|
||
|
||
list_filter = ['current_level', 'created_at']
|
||
search_fields = ['user__email', 'referral_code', 'referred_by__email']
|
||
readonly_fields = [
|
||
'referral_code',
|
||
'total_points',
|
||
'direct_referrals_count',
|
||
'indirect_referrals_count',
|
||
'total_earned',
|
||
'created_at',
|
||
'updated_at',
|
||
'referral_link'
|
||
]
|
||
|
||
fieldsets = (
|
||
('Основная информация', {
|
||
'fields': ('user', 'referral_code', 'referred_by', 'referral_link')
|
||
}),
|
||
('Уровень и очки', {
|
||
'fields': ('current_level', 'total_points')
|
||
}),
|
||
('Статистика', {
|
||
'fields': (
|
||
'direct_referrals_count',
|
||
'indirect_referrals_count',
|
||
'total_earned'
|
||
)
|
||
}),
|
||
('Даты', {
|
||
'fields': ('created_at', 'updated_at')
|
||
}),
|
||
)
|
||
|
||
def user_email(self, obj):
|
||
return obj.user.email
|
||
user_email.short_description = 'Email'
|
||
|
||
def referred_by_email(self, obj):
|
||
return obj.referred_by.email if obj.referred_by else '-'
|
||
referred_by_email.short_description = 'Пригласил'
|
||
|
||
def referral_link(self, obj):
|
||
link = obj.get_referral_link()
|
||
return format_html('<a href="{}" target="_blank">{}</a>', link, link)
|
||
referral_link.short_description = 'Реферальная ссылка'
|
||
|
||
|
||
@admin.register(BonusAccount)
|
||
class BonusAccountAdmin(admin.ModelAdmin):
|
||
"""Админ для бонусных счетов."""
|
||
|
||
list_display = [
|
||
'user_email',
|
||
'balance',
|
||
'total_earned',
|
||
'total_spent',
|
||
'updated_at'
|
||
]
|
||
|
||
list_editable = ['balance']
|
||
|
||
search_fields = ['user__email']
|
||
readonly_fields = ['created_at', 'updated_at']
|
||
|
||
fieldsets = (
|
||
('Основная информация', {
|
||
'fields': ('user',)
|
||
}),
|
||
('Баланс и статистика', {
|
||
'fields': ('balance', 'total_earned', 'total_spent'),
|
||
'description': 'Внимание: изменение баланса напрямую не создает транзакцию в истории. '
|
||
'Для корректного учета используйте методы add_bonus() и spend_bonus() модели.'
|
||
}),
|
||
('Временные метки', {
|
||
'fields': ('created_at', 'updated_at'),
|
||
'classes': ('collapse',)
|
||
}),
|
||
)
|
||
|
||
def user_email(self, obj):
|
||
return obj.user.email
|
||
user_email.short_description = 'Email'
|
||
|
||
def save_model(self, request, obj, form, change):
|
||
"""
|
||
Сохранить модель с логированием изменений.
|
||
"""
|
||
if change:
|
||
# Получаем старые значения для логирования
|
||
old_obj = BonusAccount.objects.get(pk=obj.pk)
|
||
|
||
# Логируем изменения баланса
|
||
if old_obj.balance != obj.balance:
|
||
from .models import BonusTransaction
|
||
from decimal import Decimal
|
||
|
||
diff = obj.balance - old_obj.balance
|
||
if diff != 0:
|
||
transaction_type = 'earn' if diff > 0 else 'spend'
|
||
BonusTransaction.objects.create(
|
||
user=obj.user,
|
||
amount=abs(diff),
|
||
transaction_type=transaction_type,
|
||
reason=f'Ручное изменение баланса администратором {request.user.email}',
|
||
balance_after=obj.balance
|
||
)
|
||
|
||
# Обновляем статистику
|
||
if diff > 0:
|
||
obj.total_earned += diff
|
||
else:
|
||
obj.total_spent += abs(diff)
|
||
|
||
# Логируем изменения статистики (если изменены напрямую)
|
||
if old_obj.total_earned != obj.total_earned or old_obj.total_spent != obj.total_spent:
|
||
# Если статистика изменена напрямую, просто сохраняем
|
||
pass
|
||
|
||
super().save_model(request, obj, form, change)
|
||
|
||
|
||
@admin.register(ReferralEarning)
|
||
class ReferralEarningAdmin(admin.ModelAdmin):
|
||
"""Админ для заработков с рефералов."""
|
||
|
||
list_display = [
|
||
'referrer_email',
|
||
'referral_email',
|
||
'level',
|
||
'payment_amount',
|
||
'commission_percent',
|
||
'earned_amount',
|
||
'created_at'
|
||
]
|
||
|
||
list_filter = ['level', 'created_at']
|
||
search_fields = ['referrer__email', 'referral__email']
|
||
readonly_fields = [
|
||
'referrer',
|
||
'referral',
|
||
'payment',
|
||
'level',
|
||
'payment_amount',
|
||
'commission_percent',
|
||
'earned_amount',
|
||
'created_at'
|
||
]
|
||
|
||
def referrer_email(self, obj):
|
||
return obj.referrer.email
|
||
referrer_email.short_description = 'Реферер'
|
||
|
||
def referral_email(self, obj):
|
||
return obj.referral.email
|
||
referral_email.short_description = 'Реферал'
|
||
|
||
|
||
@admin.register(PointsTransaction)
|
||
class PointsTransactionAdmin(admin.ModelAdmin):
|
||
"""Админ для транзакций очков."""
|
||
|
||
list_display = ['user_email', 'points', 'reason', 'balance_after', 'created_at']
|
||
list_filter = ['created_at']
|
||
search_fields = ['user__email', 'reason']
|
||
readonly_fields = ['user', 'points', 'reason', 'balance_after', 'created_at']
|
||
|
||
def user_email(self, obj):
|
||
return obj.user.email
|
||
user_email.short_description = 'Email'
|
||
|
||
|
||
@admin.register(BonusTransaction)
|
||
class BonusTransactionAdmin(admin.ModelAdmin):
|
||
"""Админ для транзакций бонусов."""
|
||
|
||
list_display = [
|
||
'user_email',
|
||
'amount',
|
||
'transaction_type',
|
||
'reason',
|
||
'balance_after',
|
||
'created_at'
|
||
]
|
||
|
||
list_filter = ['transaction_type', 'created_at']
|
||
search_fields = ['user__email', 'reason']
|
||
readonly_fields = ['user', 'amount', 'transaction_type', 'reason', 'balance_after', 'created_at']
|
||
|
||
def user_email(self, obj):
|
||
return obj.user.email
|
||
user_email.short_description = 'Email'
|
||
|
||
|
||
@admin.register(PromoCode)
|
||
class PromoCodeAdmin(admin.ModelAdmin):
|
||
"""Админ для промокодов."""
|
||
|
||
list_display = [
|
||
'code',
|
||
'name',
|
||
'discount_display',
|
||
'current_uses',
|
||
'max_uses',
|
||
'valid_until',
|
||
'is_active',
|
||
'created_at'
|
||
]
|
||
|
||
list_filter = ['is_active', 'discount_type', 'created_at']
|
||
search_fields = ['code', 'name', 'description']
|
||
filter_horizontal = ['applicable_plans']
|
||
|
||
fieldsets = (
|
||
('Основная информация', {
|
||
'fields': ('code', 'name', 'description')
|
||
}),
|
||
('Скидка', {
|
||
'fields': ('discount_type', 'discount_value')
|
||
}),
|
||
('Применимость', {
|
||
'fields': ('applicable_plans',)
|
||
}),
|
||
('Ограничения', {
|
||
'fields': ('max_uses', 'current_uses', 'valid_until', 'is_active')
|
||
}),
|
||
('Метаданные', {
|
||
'fields': ('created_by', 'created_at', 'updated_at'),
|
||
'classes': ('collapse',)
|
||
}),
|
||
)
|
||
|
||
readonly_fields = ['current_uses', 'created_at', 'updated_at']
|
||
|
||
def discount_display(self, obj):
|
||
if obj.discount_type == 'percent':
|
||
return f'{obj.discount_value}%'
|
||
return f'{obj.discount_value} ₽'
|
||
discount_display.short_description = 'Скидка'
|
||
|
||
def save_model(self, request, obj, form, change):
|
||
if not change:
|
||
obj.created_by = request.user
|
||
super().save_model(request, obj, form, change)
|
||
|
||
|
||
@admin.register(PromoCodeUsage)
|
||
class PromoCodeUsageAdmin(admin.ModelAdmin):
|
||
"""Админ для использований промокодов."""
|
||
|
||
list_display = [
|
||
'user_email',
|
||
'promo_code_code',
|
||
'original_amount',
|
||
'discount_amount',
|
||
'final_amount',
|
||
'created_at'
|
||
]
|
||
|
||
list_filter = ['created_at', 'promo_code']
|
||
search_fields = ['user__email', 'promo_code__code']
|
||
readonly_fields = [
|
||
'user',
|
||
'promo_code',
|
||
'payment',
|
||
'original_amount',
|
||
'discount_amount',
|
||
'final_amount',
|
||
'created_at'
|
||
]
|
||
|
||
def user_email(self, obj):
|
||
return obj.user.email
|
||
user_email.short_description = 'Email'
|
||
|
||
def promo_code_code(self, obj):
|
||
return obj.promo_code.code
|
||
promo_code_code.short_description = 'Промокод'
|
||
|