from django.contrib import messages from django.core.cache import cache from django.db import IntegrityError, transaction from django.shortcuts import redirect, render from django.urls import reverse from django.utils import timezone from .forms import ConsultationRequestForm from .models import ConsultationSlot # Антиспам: не более 3 заявок с одного IP за 10 минут SUBMIT_LIMIT = 3 SUBMIT_WINDOW_SECONDS = 600 def _client_ip(request): # X-Forwarded-For добавляет nginx; берём первый адрес (реальный клиент) xff = request.META.get("HTTP_X_FORWARDED_FOR") if xff: return xff.split(",")[0].strip() return request.META.get("REMOTE_ADDR", "unknown") def _submit_allowed(request): key = f"submit:{_client_ip(request)}" now = timezone.now().timestamp() stamps = cache.get(key, []) stamps = [s for s in stamps if now - s < SUBMIT_WINDOW_SECONDS] if len(stamps) >= SUBMIT_LIMIT: cache.set(key, stamps, SUBMIT_WINDOW_SECONDS) return False stamps.append(now) cache.set(key, stamps, SUBMIT_WINDOW_SECONDS) return True def home(request): if request.method == "POST": if not _submit_allowed(request): messages.error( request, "Слишком много заявок за короткое время. Попробуйте через 10 минут.", ) return redirect(f"{reverse('consultations:home')}#booking") form = ConsultationRequestForm(request.POST) if form.is_valid(): try: with transaction.atomic(): form.save() except IntegrityError: form.add_error("slot", "Это время только что занял другой ученик. Выберите другой слот.") else: messages.success( request, "Заявка принята. Игорь Олегович свяжется с вами для подтверждения консультации.", ) return redirect(f"{reverse('consultations:home')}#booking") else: form = ConsultationRequestForm() return render( request, "consultations/home.html", { "form": form, "available_slot_count": ConsultationSlot.objects.available().count(), }, ) def privacy_policy(request): return render(request, "consultations/privacy.html")