init commit
This commit is contained in:
0
lottery/draw/__init__.py
Normal file
0
lottery/draw/__init__.py
Normal file
479
lottery/draw/admin.py
Normal file
479
lottery/draw/admin.py
Normal 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")
|
||||
7
lottery/draw/apps.py
Normal file
7
lottery/draw/apps.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class DrawConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'draw'
|
||||
verbose_name='Розыгрыш'
|
||||
14
lottery/draw/forms.py
Normal file
14
lottery/draw/forms.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from django import forms
|
||||
from webapp.models import Invoice
|
||||
|
||||
|
||||
class AddParticipantsForm(forms.Form):
|
||||
deposit_min = forms.DecimalField(label="Минимальный депозит", required=False)
|
||||
deposit_max = forms.DecimalField(label="Максимальный депозит", required=False)
|
||||
invoices = forms.ModelMultipleChoiceField(
|
||||
queryset=Invoice.objects.none(),
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
required=False,
|
||||
label="Доступные счета"
|
||||
)
|
||||
|
||||
36
lottery/draw/migrations/0001_initial.py
Normal file
36
lottery/draw/migrations/0001_initial.py
Normal file
@@ -0,0 +1,36 @@
|
||||
# Generated by Django 5.1.6 on 2025-03-06 10:42
|
||||
|
||||
import django.db.models.deletion
|
||||
from decimal import Decimal
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('webapp', '0002_invoice'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Lottery',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255, verbose_name='Название лотереи')),
|
||||
('description', models.TextField(blank=True, verbose_name='Описание')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Prize',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('prize_place', models.CharField(help_text='Например, 1 место, 2 место и т.д.', max_length=50, verbose_name='Призовое место')),
|
||||
('description', models.TextField(blank=True, verbose_name='Описание приза')),
|
||||
('reward', models.DecimalField(decimal_places=2, default=Decimal('0.00'), max_digits=10, verbose_name='Награда')),
|
||||
('lottery', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='prizes', to='draw.lottery', verbose_name='Лотерея')),
|
||||
('winner', models.ForeignKey(blank=True, help_text='Выберите счет участника лотереи, который выиграл приз', null=True, on_delete=django.db.models.deletion.SET_NULL, to='webapp.invoice', verbose_name='Победитель (счет)')),
|
||||
],
|
||||
),
|
||||
]
|
||||
29
lottery/draw/migrations/0002_lotteryparticipant.py
Normal file
29
lottery/draw/migrations/0002_lotteryparticipant.py
Normal file
@@ -0,0 +1,29 @@
|
||||
# Generated by Django 5.1.6 on 2025-03-06 10:47
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('draw', '0001_initial'),
|
||||
('webapp', '0002_invoice'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='LotteryParticipant',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('added_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата добавления')),
|
||||
('invoice', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='webapp.invoice', verbose_name='Счет участника')),
|
||||
('lottery', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='participants', to='draw.lottery', verbose_name='Лотерея')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Участник лотереи',
|
||||
'verbose_name_plural': 'Участники лотереи',
|
||||
'unique_together': {('lottery', 'invoice')},
|
||||
},
|
||||
),
|
||||
]
|
||||
19
lottery/draw/migrations/0003_alter_prize_winner.py
Normal file
19
lottery/draw/migrations/0003_alter_prize_winner.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 5.1.6 on 2025-03-06 10:48
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('draw', '0002_lotteryparticipant'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='prize',
|
||||
name='winner',
|
||||
field=models.ForeignKey(blank=True, help_text='Выберите счет участника лотереи, который выиграл приз', null=True, on_delete=django.db.models.deletion.SET_NULL, to='draw.lotteryparticipant', verbose_name='Победитель (счет)'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,21 @@
|
||||
# Generated by Django 5.1.6 on 2025-03-06 10:52
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('draw', '0003_alter_prize_winner'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='lottery',
|
||||
options={'verbose_name': 'Розыгрыш', 'verbose_name_plural': 'Розыгрыши'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='prize',
|
||||
options={'verbose_name': 'Приз', 'verbose_name_plural': 'Призы'},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,56 @@
|
||||
# Generated by Django 5.1.6 on 2025-03-06 11:19
|
||||
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('draw', '0004_alter_lottery_options_alter_prize_options'),
|
||||
('webapp', '0003_alter_client_options_alter_invoice_options'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='lottery',
|
||||
options={},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='prize',
|
||||
options={},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='lottery',
|
||||
name='created_at',
|
||||
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now, verbose_name='Дата создания'),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='lotteryparticipant',
|
||||
name='used',
|
||||
field=models.BooleanField(default=False, help_text='Отметка, что этот счет уже участвовал в розыгрыше', verbose_name='Использован'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='lotteryparticipant',
|
||||
name='invoice',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='draw.lotteryparticipant', verbose_name='Счет участника'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='prize',
|
||||
name='winner',
|
||||
field=models.ForeignKey(blank=True, help_text='Выберите счет участника лотереи, который выиграл приз', null=True, on_delete=django.db.models.deletion.SET_NULL, to='webapp.invoice', verbose_name='Победитель (счет)'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DrawResult',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('confirmed', models.BooleanField(default=False, verbose_name='Подтвержден')),
|
||||
('drawn_at', models.DateTimeField(auto_now_add=True, verbose_name='Время розыгрыша')),
|
||||
('lottery', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='draw_results', to='draw.lottery', verbose_name='Лотерея')),
|
||||
('participant', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='draw.lotteryparticipant', verbose_name='Победитель')),
|
||||
('prize', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='draw.prize', verbose_name='Приз')),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,25 @@
|
||||
# Generated by Django 5.1.6 on 2025-03-06 11:21
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('draw', '0005_alter_lottery_options_alter_prize_options_and_more'),
|
||||
('webapp', '0003_alter_client_options_alter_invoice_options'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='lotteryparticipant',
|
||||
name='invoice',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='webapp.invoice', verbose_name='Счет участника'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='prize',
|
||||
name='winner',
|
||||
field=models.ForeignKey(blank=True, help_text='Выберите счет участника лотереи, который выиграл приз', null=True, on_delete=django.db.models.deletion.SET_NULL, to='draw.lotteryparticipant', verbose_name='Победитель (счет)'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,25 @@
|
||||
# Generated by Django 5.1.6 on 2025-03-06 11:29
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('draw', '0006_alter_lotteryparticipant_invoice_alter_prize_winner'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='drawresult',
|
||||
options={'verbose_name': 'Результат розыгрыша', 'verbose_name_plural': 'Результаты розыгрыша'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='lottery',
|
||||
options={'verbose_name': 'Лотерея', 'verbose_name_plural': 'Лотереи'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='prize',
|
||||
options={'verbose_name': 'Приз', 'verbose_name_plural': 'Призы'},
|
||||
),
|
||||
]
|
||||
18
lottery/draw/migrations/0008_lottery_finished.py
Normal file
18
lottery/draw/migrations/0008_lottery_finished.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.6 on 2025-03-22 22:22
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('draw', '0007_alter_drawresult_options_alter_lottery_options_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='lottery',
|
||||
name='finished',
|
||||
field=models.BooleanField(default=False, verbose_name='Завершена'),
|
||||
),
|
||||
]
|
||||
0
lottery/draw/migrations/__init__.py
Normal file
0
lottery/draw/migrations/__init__.py
Normal file
113
lottery/draw/models.py
Normal file
113
lottery/draw/models.py
Normal file
@@ -0,0 +1,113 @@
|
||||
from django.db import models
|
||||
from decimal import Decimal
|
||||
from webapp.models import Invoice # Явно импортируем модель Invoice из приложения webapp
|
||||
|
||||
class Lottery(models.Model):
|
||||
name = models.CharField("Название лотереи", max_length=255)
|
||||
description = models.TextField("Описание", blank=True)
|
||||
created_at = models.DateTimeField("Дата создания", auto_now_add=True)
|
||||
finished = models.BooleanField(default=False, verbose_name="Завершена")
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Лотерея"
|
||||
verbose_name_plural = "Лотереи"
|
||||
|
||||
|
||||
class Prize(models.Model):
|
||||
lottery = models.ForeignKey(
|
||||
Lottery,
|
||||
on_delete=models.CASCADE,
|
||||
related_name="prizes",
|
||||
verbose_name="Лотерея"
|
||||
)
|
||||
prize_place = models.CharField(
|
||||
"Призовое место",
|
||||
max_length=50,
|
||||
help_text="Например, 1 место, 2 место и т.д."
|
||||
)
|
||||
description = models.TextField("Описание приза", blank=True)
|
||||
reward = models.DecimalField(
|
||||
"Награда",
|
||||
max_digits=10,
|
||||
decimal_places=2,
|
||||
default=Decimal("0.00")
|
||||
)
|
||||
# Победитель назначается администратором из списка участников лотереи (LotteryParticipant)
|
||||
winner = models.ForeignKey(
|
||||
"draw.LotteryParticipant",
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name="Победитель (счет)",
|
||||
help_text="Выберите счет участника лотереи, который выиграл приз"
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.prize_place} - {self.lottery.name}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Приз"
|
||||
verbose_name_plural = "Призы"
|
||||
|
||||
|
||||
class LotteryParticipant(models.Model):
|
||||
lottery = models.ForeignKey(
|
||||
Lottery,
|
||||
on_delete=models.CASCADE,
|
||||
related_name="participants",
|
||||
verbose_name="Лотерея"
|
||||
)
|
||||
# Ссылка на счет-участник из приложения webapp (модель Invoice)
|
||||
invoice = models.ForeignKey(
|
||||
Invoice,
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name="Счет участника"
|
||||
)
|
||||
added_at = models.DateTimeField("Дата добавления", auto_now_add=True)
|
||||
used = models.BooleanField("Использован", default=False,
|
||||
help_text="Отметка, что этот счет уже участвовал в розыгрыше")
|
||||
|
||||
class Meta:
|
||||
unique_together = ("lottery", "invoice")
|
||||
verbose_name = "Участник лотереи"
|
||||
verbose_name_plural = "Участники лотереи"
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.invoice} в {self.lottery.name}"
|
||||
|
||||
|
||||
class DrawResult(models.Model):
|
||||
"""
|
||||
Результат розыгрыша для конкретного приза.
|
||||
Каждому призу соответствует один активный результат розыгрыша.
|
||||
"""
|
||||
lottery = models.ForeignKey(
|
||||
Lottery,
|
||||
on_delete=models.CASCADE,
|
||||
related_name="draw_results",
|
||||
verbose_name="Лотерея"
|
||||
)
|
||||
prize = models.OneToOneField(
|
||||
Prize,
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name="Приз"
|
||||
)
|
||||
# Ссылка на участника (LotteryParticipant), выбранного в розыгрыше
|
||||
participant = models.ForeignKey(
|
||||
LotteryParticipant,
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name="Победитель"
|
||||
)
|
||||
confirmed = models.BooleanField("Подтвержден", default=False)
|
||||
drawn_at = models.DateTimeField("Время розыгрыша", auto_now_add=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"Результат для {self.prize} — {self.participant}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Результат розыгрыша"
|
||||
verbose_name_plural = "Результаты розыгрыша"
|
||||
3
lottery/draw/tests.py
Normal file
3
lottery/draw/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
3
lottery/draw/views.py
Normal file
3
lottery/draw/views.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
Reference in New Issue
Block a user