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 from .views import view_draw_results # Настройка логгера 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: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) # При создании нового приза поле 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")