Files
main-site/apps/comments/views.py
Mark c4fde90a9c
All checks were successful
CI / nightly-e2e (pull_request) Has been skipped
CI / ci (pull_request) Successful in 1m1s
fix(spec): enforce read-time budget and re-render invalid comment submissions
2026-02-28 17:36:34 +00:00

64 lines
2.5 KiB
Python

from __future__ import annotations
from django.conf import settings
from django.contrib import messages
from django.core.cache import cache
from django.core.exceptions import ValidationError
from django.http import HttpResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.views import View
from apps.blog.models import ArticlePage
from apps.comments.forms import CommentForm
from apps.comments.models import Comment
def client_ip_from_request(request) -> str:
remote_addr = request.META.get("REMOTE_ADDR", "").strip()
trusted_proxies = getattr(settings, "TRUSTED_PROXY_IPS", [])
x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR", "")
if remote_addr in trusted_proxies and x_forwarded_for:
return x_forwarded_for.split(",")[0].strip()
return remote_addr
class CommentCreateView(View):
def _render_article_with_errors(self, request, article, form):
context = article.get_context(request)
context["page"] = article
context["comment_form"] = form
return render(request, "blog/article_page.html", context, status=200)
def post(self, request):
ip = client_ip_from_request(request)
key = f"comment-rate:{ip}"
count = cache.get(key, 0)
if count >= 3:
return HttpResponse(status=429)
cache.set(key, count + 1, timeout=60)
form = CommentForm(request.POST)
article = get_object_or_404(ArticlePage, pk=request.POST.get("article_id"))
if not article.comments_enabled:
return HttpResponse(status=404)
if form.is_valid():
if form.cleaned_data.get("honeypot"):
return redirect(f"{article.url}?commented=1")
comment = form.save(commit=False)
comment.article = article
parent_id = form.cleaned_data.get("parent_id")
if parent_id:
comment.parent = Comment.objects.filter(pk=parent_id, article=article).first()
comment.ip_address = ip or None
try:
comment.full_clean()
except ValidationError:
form.add_error(None, "Reply depth exceeds the allowed limit")
return self._render_article_with_errors(request, article, form)
comment.save()
messages.success(request, "Your comment is awaiting moderation")
return redirect(f"{article.url}?commented=1")
return self._render_article_with_errors(request, article, form)