355 lines
9.5 KiB
Python
355 lines
9.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 BlogBanner, Category, Comment, PostAsset, Tag
|
|
from core.media import PREVIEW_VARIANT, THUMBNAIL_VARIANT, derivative_url
|
|
|
|
|
|
class CategorySchema(ModelSchema):
|
|
created_at: Optional[datetime] = None
|
|
parent_id: Optional[int] = None
|
|
|
|
class Config:
|
|
model = Category
|
|
model_fields = ["id", "name", "slug", "description", "created_at"]
|
|
|
|
@staticmethod
|
|
def resolve_parent_id(obj):
|
|
return obj.parent_id
|
|
|
|
|
|
class CategoryPathSchema(Schema):
|
|
id: int
|
|
name: str
|
|
slug: str
|
|
|
|
|
|
class CategoryFilterSchema(Schema):
|
|
id: int
|
|
name: str
|
|
slug: str
|
|
parent_id: Optional[int] = None
|
|
post_count: int = 0
|
|
children: List["CategoryFilterSchema"] = []
|
|
|
|
|
|
class TagSchema(ModelSchema):
|
|
created_at: Optional[datetime] = None
|
|
|
|
class Config:
|
|
model = Tag
|
|
model_fields = ["id", "name", "slug", "created_at"]
|
|
|
|
|
|
class TagFilterSchema(Schema):
|
|
id: int
|
|
name: str
|
|
slug: str
|
|
post_count: int = 0
|
|
|
|
|
|
class AuthorSchema(Schema):
|
|
id: int
|
|
username: str
|
|
first_name: str
|
|
last_name: str
|
|
bio: Optional[str] = None
|
|
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"})"
|
|
|
|
@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
|
|
category_path: List[CategoryPathSchema] = []
|
|
writers: List[AuthorSchema] = []
|
|
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_category_path(obj):
|
|
if not obj.category_id:
|
|
return []
|
|
return obj.category.path
|
|
|
|
@staticmethod
|
|
def resolve_writers(obj):
|
|
writers = list(obj.writers.all())
|
|
return writers or [obj.author]
|
|
|
|
@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,
|
|
is_hidden=False,
|
|
is_deleted=False,
|
|
).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]] = []
|
|
writer_ids: Optional[List[int]] = None
|
|
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
|
|
is_hidden: bool = False
|
|
is_deleted: bool = False
|
|
deleted_at: Optional[datetime] = None
|
|
hidden_replies_count: int = 0
|
|
|
|
class Config:
|
|
model = Comment
|
|
model_fields = ["id", "content", "created_at", "updated_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
|
|
|
|
@staticmethod
|
|
def resolve_hidden_replies_count(obj):
|
|
if not getattr(obj, "replies", None):
|
|
return 0
|
|
return sum(len(reply.replies.all()) for reply in obj.replies.all())
|
|
|
|
|
|
class CommentCreateSchema(Schema):
|
|
content: str
|
|
parent_id: Optional[int] = None
|
|
|
|
|
|
class CommentUpdateSchema(Schema):
|
|
content: str
|
|
|
|
|
|
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]
|
|
|
|
|
|
class BlogBannerSchema(ModelSchema):
|
|
image_url: str
|
|
|
|
class Config:
|
|
model = BlogBanner
|
|
model_fields = ["id", "title", "alt_text", "url", "sort_order"]
|
|
|
|
@staticmethod
|
|
def resolve_image_url(obj, context):
|
|
request = context["request"]
|
|
return request.build_absolute_uri(obj.image.url) if obj.image else ""
|
|
|
|
|
|
class BlogFilterAuthorSchema(Schema):
|
|
id: int
|
|
username: str
|
|
first_name: str
|
|
last_name: str
|
|
post_count: int = 0
|
|
|
|
|
|
class BlogFiltersSchema(Schema):
|
|
categories: List[CategoryFilterSchema]
|
|
tags: List[TagFilterSchema]
|
|
authors: List[BlogFilterAuthorSchema]
|