added blog api, updated user api

This commit is contained in:
Alexander Karpov 2023-08-01 04:08:11 +03:00
parent e403a29cda
commit 13638466a2
14 changed files with 223 additions and 7 deletions

View File

@ -0,0 +1,86 @@
from drf_spectacular.utils import extend_schema_field
from rest_framework import serializers
from akarpov.blog.models import Comment, Post, Tag
from akarpov.common.api import RecursiveField
from akarpov.users.api.serializers import UserPublicInfoSerializer
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = ["name", "color"]
class PostSerializer(serializers.ModelSerializer):
creator = UserPublicInfoSerializer()
main_tag = serializers.SerializerMethodField(method_name="get_h_tag")
url = serializers.HyperlinkedIdentityField(
view_name="api:blog:post", lookup_field="slug"
)
@extend_schema_field(TagSerializer)
def get_h_tag(self, obj):
return TagSerializer(many=False).to_representation(instance=obj.h_tags()[0])
class Meta:
model = Post
fields = [
"title",
"url",
"main_tag",
"image_cropped",
"summary",
"creator",
"post_views",
"rating",
"comment_count",
"short_link",
"created",
"edited",
]
extra_kwargs = {
"url": {"view_name": "api:blog:post", "lookup_field": "slug"},
}
class FullPostSerializer(PostSerializer):
"""note: body is returned as html for format"""
tags = TagSerializer(many=True)
comments = serializers.HyperlinkedIdentityField(
view_name="api:blog:post_comments", lookup_field="slug"
)
class Meta:
model = Post
fields = [
"title",
"image",
"tags",
"summary",
"creator",
"post_views",
"rating",
"comment_count",
"comments",
"short_link",
"created",
"edited",
]
extra_kwargs = {
"fio": {"required": False},
"username": {"required": False},
"is_agent": {"read_only": True},
"form": {"read_only": True},
}
class CommentSerializer(serializers.ModelSerializer):
author = UserPublicInfoSerializer()
children = RecursiveField(many=True)
class Meta:
model = Comment
fields = ["author", "body", "created", "rating"]

17
akarpov/blog/api/urls.py Normal file
View File

@ -0,0 +1,17 @@
from django.urls import path
from akarpov.blog.api.views import (
GetPost,
ListCommentsSerializer,
ListMainPostsView,
ListPostsView,
)
app_name = "blog_api"
urlpatterns = [
path("", ListMainPostsView.as_view(), name="list_main"),
path("all/", ListPostsView.as_view(), name="list_all"),
path("<str:slug>", GetPost.as_view(), name="post"),
path("<str:slug>/comments", ListCommentsSerializer.as_view(), name="post_comments"),
]

50
akarpov/blog/api/views.py Normal file
View File

@ -0,0 +1,50 @@
from rest_framework import generics
from rest_framework.generics import get_object_or_404
from rest_framework.permissions import AllowAny
from akarpov.blog.api.serializers import (
CommentSerializer,
FullPostSerializer,
PostSerializer,
)
from akarpov.blog.models import Post
from akarpov.blog.services import get_main_rating_posts
from akarpov.common.api import StandardResultsSetPagination
class ListMainPostsView(generics.ListAPIView):
serializer_class = PostSerializer
permission_classes = [AllowAny]
pagination_class = StandardResultsSetPagination
def get_queryset(self):
return get_main_rating_posts()
class ListPostsView(generics.ListAPIView):
serializer_class = PostSerializer
permission_classes = [AllowAny]
pagination_class = StandardResultsSetPagination
def get_queryset(self):
return Post.objects.all()
class GetPost(generics.RetrieveAPIView):
serializer_class = FullPostSerializer
permission_classes = [AllowAny]
def get_object(self):
post = get_object_or_404(Post, slug=self.kwargs["slug"])
post.post_views += 1
post.save(update_fields=["post_views"])
return post
class ListCommentsSerializer(generics.ListAPIView):
serializer_class = CommentSerializer
permission_classes = [AllowAny]
def get_queryset(self):
post = get_object_or_404(Post, slug=self.kwargs["slug"])
return post.comments.filter(parent__isnull=True)

View File

@ -3,6 +3,8 @@
from django.db import models from django.db import models
from django.db.models import Count from django.db.models import Count
from django.urls import reverse from django.urls import reverse
from drf_spectacular.utils import extend_schema_field
from rest_framework import serializers
from akarpov.common.models import BaseImageModel from akarpov.common.models import BaseImageModel
from akarpov.tools.shortener.models import ShortLinkModel from akarpov.tools.shortener.models import ShortLinkModel
@ -41,9 +43,8 @@ def get_comments(self):
def h_tags(self): def h_tags(self):
# TODO: add caching here # TODO: add caching here
tags = ( tags = (
Tag.objects.all() Tag.objects.filter(posts__id=self.id)
.annotate(num_posts=Count("posts")) .annotate(num_posts=Count("posts"))
.filter(posts__id=self.id)
.order_by("-num_posts") .order_by("-num_posts")
) )
return tags return tags
@ -57,6 +58,7 @@ def text(self):
return cleanhtml(self.body) return cleanhtml(self.body)
@property @property
@extend_schema_field(serializers.CharField)
def summary(self): def summary(self):
body = self.text body = self.text
return body[:100] + "..." if len(body) > 100 else "" return body[:100] + "..." if len(body) > 100 else ""

