Browse Source

Blog endpoints added for admin panel

master
Mohsen Taba 2 months ago
parent
commit
2dfc9c43f7
  1. 73
      apps/blog/serializers_admin.py
  2. 14
      apps/blog/urls.py
  3. 79
      apps/blog/views_admin.py

73
apps/blog/serializers_admin.py

@ -0,0 +1,73 @@
from rest_framework import serializers
from .models import Blog, BlogContent, BlogSeo
from utils import absolute_url
# ─── Helper: return full URL for an ImageField ────────────────────────────────
class AbsoluteImageField(serializers.ImageField):
"""
Wraps DRF's ImageField to return a full absolute URL on read,
while accepting a real uploaded file (InMemoryUploadedFile /
TemporaryUploadedFile) on write just like a normal ImageField.
"""
def to_representation(self, value):
if not value:
return None
request = self.context.get("request")
url = value.url if hasattr(value, "url") else str(value)
if request:
return request.build_absolute_uri(url)
return url
# ─── Serializers ──────────────────────────────────────────────────────────────
class AdminBlogSeoSerializer(serializers.ModelSerializer):
class Meta:
model = BlogSeo
fields = ["id", "title", "keywords", "description", "language"]
class AdminBlogContentSerializer(serializers.ModelSerializer):
image = AbsoluteImageField(required=False, allow_null=True)
class Meta:
model = BlogContent
fields = [
"id", "blog", "title", "content", "slug", "image", "order",
"created_at", "updated_at",
]
class AdminBlogListSerializer(serializers.ModelSerializer):
"""
Lightweight serializer for the admin data table.
Exposes raw JSON fields so the React frontend can manage translations.
"""
thumbnail = AbsoluteImageField(required=False, allow_null=True)
class Meta:
model = Blog
fields = [
"id", "title", "slogan", "summary", "slug", "thumbnail",
"views_count", "created_at", "updated_at",
]
read_only_fields = ["id", "views_count", "created_at", "updated_at"]
class AdminBlogDetailSerializer(serializers.ModelSerializer):
"""
Full serializer for the admin detail/edit view.
Includes nested contents and SEO data.
"""
thumbnail = AbsoluteImageField(required=False, allow_null=True)
contents = AdminBlogContentSerializer(many=True, read_only=True)
seos = AdminBlogSeoSerializer(many=True, read_only=True)
class Meta:
model = Blog
fields = [
"id", "title", "slogan", "summary", "slug", "thumbnail",
"views_count", "created_at", "updated_at", "contents", "seos",
]
read_only_fields = ["id", "views_count", "created_at", "updated_at"]

14
apps/blog/urls.py

@ -1,8 +1,18 @@
from django.urls import path, re_path
from django.urls import path, re_path , include
from rest_framework.routers import DefaultRouter
from .views import BlogListAPIView, RelatedBlogsAPIView, BlogDetailBySlugAPIView
from .views_admin import AdminBlogViewSet, AdminBlogContentViewSet, AdminBlogSeoViewSet
app_name = 'blog'
# --- Admin Router Setup ---
admin_router = DefaultRouter()
admin_router.register(r'blogs', AdminBlogViewSet, basename='admin-blogs')
admin_router.register(r'contents', AdminBlogContentViewSet, basename='admin-blog-contents')
admin_router.register(r'seo', AdminBlogSeoViewSet, basename='admin-blog-seo')
urlpatterns = [
# Blog list with search and sort_by filters
path('list/', BlogListAPIView.as_view(), name='blog-list'),
@ -14,6 +24,8 @@ urlpatterns = [
re_path(r'^detail/(?P<slug>[\w\-\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\u200C\u200D]+)/$',
BlogDetailBySlugAPIView.as_view(),
name='blog-detail'),
path('admin/', include(admin_router.urls)),
]

79
apps/blog/views_admin.py

@ -0,0 +1,79 @@
from django.db.models import Q
from rest_framework.viewsets import ModelViewSet
from rest_framework.permissions import IsAuthenticated, IsAdminUser
from rest_framework.authentication import TokenAuthentication
from rest_framework.parsers import MultiPartParser, FormParser, JSONParser
from .models import Blog, BlogContent, BlogSeo
from .serializers_admin import (
AdminBlogListSerializer,
AdminBlogDetailSerializer,
AdminBlogContentSerializer,
AdminBlogSeoSerializer
)
class AdminBlogViewSet(ModelViewSet):
"""
Admin CRUD ViewSet for Blog management.
"""
permission_classes = [IsAuthenticated, IsAdminUser]
authentication_classes = [TokenAuthentication]
# Allow multipart so admins can upload thumbnail images directly
parser_classes = (MultiPartParser, FormParser, JSONParser)
def get_serializer_class(self):
if self.action == 'list':
return AdminBlogListSerializer
return AdminBlogDetailSerializer
def get_queryset(self):
queryset = Blog.objects.all().prefetch_related('contents', 'seos')
# Custom Search handling for JSON fields
search_query = self.request.query_params.get('search', None)
if search_query:
# Note: Searching inside a JSON array of dicts in Django can be tricky
# depending on your DB (PostgreSQL vs SQLite).
# This is a safe fallback using text casting for SQLite/PG compatibility.
queryset = queryset.filter(
Q(title__icontains=search_query) |
Q(slogan__icontains=search_query)
)
return queryset.order_by('-created_at')
class AdminBlogContentViewSet(ModelViewSet):
"""
Admin CRUD ViewSet for managing the content blocks inside a specific blog.
"""
serializer_class = AdminBlogContentSerializer
permission_classes = [IsAuthenticated, IsAdminUser]
authentication_classes = [TokenAuthentication]
parser_classes = (MultiPartParser, FormParser, JSONParser)
def get_queryset(self):
queryset = BlogContent.objects.all()
# Filter contents by blog_id if provided in the URL query params
blog_id = self.request.query_params.get('blog_id', None)
if blog_id:
queryset = queryset.filter(blog_id=blog_id)
return queryset.order_by('order', 'created_at')
class AdminBlogSeoViewSet(ModelViewSet):
"""
Admin CRUD ViewSet for managing SEO meta tags for a specific blog.
"""
serializer_class = AdminBlogSeoSerializer
permission_classes = [IsAuthenticated, IsAdminUser]
authentication_classes = [TokenAuthentication]
def get_queryset(self):
queryset = BlogSeo.objects.all()
blog_id = self.request.query_params.get('blog_id', None)
if blog_id:
queryset = queryset.filter(blog_id=blog_id)
return queryset
Loading…
Cancel
Save