Posts, Likes, and Comments

Compose posts, toggle likes, and add comments without a full page reload.

Goal for this part

Let signed-in people compose a post, like someone else’s post, and leave a short comment. Authors can edit or delete only their own posts. Likes and comments use HTMX fragments.

Concepts

Ownership is a query: post.author_id == request.user.id. Do not hide the edit button and call that security — hide the button and check in the view. Unique constraints make like-toggle safe under double-clicks: insert or delete, never increment a counter column by hand.

Walkthrough

from django.utils.text import slugify
from django.utils.crypto import get_random_string

def compose(request):
    form = PostForm(request.POST or None)
    if request.method == "POST" and form.is_valid():
        post = form.save(commit=False)
        post.author = request.user
        post.slug = slugify(post.body[:40]) + "-" + get_random_string(6)
        post.save()
        return redirect("posts:detail", slug=post.slug)
    return render(request, "posts/compose.html", {"form": form})
@login_required
@require_POST
def toggle_like(request, slug):
    post = get_object_or_404(Post, slug=slug)
    like, created = Like.objects.get_or_create(user=request.user, post=post)
    if not created:
        like.delete()
    return render(request, "posts/_like_button.html", {"post": post, "user": request.user})

Comments are a small POST on the detail page. Cap them at 280 characters. Strip leading/trailing whitespace and reject empty bodies. Return the new comment list fragment so the form can clear with hx-on::after-request or a swapped empty textarea.

How to run it and what you should see

Sign in as Ada. Compose “Block party on Saturday.” The post should appear on the feed and on Ada’s profile. Like Grace’s post: the heart count increments without reload. Comment on it. Sign in as Grace and confirm she cannot open Ada’s edit URL.

Common mistakes

Slug collisions happen if you only slugify the first words — append a short random suffix. A like view that accepts GET will be triggered by prefetch. Deleting a post must cascade likes and comments (the foreign keys already do if you set on_delete=CASCADE).

Try this

Double-click Like quickly. The count should land on 0 or 1, never 2. Then delete your own post and confirm its detail URL 404s.