View File

@ -1,6 +1,7 @@
from django.urls import reverse from django.urls import reverse
from akarpov.blog import models from akarpov.blog import models
from akarpov.blog.models import Post
from akarpov.users.models import User from akarpov.users.models import User
@ -52,3 +53,7 @@ def get_rating_bar(user: User, post):
+ f"""<form method="post" action="{url_down}" class="col-auto align-self-center"><button class="btn border-0 + f"""<form method="post" action="{url_down}" class="col-auto align-self-center"><button class="btn border-0
btn-small"><i style="font-size: 1rem;" class="bi bi-arrow-down-circle"></i></button></form></div>""" btn-small"><i style="font-size: 1rem;" class="bi bi-arrow-down-circle"></i></button></form></div>"""
) )
def get_main_rating_posts() -> [Post]:
return Post.objects.filter(creator__is_superuser=True)

View File

@ -2,6 +2,7 @@
from akarpov.blog.views import ( from akarpov.blog.views import (
comment, comment,
main_post_list_view,
post_create_view, post_create_view,
post_detail_view, post_detail_view,
post_list_view, post_list_view,
@ -12,7 +13,8 @@
app_name = "blog" app_name = "blog"
urlpatterns = [ urlpatterns = [
path("", post_list_view, name="post_list"), path("", main_post_list_view, name="post_list"),
path("all", post_list_view, name="all_posts_list"),
path("p/<str:slug>", post_detail_view, name="post"), path("p/<str:slug>", post_detail_view, name="post"),
path("create/", post_create_view, name="post_create"), path("create/", post_create_view, name="post_create"),
path("<str:slug>/edit", post_update_view, name="post_edit"), path("<str:slug>/edit", post_update_view, name="post_edit"),

View File

@ -7,7 +7,7 @@
from akarpov.blog.forms import PostForm from akarpov.blog.forms import PostForm
from akarpov.blog.models import Comment, Post, PostRating from akarpov.blog.models import Comment, Post, PostRating
from akarpov.blog.services import get_rating_bar from akarpov.blog.services import get_main_rating_posts, get_rating_bar
class PostDetailView(DetailView): class PostDetailView(DetailView):
@ -34,6 +34,34 @@ def get_context_data(self, **kwargs):
post_detail_view = PostDetailView.as_view() post_detail_view = PostDetailView.as_view()
class MainPostListView(ListView):
model = Post
template_name = "blog/list.html"
def get_queryset(self):
try:
if (
self.request.user.is_authenticated
and not self.request.user.is_superuser
):
posts = get_main_rating_posts() | Post.objects.filter(
creator=self.request.user
)
else:
posts = get_main_rating_posts()
params = self.request.GET
if "tag" in params:
posts = posts.filter(tags__name=params["tag"])
return posts
except Post.DoesNotExist:
return Post.objects.none()
main_post_list_view = MainPostListView.as_view()
class PostListView(ListView): class PostListView(ListView):
model = Post model = Post
template_name = "blog/list.html" template_name = "blog/list.html"

View File

@ -1,3 +1,4 @@
from rest_framework import serializers
from rest_framework.pagination import PageNumberPagination from rest_framework.pagination import PageNumberPagination
@ -17,3 +18,9 @@ class BigResultsSetPagination(PageNumberPagination):
page_size = 100 page_size = 100
page_size_query_param = "page_size" page_size_query_param = "page_size"
max_page_size = 1000 max_page_size = 1000
class RecursiveField(serializers.Serializer):
def to_representation(self, value):
serializer = self.parent.parent.__class__(value, context=self.context)
return serializer.data

View File

View File

@ -0,0 +1,7 @@
from django.urls import include, path
app_name = "tools"
urlpatterns = [
path("qr/", include("akarpov.tools.qr.api.urls", namespace="qr")),
]

View File

@ -25,7 +25,7 @@ def validate_token(self, token):
class UserPublicInfoSerializer(serializers.ModelSerializer): class UserPublicInfoSerializer(serializers.ModelSerializer):
url = serializers.HyperlinkedIdentityField( url = serializers.HyperlinkedIdentityField(
view_name="user_retrieve_username_api", lookup_field="username" view_name="api:users:user_retrieve_username_api", lookup_field="username"
) )
class Meta: class Meta:

View File

@ -7,6 +7,9 @@
UserRetrieveViewSet, UserRetrieveViewSet,
) )
app_name = "users_api"
urlpatterns = [ urlpatterns = [
path("", UserListViewSet.as_view(), name="user_list_api"), path("", UserListViewSet.as_view(), name="user_list_api"),
path( path(

View File

@ -3,6 +3,8 @@
from akarpov.users.api.views import UserRegisterViewSet from akarpov.users.api.views import UserRegisterViewSet
app_name = "api"
urlpatterns_v1 = [ urlpatterns_v1 = [
path( path(
"auth/", "auth/",
@ -17,11 +19,18 @@
), ),
path( path(
"users/", "users/",
include("akarpov.users.api.urls"), include("akarpov.users.api.urls", namespace="users"),
),
path(
"blog/",
include("akarpov.blog.api.urls", namespace="blog"),
), ),
path( path(
"tools/", "tools/",
include([path("qr/", include("akarpov.tools.qr.api.urls"))]), include(
"akarpov.tools.api.urls",
namespace="tools",
),
), ),
] ]