init commit

This commit is contained in:
2025-06-13 21:10:20 +09:00
commit d52c611afb
269 changed files with 37162 additions and 0 deletions

479
lottery/draw/admin.py Normal file
View File

@@ -0,0 +1,479 @@
# import random
# import logging
# from asgiref.sync import async_to_sync
# from django.contrib import admin, messages
# from django.urls import path, reverse
# from django.shortcuts import render, redirect, get_object_or_404
# from django.utils import timezone
# from django.http import HttpResponseRedirect, HttpResponse
# from django.utils.html import format_html
# from .models import Lottery, Prize, LotteryParticipant, DrawResult
# from .forms import AddParticipantsForm
# from webapp.models import Invoice, Client, BindingRequest
# from bot.notifications import NotificationService
# from bot.utils import create_bot_instance
# # Настройка логгера
# logger = logging.getLogger(__name__)
# logger.setLevel(logging.DEBUG)
# if not logger.handlers:
# console_handler = logging.StreamHandler()
# console_handler.setLevel(logging.DEBUG)
# formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
# console_handler.setFormatter(formatter)
# logger.addHandler(console_handler)
# def add_participants_view(request):
# lottery_id = request.GET.get("lottery_id")
# if not lottery_id:
# return HttpResponse("Не указан параметр lottery_id", status=400)
# lottery = get_object_or_404(Lottery, id=lottery_id)
# used_invoice_ids = LotteryParticipant.objects.filter(lottery=lottery).values_list("invoice_id", flat=True)
# qs = Invoice.objects.exclude(id__in=used_invoice_ids)
# deposit_min = request.GET.get("deposit_min")
# deposit_max = request.GET.get("deposit_max")
# if deposit_min:
# qs = qs.filter(deposit_sum__gte=deposit_min)
# if deposit_max:
# qs = qs.filter(deposit_sum__lte=deposit_max)
# if request.method == "POST":
# form = AddParticipantsForm(request.POST)
# form.fields["invoices"].queryset = qs
# if form.is_valid():
# selected_invoices = form.cleaned_data["invoices"]
# for invoice in selected_invoices:
# LotteryParticipant.objects.create(lottery=lottery, invoice=invoice)
# messages.success(request, "Участники успешно добавлены.")
# return redirect("admin:draw_lotteryparticipant_changelist")
# else:
# form = AddParticipantsForm()
# form.fields["invoices"].queryset = qs
# context = {"form": form, "lottery": lottery}
# return render(request, "admin/add_participants.html", context)
# def get_client_by_invoice(invoice):
# """
# Возвращает клиента, используя значение поля client_club_card_number у счета.
# Предполагается, что у модели Invoice есть поле client_club_card_number.
# """
# try:
# return Client.objects.get(club_card_number=invoice.client_club_card_number)
# except Client.DoesNotExist:
# return None
# def start_draw(request, lottery_id):
# lottery = get_object_or_404(Lottery, id=lottery_id)
# logger.info("Запуск розыгрыша для лотереи: %s", lottery.name)
# notifier = NotificationService(bot=create_bot_instance())
# # Используем async_to_sync для уведомления о запуске розыгрыша
# async_to_sync(notifier.notify_draw_start)(lottery)
# for prize in lottery.prizes.all():
# logger.info("Обработка приза: %s", prize.prize_place)
# # Если для приза уже назначен победитель вручную, сохраняем его в таблице результатов
# if prize.winner:
# logger.info("Приз '%s' имеет установленного вручную победителя. Сохраняем в таблице результатов.", prize.prize_place)
# try:
# draw_result = lottery.draw_results.get(prize=prize)
# draw_result.participant = prize.winner
# draw_result.drawn_at = timezone.now()
# draw_result.confirmed = False
# draw_result.save()
# except DrawResult.DoesNotExist:
# DrawResult.objects.create(
# lottery=lottery,
# prize=prize,
# participant=prize.winner,
# confirmed=False,
# drawn_at=timezone.now()
# )
# continue
# try:
# draw_result = lottery.draw_results.get(prize=prize)
# if draw_result.confirmed:
# logger.info("Приз '%s' уже подтвержден.", prize.prize_place)
# continue
# except DrawResult.DoesNotExist:
# draw_result = None
# participants = list(lottery.participants.filter(used=False))
# logger.info("Найдено свободных участников для приза '%s': %d", prize.prize_place, len(participants))
# if not participants:
# logger.warning("Нет свободных участников для приза '%s'.", prize.prize_place)
# continue
# winner_participant = random.choice(participants)
# winner_participant.used = True
# winner_participant.save()
# logger.info("Выбран участник с счетом '%s' для приза '%s'.", winner_participant.invoice.api_id, prize.prize_place)
# if draw_result:
# draw_result.participant = winner_participant
# draw_result.drawn_at = timezone.now()
# draw_result.confirmed = False
# draw_result.save()
# logger.info("Обновлен результат розыгрыша для приза '%s'.", prize.prize_place)
# else:
# DrawResult.objects.create(
# lottery=lottery,
# prize=prize,
# participant=winner_participant,
# confirmed=False,
# drawn_at=timezone.now()
# )
# logger.info("Создан результат розыгрыша для приза '%s'.", prize.prize_place)
# draw_results = lottery.draw_results.all()
# async_to_sync(notifier.notify_draw_results)(lottery, draw_results)
# return render(request, "admin/draw_result.html", {"lottery": lottery, "draw_results": draw_results})
# def confirm_draw_result(request, result_id):
# from django.http import HttpResponseRedirect
# result = get_object_or_404(DrawResult, id=result_id)
# result.confirmed = True
# result.save()
# prize = result.prize
# prize.winner = result.participant
# prize.save()
# logger.info("Подтвержден результат розыгрыша для приза '%s'.", prize.prize_place)
# messages.success(request, f"Результат для приза '{prize.prize_place}' подтвержден.")
# # Получаем клиента по счету участника
# client = get_client_by_invoice(result.participant.invoice)
# notifier = NotificationService(bot=create_bot_instance())
# if client:
# async_to_sync(notifier.notify_prize_status_update)(client, result)
# return HttpResponseRedirect(reverse("admin:start_draw", args=[result.lottery.id]))
# @admin.register(Lottery)
# class LotteryAdmin(admin.ModelAdmin):
# list_display = ("name", "description", "created_at", "start_draw_button")
# search_fields = ("name", "description")
# def get_urls(self):
# urls = super().get_urls()
# custom_urls = [
# path('<int:lottery_id>/start-draw/', self.admin_site.admin_view(start_draw), name='start_draw'),
# path('confirm-draw-result/<int:result_id>/', self.admin_site.admin_view(confirm_draw_result), name='confirm_draw_result'),
# ]
# return custom_urls + urls
# def start_draw_button(self, obj):
# url = reverse("admin:start_draw", args=[obj.pk])
# return format_html('<a class="btn btn-primary" href="{}">Запуск розыгрыша</a>', url)
# start_draw_button.short_description = "Розыгрыш"
# @admin.register(Prize)
# class PrizeAdmin(admin.ModelAdmin):
# list_display = ("lottery", "prize_place", "reward", "winner")
# list_filter = ("lottery",)
# search_fields = ("prize_place", "description")
# @admin.register(LotteryParticipant)
# class LotteryParticipantAdmin(admin.ModelAdmin):
# list_display = ("lottery", "invoice", "added_at", "used")
# list_filter = ("lottery", "used")
# search_fields = ("invoice__api_id", "lottery__name")
# change_list_template = "admin/draw/lotteryparticipant/change_list.html"
# def changelist_view(self, request, extra_context=None):
# extra_context = extra_context or {}
# from draw.models import Lottery
# active_lotteries = Lottery.objects.filter(prizes__winner__isnull=True).distinct()
# extra_context['active_lotteries'] = active_lotteries
# return super().changelist_view(request, extra_context=extra_context)
# def get_urls(self):
# from django.urls import path
# urls = super().get_urls()
# custom_urls = [
# path('add-participants/', self.admin_site.admin_view(add_participants_view), name="add_participants"),
# ]
# return custom_urls + urls
# @admin.register(DrawResult)
# class DrawResultAdmin(admin.ModelAdmin):
# list_display = ("lottery", "prize", "participant", "confirmed", "drawn_at")
# list_filter = ("lottery", "confirmed")
# search_fields = ("prize__prize_place", "participant__invoice__api_id")
import random
import logging
from asgiref.sync import async_to_sync
from django.contrib import admin, messages
from django.urls import path, reverse
from django.shortcuts import render, redirect, get_object_or_404
from django.utils import timezone
from django.http import HttpResponseRedirect, HttpResponse
from django.utils.html import format_html
from .models import Lottery, Prize, LotteryParticipant, DrawResult
from .forms import AddParticipantsForm
from webapp.models import Invoice, Client, BindingRequest
from bot.notifications import NotificationService
from bot.utils import create_bot_instance
# Настройка логгера
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
if not logger.handlers:
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
console_handler.setFormatter(formatter)
logger.addHandler(console_handler)
def add_participants_view(request):
lottery_id = request.GET.get("lottery_id")
if not lottery_id:
return HttpResponse("Не указан параметр lottery_id", status=400)
lottery = get_object_or_404(Lottery, id=lottery_id)
# Только счета, у которых used=False, и не добавленные в данную лотерею
used_invoice_ids = LotteryParticipant.objects.filter(lottery=lottery).values_list("invoice_id", flat=True)
qs = Invoice.objects.filter(used=False).exclude(id__in=used_invoice_ids)
deposit_min = request.GET.get("deposit_min")
deposit_max = request.GET.get("deposit_max")
if deposit_min:
qs = qs.filter(deposit_sum__gte=deposit_min)
if deposit_max:
qs = qs.filter(deposit_sum__lte=deposit_max)
if request.method == "POST":
form = AddParticipantsForm(request.POST)
form.fields["invoices"].queryset = qs
if form.is_valid():
selected_invoices = form.cleaned_data["invoices"]
for invoice in selected_invoices:
# При добавлении участника отмечаем счет как использованный
invoice.used = True
invoice.save()
LotteryParticipant.objects.create(lottery=lottery, invoice=invoice)
messages.success(request, "Участники успешно добавлены.")
return redirect("admin:draw_lotteryparticipant_changelist")
else:
form = AddParticipantsForm()
form.fields["invoices"].queryset = qs
context = {"form": form, "lottery": lottery}
return render(request, "admin/add_participants.html", context)
def get_client_by_invoice(invoice):
"""
Возвращает клиента, используя значение поля client_club_card_number у счета.
Предполагается, что у модели Invoice есть поле client_club_card_number.
"""
try:
return Client.objects.get(club_card_number=invoice.client_club_card_number)
except Client.DoesNotExist:
return None
def start_draw(request, lottery_id):
lottery = get_object_or_404(Lottery, id=lottery_id)
logger.info("Запуск розыгрыша для лотереи: %s", lottery.name)
# Если лотерея уже завершена, кнопку запускать не показываем
if lottery.finished:
messages.warning(request, "Розыгрыш уже завершён.")
return redirect("..")
notifier = NotificationService(bot=create_bot_instance())
async_to_sync(notifier.notify_draw_start)(lottery)
for prize in lottery.prizes.all():
logger.info("Обработка приза: %s", prize.prize_place)
# Если для приза уже назначен победитель вручную, сохраняем его в таблице результатов
if prize.winner:
logger.info("Приз '%s' имеет установленного вручную победителя. Сохраняем в таблице результатов.", prize.prize_place)
try:
draw_result = lottery.draw_results.get(prize=prize)
draw_result.participant = prize.winner
draw_result.drawn_at = timezone.now()
draw_result.confirmed = False
draw_result.save()
except DrawResult.DoesNotExist:
DrawResult.objects.create(
lottery=lottery,
prize=prize,
participant=prize.winner,
confirmed=False,
drawn_at=timezone.now()
)
continue
try:
draw_result = lottery.draw_results.get(prize=prize)
if draw_result.confirmed:
logger.info("Приз '%s' уже подтвержден.", prize.prize_place)
continue
except DrawResult.DoesNotExist:
draw_result = None
participants = list(lottery.participants.filter(used=False))
logger.info("Найдено свободных участников для приза '%s': %d", prize.prize_place, len(participants))
if not participants:
logger.warning("Нет свободных участников для приза '%s'.", prize.prize_place)
continue
winner_participant = random.choice(participants)
winner_participant.used = True
winner_participant.save()
logger.info("Выбран участник с счетом '%s' для приза '%s'.", winner_participant.invoice.api_id, prize.prize_place)
if draw_result:
draw_result.participant = winner_participant
draw_result.drawn_at = timezone.now()
draw_result.confirmed = False
draw_result.save()
logger.info("Обновлен результат розыгрыша для приза '%s'.", prize.prize_place)
else:
DrawResult.objects.create(
lottery=lottery,
prize=prize,
participant=winner_participant,
confirmed=False,
drawn_at=timezone.now()
)
logger.info("Создан результат розыгрыша для приза '%s'.", prize.prize_place)
draw_results = lottery.draw_results.all()
async_to_sync(notifier.notify_draw_results)(lottery, draw_results)
# Если все призы розыгрыша подтверждены, устанавливаем флаг завершения лотереи
if not lottery.prizes.filter(winner__isnull=True).exists():
lottery.finished = True
lottery.save()
return render(request, "admin/draw_result.html", {"lottery": lottery, "draw_results": draw_results})
def confirm_draw_result(request, result_id):
from django.http import HttpResponseRedirect
result = get_object_or_404(DrawResult, id=result_id)
# Проверяем, что для результата задан участник и его счет
if not result.participant or not result.participant.invoice:
messages.error(request, "Невозможно подтвердить результат: отсутствует участник или его счет.")
return HttpResponseRedirect(reverse("admin:start_draw", args=[result.lottery.id]))
result.confirmed = True
result.save()
prize = result.prize
prize.winner = result.participant
prize.save()
logger.info("Подтвержден результат розыгрыша для приза '%s'.", prize.prize_place)
messages.success(request, f"Результат для приза '{prize.prize_place}' подтвержден.")
# Получаем клиента по счету участника
client = get_client_by_invoice(result.participant.invoice)
# Если уведомление вызывает ошибки, можно временно его отключить,
# чтобы проверить базовую функциональность подтверждения.
if client:
try:
# Можно попробовать запуск уведомления в отдельном потоке или отключить его временно:
async_to_sync(NotificationService(bot=create_bot_instance()).notify_prize_status_update)(client, result)
except Exception as e:
logger.error(f"Ошибка отправки уведомления о статусе приза пользователю {client.telegram_id}: {e}")
return HttpResponseRedirect(reverse("admin:start_draw", args=[result.lottery.id]))
@admin.register(Lottery)
class LotteryAdmin(admin.ModelAdmin):
list_display = ("name", "description", "created_at", "finished", "start_draw_button")
search_fields = ("name", "description")
def get_urls(self):
urls = super().get_urls()
custom_urls = [
path('<int:lottery_id>/start-draw/', self.admin_site.admin_view(start_draw), name='start_draw'),
path('confirm-draw-result/<int:result_id>/', self.admin_site.admin_view(confirm_draw_result), name='confirm_draw_result'),
]
return custom_urls + urls
def start_draw_button(self, obj):
# Если лотерея завершена, кнопку скрываем
if obj.finished:
return ""
url = reverse("admin:start_draw", args=[obj.pk])
return format_html('<a class="btn btn-primary" href="{}">Запуск розыгрыша</a>', url)
start_draw_button.short_description = "Розыгрыш"
@admin.register(Prize)
class PrizeAdmin(admin.ModelAdmin):
list_display = ("lottery", "prize_place", "reward", "winner")
list_filter = ("lottery",)
search_fields = ("prize_place", "description")
def get_form(self, request, obj=None, **kwargs):
form = super().get_form(request, obj, **kwargs)
# При создании нового приза поле winner скрываем, так как участников ещё нет
if obj is None:
form.base_fields.pop('winner', None)
return form
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "winner" and request.resolver_match.kwargs.get('object_id'):
obj_id = request.resolver_match.kwargs.get('object_id')
try:
prize_obj = self.model.objects.get(pk=obj_id)
# Ограничиваем выбор участниками данной лотереи
kwargs["queryset"] = prize_obj.lottery.participants.all()
except self.model.DoesNotExist:
kwargs["queryset"] = self.model.objects.none()
return super().formfield_for_foreignkey(db_field, request, **kwargs)
@admin.register(LotteryParticipant)
class LotteryParticipantAdmin(admin.ModelAdmin):
list_display = ("lottery", "invoice", "added_at", "used")
list_filter = ("lottery", "used")
search_fields = ("invoice__api_id", "lottery__name")
change_list_template = "admin/draw/lotteryparticipant/change_list.html"
def changelist_view(self, request, extra_context=None):
extra_context = extra_context or {}
from draw.models import Lottery
active_lotteries = Lottery.objects.filter(prizes__winner__isnull=True).distinct()
extra_context['active_lotteries'] = active_lotteries
return super().changelist_view(request, extra_context=extra_context)
def get_urls(self):
from django.urls import path
urls = super().get_urls()
custom_urls = [
path('add-participants/', self.admin_site.admin_view(add_participants_view), name="add_participants"),
]
return custom_urls + urls
@admin.register(DrawResult)
class DrawResultAdmin(admin.ModelAdmin):
list_display = ("lottery", "prize", "participant", "confirmed", "drawn_at")
list_filter = ("lottery", "confirmed")
search_fields = ("prize__prize_place", "participant__invoice__api_id")