Files
guilan-ace-backend/apps/blog/api/schemas.py
Amirhossein Khalili 954e78d0cb
Some checks failed
Backend CI/CD / test (push) Has been cancelled
Backend CI/CD / deploy (push) Has been cancelled
feat(backend): add blog publishing platform
2026-06-08 21:31:06 +03:30

268 lines
7.5 KiB
Python

"""Blog API schemas."""
from datetime import datetime
from typing import List, Optional
from ninja import ModelSchema, Schema
from apps.blog.models import Category, Comment, PostAsset, Tag
from core.media import PREVIEW_VARIANT, THUMBNAIL_VARIANT, derivative_url
class CategorySchema(ModelSchema):
created_at: Optional[datetime] = None
class Config:
model = Category
model_fields = ["id", "name", "slug", "description", "created_at"]
class TagSchema(ModelSchema):
created_at: Optional[datetime] = None
class Config:
model = Tag
model_fields = ["id", "name", "slug", "created_at"]
class AuthorSchema(Schema):
id: int
username: str
first_name: str
last_name: str
profile_picture: Optional[str] = None
profile_picture_thumbnail_url: Optional[str] = None
profile_picture_preview_url: Optional[str] = None
@staticmethod
def resolve_profile_picture(obj, context):
request = context["request"]
if obj.profile_picture and hasattr(obj.profile_picture, "url"):
return request.build_absolute_uri(obj.profile_picture.url)
return None
@staticmethod
def resolve_profile_picture_thumbnail_url(obj, context):
request = context["request"]
url = derivative_url(obj.profile_picture, THUMBNAIL_VARIANT)
return request.build_absolute_uri(url) if url else None
@staticmethod
def resolve_profile_picture_preview_url(obj, context):
request = context["request"]
url = derivative_url(obj.profile_picture, PREVIEW_VARIANT)
return request.build_absolute_uri(url) if url else None
class PostAssetSchema(ModelSchema):
absolute_file_url: Optional[str] = None
absolute_thumbnail_url: Optional[str] = None
absolute_preview_url: Optional[str] = None
absolute_blur_url: Optional[str] = None
markdown_image: Optional[str] = None
markdown_link: Optional[str] = None
uploaded_by: AuthorSchema
class Config:
model = PostAsset
model_fields = [
"id",
"file_type",
"title",
"alt_text",
"caption",
"size",
"mime_type",
"created_at",
]
@staticmethod
def resolve_absolute_file_url(obj, context):
request = context["request"]
return request.build_absolute_uri(obj.file.url) if obj.file else None
@staticmethod
def resolve_absolute_thumbnail_url(obj, context):
request = context["request"]
return request.build_absolute_uri(obj.thumbnail_url) if obj.thumbnail_url else None
@staticmethod
def resolve_absolute_preview_url(obj, context):
request = context["request"]
return request.build_absolute_uri(obj.preview_url) if obj.preview_url else None
@staticmethod
def resolve_absolute_blur_url(obj, context):
request = context["request"]
return request.build_absolute_uri(obj.blur_url) if obj.blur_url else None
@staticmethod
def resolve_markdown_image(obj, context):
request = context["request"]
if obj.file_type != PostAsset.FileType.IMAGE or not obj.file:
return None
return f"![{obj.alt_text or obj.title}]({request.build_absolute_uri(obj.file.url)})"
@staticmethod
def resolve_markdown_link(obj, context):
request = context["request"]
if not obj.file:
return None
return f"[{obj.title}]({request.build_absolute_uri(obj.file.url)})"
class PostListSchema(Schema):
id: int
title: str
slug: str
excerpt: str
author: AuthorSchema
featured_image: Optional[str] = None
absolute_featured_image_url: Optional[str] = None
absolute_featured_image_thumbnail_url: Optional[str] = None
absolute_featured_image_preview_url: Optional[str] = None
status: str
published_at: Optional[datetime] = None
category: Optional[CategorySchema] = None
tags: List[TagSchema]
is_featured: bool
created_at: datetime
updated_at: datetime
reading_time: int
seo_title: str
seo_description: str
canonical_url: str
og_title: str
og_description: str
noindex: bool
focus_keyword: str
likes_count: int
saves_count: int
comments_count: int
@staticmethod
def resolve_absolute_featured_image_url(obj, context):
request = context["request"]
if obj.featured_image and hasattr(obj.featured_image, "url"):
return request.build_absolute_uri(obj.featured_image.url)
return None
@staticmethod
def resolve_absolute_featured_image_thumbnail_url(obj, context):
request = context["request"]
url = derivative_url(obj.featured_image, THUMBNAIL_VARIANT)
return request.build_absolute_uri(url) if url else None
@staticmethod
def resolve_absolute_featured_image_preview_url(obj, context):
request = context["request"]
url = derivative_url(obj.featured_image, PREVIEW_VARIANT)
return request.build_absolute_uri(url) if url else None
@staticmethod
def resolve_likes_count(obj):
return getattr(obj, "likes_count", None) or obj.likes.count()
@staticmethod
def resolve_saves_count(obj):
return getattr(obj, "saves_count", None) or obj.saves.count()
@staticmethod
def resolve_comments_count(obj):
return getattr(obj, "comments_count", None) or obj.comments.filter(is_approved=True).count()
class PostDetailSchema(PostListSchema):
content: str
content_html: str
og_image_url: Optional[str] = None
assets: List[PostAssetSchema] = []
@staticmethod
def resolve_og_image_url(obj, context):
request = context["request"]
if obj.og_image and hasattr(obj.og_image, "url"):
return request.build_absolute_uri(obj.og_image.url)
return None
class PostCreateSchema(Schema):
title: str
content: str
excerpt: Optional[str] = None
category_id: Optional[int] = None
tag_ids: Optional[List[int]] = []
status: str = "draft"
is_featured: bool = False
seo_title: Optional[str] = ""
seo_description: Optional[str] = ""
canonical_url: Optional[str] = ""
og_title: Optional[str] = ""
og_description: Optional[str] = ""
noindex: Optional[bool] = False
focus_keyword: Optional[str] = ""
class PostReviewSchema(Schema):
action: str
note: Optional[str] = ""
class PostAssetCreateSchema(Schema):
title: Optional[str] = ""
alt_text: Optional[str] = ""
caption: Optional[str] = ""
class CommentSchema(ModelSchema):
author: AuthorSchema
replies: List["CommentSchema"] = []
post_id: int
post_title: str
post_slug: str
parent_id: Optional[int] = None
class Config:
model = Comment
model_fields = ["id", "content", "created_at", "is_approved", "hidden_at"]
@staticmethod
def resolve_post_id(obj):
return obj.post_id
@staticmethod
def resolve_post_title(obj):
return obj.post.title
@staticmethod
def resolve_post_slug(obj):
return obj.post.slug
@staticmethod
def resolve_parent_id(obj):
return obj.parent_id
class CommentCreateSchema(Schema):
content: str
parent_id: Optional[int] = None
class CommentHideSchema(Schema):
note: Optional[str] = ""
class BlogInteractionSchema(Schema):
liked: bool
saved: bool
likes_count: int
saves_count: int
comments_count: int
class BlogProfileActivitySchema(Schema):
liked_posts: List[PostListSchema]
saved_posts: List[PostListSchema]
comments: List[CommentSchema]
replies: List[CommentSchema]