From 4039be018714c096c023241aff675a0f5e20e9d2 Mon Sep 17 00:00:00 2001 From: Amirhossein Khalili Date: Wed, 10 Jun 2026 11:55:44 +0330 Subject: [PATCH] feat(blog): add recommended posts endpoint --- apps/blog/api/views.py | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/apps/blog/api/views.py b/apps/blog/api/views.py index 2853951..ef74dbd 100644 --- a/apps/blog/api/views.py +++ b/apps/blog/api/views.py @@ -357,6 +357,47 @@ def list_posts( return list(queryset[offset : offset + limit]) +@blog_router.get("/posts/{slug}/recommended", response=List[PostListSchema]) +def recommended_posts(request, slug: str, limit: int = Query(3, ge=1, le=12)): + post = get_object_or_404( + Post.objects.select_related("category").prefetch_related("tags"), + slug=slug, + status=Post.StatusChoices.PUBLISHED, + ) + tag_ids = list(post.tags.values_list("id", flat=True)) + selected = [] + seen_ids = {post.id} + + def append_posts(queryset): + remaining = limit - len(selected) + if remaining <= 0: + return + for candidate in queryset.exclude(id__in=seen_ids)[:remaining]: + selected.append(candidate) + seen_ids.add(candidate.id) + + if tag_ids: + append_posts( + _published_queryset() + .filter(tags__id__in=tag_ids) + .annotate(shared_tags=Count("tags", filter=Q(tags__id__in=tag_ids), distinct=True)) + .order_by("-shared_tags", "-published_at", "-created_at") + .distinct() + ) + + if len(selected) < limit and post.category_id: + append_posts( + _published_queryset() + .filter(category_id=post.category_id) + .order_by("-published_at", "-created_at") + ) + + if len(selected) < limit: + append_posts(_published_queryset().order_by("-published_at", "-created_at")) + + return selected + + @blog_router.get("/posts/{slug}", response=PostDetailSchema) def get_post(request, slug: str): return get_object_or_404(_published_queryset(), slug=slug)