parent
947c5476c4
commit
55e722c017
9 changed files with 314 additions and 102 deletions
@ -0,0 +1,40 @@ |
|||||||
|
# Generated by Django 4.1.1 on 2022-09-23 16:26 |
||||||
|
|
||||||
|
from django.db import migrations, models |
||||||
|
import django.db.models.deletion |
||||||
|
import django.utils.timezone |
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration): |
||||||
|
|
||||||
|
dependencies = [ |
||||||
|
('article', '0002_article_author'), |
||||||
|
] |
||||||
|
|
||||||
|
operations = [ |
||||||
|
migrations.CreateModel( |
||||||
|
name='Category', |
||||||
|
fields=[ |
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
||||||
|
('title', models.CharField(max_length=100)), |
||||||
|
('created', models.DateTimeField(default=django.utils.timezone.now)), |
||||||
|
], |
||||||
|
options={ |
||||||
|
'ordering': ['-created'], |
||||||
|
}, |
||||||
|
), |
||||||
|
migrations.AlterModelOptions( |
||||||
|
name='article', |
||||||
|
options={'ordering': ['-created']}, |
||||||
|
), |
||||||
|
migrations.AlterField( |
||||||
|
model_name='article', |
||||||
|
name='id', |
||||||
|
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), |
||||||
|
), |
||||||
|
migrations.AddField( |
||||||
|
model_name='article', |
||||||
|
name='category', |
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='articles', to='article.category'), |
||||||
|
), |
||||||
|
] |
@ -0,0 +1,28 @@ |
|||||||
|
# Generated by Django 4.1.1 on 2022-09-23 18:05 |
||||||
|
|
||||||
|
from django.db import migrations, models |
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration): |
||||||
|
|
||||||
|
dependencies = [ |
||||||
|
('article', '0003_category_alter_article_options_alter_article_id_and_more'), |
||||||
|
] |
||||||
|
|
||||||
|
operations = [ |
||||||
|
migrations.CreateModel( |
||||||
|
name='Tag', |
||||||
|
fields=[ |
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
||||||
|
('text', models.CharField(max_length=30)), |
||||||
|
], |
||||||
|
options={ |
||||||
|
'ordering': ['-id'], |
||||||
|
}, |
||||||
|
), |
||||||
|
migrations.AddField( |
||||||
|
model_name='article', |
||||||
|
name='tag', |
||||||
|
field=models.ManyToManyField(blank=True, related_name='articles', to='article.tag'), |
||||||
|
), |
||||||
|
] |
@ -0,0 +1,18 @@ |
|||||||
|
# Generated by Django 4.1.1 on 2022-09-23 18:22 |
||||||
|
|
||||||
|
from django.db import migrations |
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration): |
||||||
|
|
||||||
|
dependencies = [ |
||||||
|
('article', '0004_tag_article_tag'), |
||||||
|
] |
||||||
|
|
||||||
|
operations = [ |
||||||
|
migrations.RenameField( |
||||||
|
model_name='article', |
||||||
|
old_name='tag', |
||||||
|
new_name='tags', |
||||||
|
), |
||||||
|
] |
@ -1,29 +1,128 @@ |
|||||||
from rest_framework import serializers |
from rest_framework import serializers |
||||||
from article.models import Article |
from article.models import Article, Category, Tag |
||||||
from user_info.serializers import UserDescSerializer |
from user_info.serializers import UserDescSerializer |
||||||
|
|
||||||
|
|
||||||
# 返回文章列表或创建一篇文章 |
class CategorySerializer(serializers.ModelSerializer): |
||||||
class ArticleListSerializer(serializers.ModelSerializer): |
"""所有分类的序列化器""" |
||||||
|
# 将路由间的表示转换为超链接 |
||||||
|
# category-detail是自动注册路由时, Router默认帮你设置的详情页面的名称 |
||||||
|
url = serializers.HyperlinkedIdentityField(view_name='category-detail') |
||||||
|
|
||||||
|
class Meta: |
||||||
|
model = Category |
||||||
|
fields = '__all__' |
||||||
|
# 创建时间不能修改 |
||||||
|
read_only_fields = ['created'] |
||||||
|
|
||||||
|
|
||||||
|
class ArticleBaseSerializer(serializers.HyperlinkedModelSerializer): |
||||||
|
"""将原来的ArticleSerializer抽象出一个父类""" |
||||||
author = UserDescSerializer(read_only=True) |
author = UserDescSerializer(read_only=True) |
||||||
# 使用article的url.py中的view --> detail |
# 希望文章接口不仅仅只返回分类的id而已,所以需要显式指定category,将其变成一个嵌套数据, |
||||||
url = serializers.HyperlinkedIdentityField(view_name="article:detail") |
# 分类的嵌套序列化字段 |
||||||
|
category = CategorySerializer(read_only=True) |
||||||
|
# 显示指定category的id字段,用于创建/更新category外键 |
||||||
|
category_id = serializers.IntegerField(write_only=True, allow_null=True, required=False) |
||||||
|
# 新增tag字段, 直接显示Tag的text字段 |
||||||
|
tags = serializers.SlugRelatedField( |
||||||
|
queryset=Tag.objects.all(), |
||||||
|
many=True, |
||||||
|
required=False, |
||||||
|
slug_field='text', |
||||||
|
) |
||||||
|
|
||||||
|
# 验证category_id是否正确 |
||||||
|
def validate_category_id(self, value): |
||||||
|
if not Category.objects.filter(id=value).exists() and value is not None: |
||||||
|
raise serializers.ValidationError("Category with id {} not exist.".format(value)) |
||||||
|
return value |
||||||
|
|
||||||
|
def to_internal_value(self, data): |
||||||
|
tags_data = data.get('tags') |
||||||
|
if isinstance(tags_data, list): |
||||||
|
for text in tags_data: |
||||||
|
# 不存在该标签则创建它 |
||||||
|
if not Tag.objects.filter(text=text).exists(): |
||||||
|
Tag.objects.create(text=text) |
||||||
|
return super().to_internal_value(data) |
||||||
|
|
||||||
|
|
||||||
|
class ArticleSerializer(ArticleBaseSerializer): |
||||||
|
"""文章序列化器""" |
||||||
|
class Meta: |
||||||
|
model = Article |
||||||
|
fields = '__all__' |
||||||
|
# body字段只可写不可见 |
||||||
|
extra_kwargs = {'body': {'write_only': True}} |
||||||
|
|
||||||
|
|
||||||
|
class ArticleDetailSerializer(ArticleBaseSerializer): |
||||||
|
# 渲染后的正文 |
||||||
|
body_html = serializers.SerializerMethodField() |
||||||
|
# 渲染后的目录 |
||||||
|
toc_html = serializers.SerializerMethodField() |
||||||
|
|
||||||
|
def get_body_html(self, obj): |
||||||
|
return obj.get_md()[0] |
||||||
|
|
||||||
|
def get_toc_html(self, obj): |
||||||
|
return obj.get_md()[1] |
||||||
|
|
||||||
|
class Meta: |
||||||
|
model = Article |
||||||
|
fields = '__all__' |
||||||
|
|
||||||
|
|
||||||
|
class ArticleCategoryDetailSerializer(serializers.ModelSerializer): |
||||||
|
"""分类详情的嵌套序列化器""" |
||||||
|
url = serializers.HyperlinkedIdentityField(view_name='article-detail') |
||||||
|
|
||||||
class Meta: |
class Meta: |
||||||
model = Article |
model = Article |
||||||
fields = [ |
fields = [ |
||||||
'url', |
'url', |
||||||
'title', |
'title', |
||||||
|
] |
||||||
|
|
||||||
|
|
||||||
|
class CategoryDetailSerializer(serializers.ModelSerializer): |
||||||
|
"""具体的分类详情页不显示url""" |
||||||
|
# 显示某个分类下的所有文章 |
||||||
|
articles = ArticleCategoryDetailSerializer(many=True, read_only=True) |
||||||
|
|
||||||
|
class Meta: |
||||||
|
model = Category |
||||||
|
fields = [ |
||||||
|
'id', |
||||||
|
'title', |
||||||
'created', |
'created', |
||||||
'body', |
'articles', |
||||||
'author', |
|
||||||
] |
] |
||||||
# read_only_fields = ['author'] |
|
||||||
|
|
||||||
# 返回文章详情 |
|
||||||
|
|
||||||
|
class TagSerializer(serializers.ModelSerializer): |
||||||
|
# 显示url |
||||||
|
# url = serializers.HyperlinkedIdentityField(view_name='tag-detail') |
||||||
|
|
||||||
|
"""所有标签序列化器""" |
||||||
|
|
||||||
class ArticleDetailSerializer(serializers.ModelSerializer): |
|
||||||
class Meta: |
class Meta: |
||||||
model = Article |
model = Tag |
||||||
fields = '__all__' |
fields = '__all__' |
||||||
|
|
||||||
|
# 创建或者更新前检查是否存在该tag |
||||||
|
def check_tag_obj_exists(self, validated_data): |
||||||
|
text = validated_data.get('text') |
||||||
|
if Tag.objects.filter(text=text).exists(): |
||||||
|
raise serializers.ValidationError('Tag with text {} exists.'.format(text)) |
||||||
|
|
||||||
|
def create(self, validated_data): |
||||||
|
self.check_tag_obj_exists(validated_data) |
||||||
|
return super(TagSerializer, self).create(validated_data) |
||||||
|
|
||||||
|
def update(self, instance, validated_data): |
||||||
|
self.check_tag_obj_exists(validated_data) |
||||||
|
return super(TagSerializer, self).update(instance, validated_data) |
||||||
|
|
||||||
|
|
||||||
|
@ -1,101 +1,50 @@ |
|||||||
from article.models import Article |
from rest_framework import viewsets |
||||||
from article.serializers import ArticleListSerializer, ArticleDetailSerializer |
from article.serializers import ArticleSerializer, CategorySerializer, CategoryDetailSerializer |
||||||
from rest_framework.decorators import api_view |
from article.serializers import TagSerializer,ArticleDetailSerializer |
||||||
from rest_framework.response import Response |
from article.models import Article, Category, Tag |
||||||
from rest_framework.views import APIView |
|
||||||
from django.http import Http404 |
|
||||||
from rest_framework import mixins |
|
||||||
from rest_framework import generics |
|
||||||
from rest_framework import status |
|
||||||
from article.permissions import IsAdminUserOrReadOnly |
from article.permissions import IsAdminUserOrReadOnly |
||||||
|
from rest_framework.filters import SearchFilter |
||||||
|
|
||||||
|
|
||||||
@api_view(['GET', 'POST']) |
class ArticleViewSet(viewsets.ModelViewSet): |
||||||
def article_list(request): |
"""文章视图集""" |
||||||
# 获取所有文章列表 |
queryset = Article.objects.all() |
||||||
if request.method == 'GET': |
serializer_class = ArticleSerializer |
||||||
articles = Article.objects.all() |
permission_classes = [IsAdminUserOrReadOnly] |
||||||
serializer = ArticleListSerializer(articles, many=True) |
filter_backends = [SearchFilter, ] |
||||||
return Response(serializer.data) |
|
||||||
# 创建新的文章 |
|
||||||
elif request.method == 'POST': |
|
||||||
serializer = ArticleListSerializer(data=request.data) |
|
||||||
if serializer.is_valid(): |
|
||||||
serializer.save() |
|
||||||
return Response(serializer.data, status=status.HTTP_201_CREATED) |
|
||||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) |
|
||||||
|
|
||||||
|
# http://127.0.0.1:8000/api/article/?author__username=admin&title=第一篇文章 |
||||||
|
# filter_fields = ['title', 'author__username',] # 精确查询 |
||||||
|
|
||||||
# 和article_list()功能相同 |
# 下面两种字段都可以使用模糊查询 |
||||||
class ArticleList(generics.ListCreateAPIView): |
search_fields = ('title', 'author__username') |
||||||
# 添加用户权限 |
|
||||||
permission_classes = [IsAdminUserOrReadOnly] |
|
||||||
queryset = Article.objects.all() |
|
||||||
serializer_class = ArticleListSerializer |
|
||||||
|
|
||||||
def perform_create(self, serializer): |
def perform_create(self, serializer): |
||||||
# 在序列化数据真正保存之前调用 |
# 在创建文章前,提供了视图集无法自行推断的用户外键字段。 |
||||||
serializer.save(author=self.request.user) |
serializer.save(author=self.request.user) |
||||||
|
|
||||||
|
def get_serializer_class(self): |
||||||
|
if self.action == 'list': |
||||||
|
return ArticleSerializer |
||||||
|
else: |
||||||
|
return ArticleDetailSerializer |
||||||
|
|
||||||
# 第一种版本 |
|
||||||
|
|
||||||
# class ArticleDetail(APIView): |
class CategoryViewSet(viewsets.ModelViewSet): |
||||||
# """文章详情视图""" |
"""分类视图集""" |
||||||
# def get_object(self, pk): |
queryset = Category.objects.all() |
||||||
# """获取单个文章对象""" |
serializer_class = CategorySerializer |
||||||
# try: |
permission_classes = [IsAdminUserOrReadOnly] |
||||||
# # pk代表主键 |
|
||||||
# return Article.objects.get(pk=pk) |
|
||||||
# except: |
|
||||||
# raise Http404 |
|
||||||
# |
|
||||||
# def get(self, request, pk): |
|
||||||
# article = self.get_object(pk) |
|
||||||
# serializer = ArticleDetailSerializer(article) |
|
||||||
# # 返回Json数据 |
|
||||||
# return Response(serializer.data) |
|
||||||
# |
|
||||||
# def put(self, request, pk): |
|
||||||
# article = self.get_object(pk) |
|
||||||
# serializer = ArticleDetailSerializer(article, data=request.data) |
|
||||||
# # 验证提交的数据是否合法 |
|
||||||
# # 不合法就返回400 |
|
||||||
# if serializer.is_valid(): |
|
||||||
# # 序列化器将持有的数据反序列化后 |
|
||||||
# # 保存到数据库 |
|
||||||
# serializer.save() |
|
||||||
# return Response(serializer.data) |
|
||||||
# return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) |
|
||||||
# |
|
||||||
# def delete(self, request, pk): |
|
||||||
# article = self.get_object(pk=pk) |
|
||||||
# article.delete() |
|
||||||
# # 删除成功后返回204 |
|
||||||
# return Response(status=status.HTTP_204_NO_CONTENT) |
|
||||||
|
|
||||||
# 第二种版本 |
def get_serializer_class(self): |
||||||
|
if self.action == 'list': |
||||||
|
return CategorySerializer |
||||||
|
else: |
||||||
|
return CategoryDetailSerializer |
||||||
|
|
||||||
# class ArticleDetail(mixins.RetrieveModelMixin, |
|
||||||
# mixins.UpdateModelMixin, |
|
||||||
# mixins.DestroyModelMixin, |
|
||||||
# generics.GenericAPIView): |
|
||||||
# """文章详情视图""" |
|
||||||
# queryset = Article.objects.all() |
|
||||||
# # 序列化类 |
|
||||||
# serializer_class = ArticleDetailSerializer |
|
||||||
# |
|
||||||
# def get(self, request, *args, **kwargs): |
|
||||||
# return self.retrieve(request, *args, **kwargs) |
|
||||||
# |
|
||||||
# def put(self, request, *args, **kwargs): |
|
||||||
# return self.update(request, *args, **kwargs) |
|
||||||
# |
|
||||||
# def delete(self, request, *args, **kwargs): |
|
||||||
# return self.destroy(request, *args, **kwargs) |
|
||||||
|
|
||||||
# ArticleDetail类还可以简化为下面的代码 |
class TagViewSet(viewsets.ModelViewSet): |
||||||
class ArticleDetail(generics.RetrieveUpdateDestroyAPIView): |
"""标签视图集""" |
||||||
permission_classes = [IsAdminUserOrReadOnly] |
queryset = Tag.objects.all() |
||||||
queryset = Article.objects.all() |
serializer_class = TagSerializer |
||||||
serializer_class = ArticleDetailSerializer |
permission_classes = [IsAdminUserOrReadOnly] |
Binary file not shown.
@ -1,8 +1,17 @@ |
|||||||
from django.contrib import admin |
from django.contrib import admin |
||||||
from django.urls import path, include |
from django.urls import path, include |
||||||
|
from rest_framework.routers import DefaultRouter |
||||||
|
from article import views |
||||||
|
|
||||||
|
router = DefaultRouter() |
||||||
|
router.register(r'article', views.ArticleViewSet) |
||||||
|
router.register(r'category', views.CategoryViewSet) |
||||||
|
router.register(r'tag', views.TagViewSet) |
||||||
|
|
||||||
|
|
||||||
urlpatterns = [ |
urlpatterns = [ |
||||||
path('admin/', admin.site.urls), |
path('admin/', admin.site.urls), |
||||||
path('api-auth/', include('rest_framework.urls')), |
path('api-auth/', include('rest_framework.urls')), |
||||||
path('api/article/', include('article.urls', namespace='article')), |
path('api/', include(router.urls)) |
||||||
|
# path('api/article/', include('article.urls', namespace='article')), |
||||||
] |
] |
||||||
|
Loading…
Reference in new issue