3 changed files with 165 additions and 1 deletions
@ -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"] |
|||
@ -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 |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue