import random import logging from asgiref.sync import async_to_sync from datetime import datetime from django import forms 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.utils.dateparse import parse_date from django.utils.html import format_html from django.http import HttpResponseRedirect, HttpResponse 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 from .views import view_draw_results import os 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.filter(used=False).exclude(id__in=used_invoice_ids) deposit_min = request.GET.get("deposit_min") deposit_max = request.GET.get("deposit_max") created_after = request.GET.get("created_after") created_before = request.GET.get("created_before") if deposit_min: qs = qs.filter(deposit_sum__gte=deposit_min) if deposit_max: qs = qs.filter(deposit_sum__lte=deposit_max) if created_after: try: dt = parse_date(created_after) if dt: qs = qs.filter(created_at__gte=dt) except ValueError: pass if created_before: try: dt = parse_date(created_before) if dt: qs = qs.filter(created_at__lte=dt) except ValueError: pass 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): 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) # manually_assigned_invoice_ids = set() # for prize in lottery.prizes.all(): # if prize.winner and prize.winner.invoice: # manually_assigned_invoice_ids.add(prize.winner.invoice_id) # prize.winner.used = True # prize.winner.save() # for prize in lottery.prizes.all(): # logger.info("Обработка приза: %s", prize.prize_place) # if prize.winner: # 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: # continue # except DrawResult.DoesNotExist: # draw_result = None # participants = list( # lottery.participants.filter(used=False).exclude(invoice_id__in=manually_assigned_invoice_ids) # ) # if not participants: # continue # winner_participant = random.choice(participants) # winner_participant.used = True # winner_participant.save() # if draw_result: # draw_result.participant = winner_participant # draw_result.drawn_at = timezone.now() # draw_result.confirmed = False # draw_result.save() # else: # DrawResult.objects.create( # lottery=lottery, # prize=prize, # participant=winner_participant, # confirmed=False, # drawn_at=timezone.now() # ) # 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 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()) manually_assigned_invoice_ids = set() for prize in lottery.prizes.all(): if prize.winner and prize.winner.invoice: manually_assigned_invoice_ids.add(prize.winner.invoice_id) prize.winner.used = True prize.winner.save() for prize in lottery.prizes.all(): logger.info("Обработка приза: %s", prize.prize_place) if prize.winner: 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: continue except DrawResult.DoesNotExist: draw_result = None participants = list( lottery.participants.filter(used=False).exclude(invoice_id__in=manually_assigned_invoice_ids) ) logger.info(f"Найдено участников: {len(participants)} для приза {prize.prize_place}") if not participants: continue winner_participant = random.choice(participants) winner_participant.used = True winner_participant.save() if draw_result: draw_result.participant = winner_participant draw_result.drawn_at = timezone.now() draw_result.confirmed = False draw_result.save() else: DrawResult.objects.create( lottery=lottery, prize=prize, participant=winner_participant, confirmed=False, drawn_at=timezone.now() ) draw_results = lottery.draw_results.select_related('prize', 'participant__invoice') # 🧠 Считаем сумму призов и формируем список победителей total_reward = sum([r.prize.reward for r in draw_results if r.prize and r.prize.reward]) winners_list = "" for r in draw_results: try: winners_list += f"• Счёт {r.participant.invoice.id} — {r.prize.reward}₩\\n" except Exception: continue async def notify_all(): await notifier.notify_draw_start(lottery) await notifier.notify_draw_results( lottery, results=draw_results, context_vars={ "total_reward": f"{total_reward:,}", "winners_list": winners_list.strip() } ) async_to_sync(notify_all)() 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): 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() 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}") messages.success(request, f"Результат для приза '{prize.prize_place}' подтвержден.") return HttpResponseRedirect(reverse("admin:view_draw_results", 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('/start-draw/', self.admin_site.admin_view(start_draw), name='start_draw'), path('confirm-draw-result//', self.admin_site.admin_view(confirm_draw_result), name='confirm_draw_result'), path('/view-draw-results/', self.admin_site.admin_view(view_draw_results), name='view_draw_results'), ] 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('Запуск розыгрыша', 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) 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 {} 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): 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")