过滤文章,分类,标签,markdown正文

master
barney 2 years ago
parent 947c5476c4
commit 55e722c017
  1. 40
      article/migrations/0003_category_alter_article_options_alter_article_id_and_more.py
  2. 28
      article/migrations/0004_tag_article_tag.py
  3. 18
      article/migrations/0005_rename_tag_article_tags.py
  4. 62
      article/models.py
  5. 123
      article/serializers.py
  6. 123
      article/views.py
  7. BIN
      db.sqlite3
  8. 11
      drf_vue_blog/settings.py
  9. 11
      drf_vue_blog/urls.py

@ -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,10 +1,39 @@
from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User
from markdown import Markdown
class Category(models.Model):
"""文章分类"""
# 分类名称
title = models.CharField(max_length=100)
created = models.DateTimeField(default=timezone.now)
class Meta:
ordering = ['-created']
def __str__(self):
return self.title
class Tag(models.Model):
"""文章标签"""
text = models.CharField(max_length=30)
class Meta:
ordering = ['-id']
def __str__(self):
return self.text
# 博客文章 model
class Article(models.Model):
class Meta:
# 按创建时间降序排列
ordering = ['-created']
# 作者
author = models.ForeignKey(
User,
@ -12,6 +41,24 @@ class Article(models.Model):
on_delete=models.CASCADE,
related_name='articles'
)
# 分类
category = models.ForeignKey(
Category,
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name='articles'
)
# 标签
# 一篇文章可以有多个tag,一个tag可以属于多篇文章
tags = models.ManyToManyField(
Tag,
blank=True,
related_name='articles',
)
# 标题
title = models.CharField(max_length=100)
# 正文
@ -23,3 +70,18 @@ class Article(models.Model):
def __str__(self):
return self.title
# 新增方法,将body转换为带html标签的正文
def get_md(self):
md = Markdown(
extensions=[
'markdown.extensions.extra',
'markdown.extensions.codehilite',
'markdown.extensions.toc',
]
)
md_body = md.convert(self.body)
return md_body, md.toc

@ -1,29 +1,128 @@
from rest_framework import serializers
from article.models import Article
from article.models import Article, Category, Tag
from user_info.serializers import UserDescSerializer
# 返回文章列表或创建一篇文章
class ArticleListSerializer(serializers.ModelSerializer):
class CategorySerializer(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)
# 使用article的url.py中的view --> detail
url = serializers.HyperlinkedIdentityField(view_name="article:detail")
# 希望文章接口不仅仅只返回分类的id而已,所以需要显式指定category,将其变成一个嵌套数据,
# 分类的嵌套序列化字段
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:
model = Article
fields = [
'url',
'title',
]
class CategoryDetailSerializer(serializers.ModelSerializer):
"""具体的分类详情页不显示url"""
# 显示某个分类下的所有文章
articles = ArticleCategoryDetailSerializer(many=True, read_only=True)
class Meta:
model = Category
fields = [
'id',
'title',
'created',
'body',
'author',
'articles',
]
# read_only_fields = ['author']
# 返回文章详情
class TagSerializer(serializers.ModelSerializer):
# 显示url
# url = serializers.HyperlinkedIdentityField(view_name='tag-detail')
"""所有标签序列化器"""
class ArticleDetailSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = '__all__'
model = Tag
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 article.serializers import ArticleListSerializer, ArticleDetailSerializer
from rest_framework.decorators import api_view
from rest_framework.response import Response
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 rest_framework import viewsets
from article.serializers import ArticleSerializer, CategorySerializer, CategoryDetailSerializer
from article.serializers import TagSerializer,ArticleDetailSerializer
from article.models import Article, Category, Tag
from article.permissions import IsAdminUserOrReadOnly
from rest_framework.filters import SearchFilter
@api_view(['GET', 'POST'])
def article_list(request):
# 获取所有文章列表
if request.method == 'GET':
articles = Article.objects.all()
serializer = ArticleListSerializer(articles, many=True)
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)
class ArticleViewSet(viewsets.ModelViewSet):
"""文章视图集"""
queryset = Article.objects.all()
serializer_class = ArticleSerializer
permission_classes = [IsAdminUserOrReadOnly]
filter_backends = [SearchFilter, ]
# http://127.0.0.1:8000/api/article/?author__username=admin&title=第一篇文章
# filter_fields = ['title', 'author__username',] # 精确查询
# 和article_list()功能相同
class ArticleList(generics.ListCreateAPIView):
# 添加用户权限
permission_classes = [IsAdminUserOrReadOnly]
queryset = Article.objects.all()
serializer_class = ArticleListSerializer
# 下面两种字段都可以使用模糊查询
search_fields = ('title', 'author__username')
def perform_create(self, serializer):
# 在序列化数据真正保存之前调用
# 在创建文章前,提供了视图集无法自行推断的用户外键字段。
serializer.save(author=self.request.user)
def get_serializer_class(self):
if self.action == 'list':
return ArticleSerializer
else:
return ArticleDetailSerializer
# 第一种版本
# class ArticleDetail(APIView):
# """文章详情视图"""
# def get_object(self, pk):
# """获取单个文章对象"""
# try:
# # 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)
class CategoryViewSet(viewsets.ModelViewSet):
"""分类视图集"""
queryset = Category.objects.all()
serializer_class = CategorySerializer
permission_classes = [IsAdminUserOrReadOnly]
# 第二种版本
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 ArticleDetail(generics.RetrieveUpdateDestroyAPIView):
permission_classes = [IsAdminUserOrReadOnly]
queryset = Article.objects.all()
serializer_class = ArticleDetailSerializer
class TagViewSet(viewsets.ModelViewSet):
"""标签视图集"""
queryset = Tag.objects.all()
serializer_class = TagSerializer
permission_classes = [IsAdminUserOrReadOnly]

Binary file not shown.

@ -28,6 +28,7 @@ INSTALLED_APPS = [
'rest_framework',
'article',
'user_info',
'django_filters',
]
MIDDLEWARE = [
@ -114,5 +115,11 @@ STATIC_URL = '/static/'
# 使用django_rest_framework中的分页功能
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 3, # 每页3条记录
}
# 每页3条记录
'PAGE_SIZE': 3,
# 使用django-filter后端过滤引擎
'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'],
}
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

@ -1,8 +1,17 @@
from django.contrib import admin
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 = [
path('admin/', admin.site.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…
Cancel
Save