From 74554a664a1dd2c36145628d7dbff4d752284161 Mon Sep 17 00:00:00 2001 From: Parsa Nazer Date: Wed, 6 May 2026 10:26:14 +0330 Subject: [PATCH] update blog performance and fix blog view --- backend/blog/serializers.py | 17 +++++++--- backend/blog/urls.py | 4 +-- backend/blog/views.py | 65 ++++++++++++++++++++++--------------- 3 files changed, 52 insertions(+), 34 deletions(-) diff --git a/backend/blog/serializers.py b/backend/blog/serializers.py index 12ea953..bb68e01 100644 --- a/backend/blog/serializers.py +++ b/backend/blog/serializers.py @@ -14,18 +14,25 @@ class AuthorSerializer(serializers.ModelSerializer): else: return 'ادمین وبسایت' +class BlogCategorySerilizer(serializers.ModelSerializer): + name = serializers.SerializerMethodField() + class Meta: + model = BlogCategoryModel + fields = ['id', 'name'] + + def get_name(self, obj): + return obj.title + + + class BlogSerilizer(serializers.ModelSerializer): - category = SubCategorySerializer() + category = BlogCategorySerilizer() author = AuthorSerializer() class Meta: model = BlogModel exclude = ('is_published',) -class BlogCategorySerilizer(serializers.ModelSerializer): - class Meta: - model = BlogCategoryModel - fields = '__all__' class AllBlogSerilizer(serializers.ModelSerializer): diff --git a/backend/blog/urls.py b/backend/blog/urls.py index ec475ec..1a4c373 100644 --- a/backend/blog/urls.py +++ b/backend/blog/urls.py @@ -1,8 +1,8 @@ -from django.urls import path +from django.urls import path, re_path from . import views urlpatterns = [ path('all', views.AllBlogView.as_view(), name='product-chat-view'), path('categories', views.AllBlogCategoryView.as_view()), - path('', views.BlogView.as_view(), name='product-chat-view'), + re_path(r'^(?P[\w\u0600-\u06FF\-]+)$', views.BlogView.as_view(), name='blog-view'), ] \ No newline at end of file diff --git a/backend/blog/views.py b/backend/blog/views.py index 353bcf7..f05fd03 100644 --- a/backend/blog/views.py +++ b/backend/blog/views.py @@ -6,15 +6,19 @@ from .serializers import AllBlogSerilizer, BlogSerilizer, AllBlogCategorySeriliz from django.shortcuts import get_object_or_404 from utils.pagination import StructurePagination from drf_spectacular.utils import extend_schema, OpenApiParameter, OpenApiTypes -from django.db.models import Q - +from django.db.models import Q, F +from django.views.decorators.cache import cache_page +from django.utils.decorators import method_decorator +from django.core.cache import cache class AllBlogCategoryView(APIView): serializer_class = AllBlogCategorySerilizer authentication_classes = [] + + @method_decorator(cache_page(60 * 60)) # Cache for 1 hour def get(self, request): - categories = BlogCategoryModel.objects.all() + categories = BlogCategoryModel.objects.all().only('id', 'name') category_ser = self.serializer_class(instance=categories, many=True, context={'request': request}) return Response(category_ser.data, status=status.HTTP_200_OK) @@ -23,29 +27,30 @@ class AllBlogView(APIView): serializer_class = AllBlogSerilizer pagination_class = StructurePagination authentication_classes = [] + @extend_schema( parameters=[ OpenApiParameter( name="search", - description="بگرددددد تو بلاااااگگگووووو", + description="بگردش در بلاگ", required=False, type=OpenApiTypes.STR, ), OpenApiParameter( name="category_id", - description="", + description="فیلتر بر اساس دسته بندی", required=False, type=OpenApiTypes.STR, ), OpenApiParameter( name="limit", - description="لیمیتش", + description="تعداد نتایج", required=False, type=OpenApiTypes.INT, ), OpenApiParameter( name="offset", - description="افستش", + description="شروع از", required=False, type=OpenApiTypes.INT, ) @@ -56,25 +61,30 @@ class AllBlogView(APIView): }, ) def get(self, request): - blogs = BlogModel.objects.filter(is_published=True) + # Use only() to fetch required fields + blogs = BlogModel.objects.filter(is_published=True).select_related('category').only( + 'id', 'title', 'slug', 'summery', 'cover_image', 'views', 'created_at', 'category' + ) + search_query = request.query_params.get('search', None) category_id = request.query_params.get('category_id', None) if search_query: - blogs = blogs.filter(Q(title__icontains=search_query) | Q(content__icontains=search_query)) + blogs = blogs.filter(Q(title__icontains=search_query) | Q(summery__icontains=search_query)) + if category_id: - category_obj = get_object_or_404(BlogCategoryModel, pk=category_id) - blogs.filter(category=category_obj) + blogs = blogs.filter(category_id=category_id) # Use category_id directly + paginator = self.pagination_class() paginated_blogs = paginator.paginate_queryset(blogs, request) blog_ser = self.serializer_class(instance=paginated_blogs, many=True, context={'request': request}) return paginator.get_paginated_response(blog_ser.data) - - + class BlogView(APIView): serializer_class = BlogSerilizer authentication_classes = [] + def get_client_ip(self, request): """Helper function to get the client IP from request headers.""" x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR') @@ -84,24 +94,25 @@ class BlogView(APIView): ip = request.META.get('REMOTE_ADDR') return ip - def get(self, request, slug): - blog = get_object_or_404(BlogModel, slug=slug) + # Use only() to fetch required fields + blog = get_object_or_404(BlogModel.objects.select_related('category', 'author'), slug=slug) + if blog.is_published: - # Track views using session + # Track views using cache instead of session (more efficient) client_ip = self.get_client_ip(request) - session_key = f'viewed_blog_{slug}_{client_ip}' - - if not request.session.get(session_key): - blog.views += 1 - blog.save() - print(f'views {blog.views}') - print(session_key) - request.session[session_key] = True - request.session.set_expiry(3600) + cache_key = f'viewed_blog_{slug}_{client_ip}' + + if not cache.get(cache_key): + # Use F() to avoid race conditions + BlogModel.objects.filter(pk=blog.pk).update(views=F('views') + 1) + blog.refresh_from_db(fields=['views']) + cache.set(cache_key, True, 3600) # Cache for 1 hour blog_ser = self.serializer_class(instance=blog, context={'request': request}) return Response(blog_ser.data, status=status.HTTP_200_OK) else: - return Response({'detail': 'object with the given id does not exist or is not published yet'}, - status=status.HTTP_404_NOT_FOUND) \ No newline at end of file + return Response( + {'detail': 'object with the given id does not exist or is not published yet'}, + status=status.HTTP_404_NOT_FOUND + ) \ No newline at end of file