图片上传,评论系统

master
barney 2 years ago
parent 55e722c017
commit a2d714d85f
  1. 26
      article/migrations/0006_avatar_article_avatar.py
  2. 19
      article/migrations/0007_alter_article_avatar.py
  3. 14
      article/models.py
  4. 4
      article/permissions.py
  5. 56
      article/serializers.py
  6. 13
      article/views.py
  7. 0
      comment/__init__.py
  8. 3
      comment/admin.py
  9. 6
      comment/apps.py
  10. 32
      comment/migrations/0001_initial.py
  11. 0
      comment/migrations/__init__.py
  12. 30
      comment/models.py
  13. 26
      comment/permissions.py
  14. 15
      comment/serializers.py
  15. 3
      comment/tests.py
  16. 14
      comment/views.py
  17. BIN
      db.sqlite3
  18. 10
      drf_vue_blog/settings.py
  19. 10
      drf_vue_blog/urls.py
  20. BIN
      media/avatar/20220923/apple_00.png
  21. BIN
      media/avatar/20220923/apple_01.png
  22. BIN
      media/avatar/20220923/apple_02.png
  23. BIN
      media/avatar/20220923/apple_03.png
  24. BIN
      media/avatar/20220923/apple_04.png
  25. BIN
      media/avatar/20220923/apple_05.png
  26. BIN
      media/avatar/20220923/apple_06.png
  27. BIN
      media/avatar/20220923/apple_07.png
  28. BIN
      media/avatar/20220923/apple_08.png
  29. BIN
      media/avatar/20220923/apple_09.png
  30. BIN
      media/avatar/20220923/apple_10.png
  31. BIN
      media/avatar/20220923/apple_20.png

@ -0,0 +1,26 @@
# Generated by Django 4.1.1 on 2022-09-23 22:51
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('article', '0005_rename_tag_article_tags'),
]
operations = [
migrations.CreateModel(
name='Avatar',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('content', models.ImageField(upload_to='avatar/%Y%m%d')),
],
),
migrations.AddField(
model_name='article',
name='avatar',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='articles', to='article.avatar'),
),
]

@ -0,0 +1,19 @@
# Generated by Django 4.1.1 on 2022-09-23 23:57
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('article', '0006_avatar_article_avatar'),
]
operations = [
migrations.AlterField(
model_name='article',
name='avatar',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='article', to='article.avatar'),
),
]

@ -28,6 +28,11 @@ class Tag(models.Model):
return self.text return self.text
class Avatar(models.Model):
"""标题图"""
content = models.ImageField(upload_to='avatar/%Y%m%d')
# 博客文章 model # 博客文章 model
class Article(models.Model): class Article(models.Model):
class Meta: class Meta:
@ -59,6 +64,15 @@ class Article(models.Model):
related_name='articles', related_name='articles',
) )
# 标题图
avatar = models.ForeignKey(
Avatar,
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name='article' # 这里是1对1关系
)
# 标题 # 标题
title = models.CharField(max_length=100) title = models.CharField(max_length=100)
# 正文 # 正文

@ -1,4 +1,5 @@
from rest_framework import permissions from rest_framework import permissions
from rest_framework.permissions import BasePermission, SAFE_METHODS
class IsAdminUserOrReadOnly(permissions.BasePermission): class IsAdminUserOrReadOnly(permissions.BasePermission):
@ -14,3 +15,6 @@ class IsAdminUserOrReadOnly(permissions.BasePermission):
# 仅管理员可进行其他操作 # 仅管理员可进行其他操作
return request.user.is_superuser return request.user.is_superuser

@ -1,7 +1,7 @@
from rest_framework import serializers from rest_framework import serializers
from article.models import Article, Category, Tag from article.models import Article, Category, Tag, Avatar
from user_info.serializers import UserDescSerializer from user_info.serializers import UserDescSerializer
from comment.serializers import CommentSerializer
class CategorySerializer(serializers.ModelSerializer): class CategorySerializer(serializers.ModelSerializer):
"""所有分类的序列化器""" """所有分类的序列化器"""
@ -16,6 +16,14 @@ class CategorySerializer(serializers.ModelSerializer):
read_only_fields = ['created'] read_only_fields = ['created']
class AvatarSerializer(serializers.ModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='avatar-detail')
class Meta:
model = Avatar
fields = '__all__'
class ArticleBaseSerializer(serializers.HyperlinkedModelSerializer): class ArticleBaseSerializer(serializers.HyperlinkedModelSerializer):
"""将原来的ArticleSerializer抽象出一个父类""" """将原来的ArticleSerializer抽象出一个父类"""
author = UserDescSerializer(read_only=True) author = UserDescSerializer(read_only=True)
@ -32,10 +40,45 @@ class ArticleBaseSerializer(serializers.HyperlinkedModelSerializer):
slug_field='text', slug_field='text',
) )
# 图片字段
avatar = AvatarSerializer(read_only=True)
avatar_id = serializers.IntegerField(
write_only=True,
allow_null=True,
required=False,
)
# 自定义错误信息
default_error_messages = {
'incorrect_avatar_id': 'Avatar with id {value} not exists.',
'incorrect_category_id': 'Category with id {value} not exists.',
'default': 'No more message here..'
}
def check_obj_exists_or_fail(self, model, value, message='default'):
if not self.default_error_messages.get(message, None):
message = 'default'
# 不为None但是不存在该id返回错误信息
if not model.objects.filter(id=value).exists() and value is not None:
# 若不存在则调用钩子方法fail()引发错误
self.fail(message, value=value)
# 验证图片id是否正确
def validate_avatar_id(self, value):
self.check_obj_exists_or_fail(
model=Avatar,
value=value,
message='incorrect_avatar_id'
)
return value
# 验证category_id是否正确 # 验证category_id是否正确
def validate_category_id(self, value): def validate_category_id(self, value):
if not Category.objects.filter(id=value).exists() and value is not None: self.check_obj_exists_or_fail(
raise serializers.ValidationError("Category with id {} not exist.".format(value)) model=Category,
value=value,
message='incorrect_category_id'
)
return value return value
def to_internal_value(self, data): def to_internal_value(self, data):
@ -58,6 +101,8 @@ class ArticleSerializer(ArticleBaseSerializer):
class ArticleDetailSerializer(ArticleBaseSerializer): class ArticleDetailSerializer(ArticleBaseSerializer):
id = serializers.IntegerField(read_only=True)
comments = CommentSerializer(many=True, read_only=True)
# 渲染后的正文 # 渲染后的正文
body_html = serializers.SerializerMethodField() body_html = serializers.SerializerMethodField()
# 渲染后的目录 # 渲染后的目录
@ -102,10 +147,10 @@ class CategoryDetailSerializer(serializers.ModelSerializer):
class TagSerializer(serializers.ModelSerializer): class TagSerializer(serializers.ModelSerializer):
"""所有标签序列化器"""
# 显示url # 显示url
# url = serializers.HyperlinkedIdentityField(view_name='tag-detail') # url = serializers.HyperlinkedIdentityField(view_name='tag-detail')
"""所有标签序列化器"""
class Meta: class Meta:
model = Tag model = Tag
@ -126,3 +171,4 @@ class TagSerializer(serializers.ModelSerializer):
return super(TagSerializer, self).update(instance, validated_data) return super(TagSerializer, self).update(instance, validated_data)

@ -1,7 +1,7 @@
from rest_framework import viewsets from rest_framework import viewsets
from article.serializers import ArticleSerializer, CategorySerializer, CategoryDetailSerializer from article.serializers import ArticleSerializer, CategorySerializer, CategoryDetailSerializer
from article.serializers import TagSerializer,ArticleDetailSerializer from article.serializers import TagSerializer,ArticleDetailSerializer, AvatarSerializer
from article.models import Article, Category, Tag from article.models import Article, Category, Tag , Avatar
from article.permissions import IsAdminUserOrReadOnly from article.permissions import IsAdminUserOrReadOnly
from rest_framework.filters import SearchFilter from rest_framework.filters import SearchFilter
@ -47,4 +47,11 @@ class TagViewSet(viewsets.ModelViewSet):
"""标签视图集""" """标签视图集"""
queryset = Tag.objects.all() queryset = Tag.objects.all()
serializer_class = TagSerializer serializer_class = TagSerializer
permission_classes = [IsAdminUserOrReadOnly] permission_classes = [IsAdminUserOrReadOnly]
class AvatarViewSet(viewsets.ModelViewSet):
"""标题图片视图集"""
queryset = Avatar.objects.all()
serializer_class = AvatarSerializer
permission_classes = [IsAdminUserOrReadOnly]

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

@ -0,0 +1,6 @@
from django.apps import AppConfig
class CommentConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'comment'

@ -0,0 +1,32 @@
# Generated by Django 4.1.1 on 2022-09-23 23:57
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('article', '0007_alter_article_avatar'),
]
operations = [
migrations.CreateModel(
name='Comment',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('content', models.TextField()),
('created', models.DateTimeField(default=django.utils.timezone.now)),
('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='article.article')),
('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to=settings.AUTH_USER_MODEL)),
],
options={
'ordering': ['-created'],
},
),
]

@ -0,0 +1,30 @@
from django.db import models
from django.utils import timezone
from article.models import Article
from django.contrib.auth.models import User
class Comment(models.Model):
# 评论人
author = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name='comments',
)
# 文章
article = models.ForeignKey(
Article,
on_delete=models.CASCADE,
related_name='comments',
)
# 评论内容
content = models.TextField()
# 评论时间
created = models.DateTimeField(default=timezone.now)
class Meta:
ordering = ['-created']
def __str__(self):
# 展示前20个字符
return self.content[:20]

@ -0,0 +1,26 @@
from rest_framework.permissions import BasePermission, SAFE_METHODS
class IsOwnerOrReadOnly(BasePermission):
"""
只有作者本人可以修改其他人只能查看
"""
message = "You must be the owner to update"
def safe_methods_or_owner(self, request, func):
if request.method in SAFE_METHODS:
return True
return func()
def has_permission(self, request, view):
return self.safe_methods_or_owner(
request,
lambda: request.user.is_authenticated
)
def has_object_permission(self, request, view, obj):
return self.safe_methods_or_owner(
request,
lambda: obj.author == request.user # 验证当前评论的作者和当前登录的用户是否为同一个人
)

@ -0,0 +1,15 @@
from rest_framework import serializers
from user_info.serializers import UserDescSerializer
from comment.models import Comment
class CommentSerializer(serializers.ModelSerializer):
"""评论的序列化器"""
url = serializers.HyperlinkedIdentityField(view_name='comment-detail')
author = UserDescSerializer(read_only=True)
class Meta:
model = Comment
fields = '__all__'
extra_kwargs = {'created': {'read_only': True}}

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

@ -0,0 +1,14 @@
from rest_framework import viewsets
from comment.models import Comment
from comment.serializers import CommentSerializer
from comment.permissions import IsOwnerOrReadOnly
class CommentViewSet(viewsets.ModelViewSet):
queryset = Comment.objects.all()
serializer_class = CommentSerializer
permission_classes = [IsOwnerOrReadOnly]
def perform_create(self, serializer):
serializer.save(author=self.request.user)

Binary file not shown.

@ -1,5 +1,5 @@
from pathlib import Path from pathlib import Path
import os
# Build paths inside the project like this: BASE_DIR / 'subdir'. # Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent BASE_DIR = Path(__file__).resolve().parent.parent
@ -29,6 +29,7 @@ INSTALLED_APPS = [
'article', 'article',
'user_info', 'user_info',
'django_filters', 'django_filters',
'comment',
] ]
MIDDLEWARE = [ MIDDLEWARE = [
@ -115,11 +116,14 @@ STATIC_URL = '/static/'
# 使用django_rest_framework中的分页功能 # 使用django_rest_framework中的分页功能
REST_FRAMEWORK = { REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
# 每页3条记录 # 每页5条记录
'PAGE_SIZE': 3, 'PAGE_SIZE': 5,
# 使用django-filter后端过滤引擎 # 使用django-filter后端过滤引擎
'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'], 'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'],
} }
# 媒体文件
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

@ -2,12 +2,16 @@ from django.contrib import admin
from django.urls import path, include from django.urls import path, include
from rest_framework.routers import DefaultRouter from rest_framework.routers import DefaultRouter
from article import views from article import views
from django.conf import settings
from django.conf.urls.static import static
from comment.views import CommentViewSet
router = DefaultRouter() router = DefaultRouter()
router.register(r'article', views.ArticleViewSet) router.register(r'article', views.ArticleViewSet)
router.register(r'category', views.CategoryViewSet) router.register(r'category', views.CategoryViewSet)
router.register(r'tag', views.TagViewSet) router.register(r'tag', views.TagViewSet)
router.register(r'avatar', views.AvatarViewSet)
router.register(r'comment', CommentViewSet)
urlpatterns = [ urlpatterns = [
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
@ -15,3 +19,7 @@ urlpatterns = [
path('api/', include(router.urls)) path('api/', include(router.urls))
# path('api/article/', include('article.urls', namespace='article')), # path('api/article/', include('article.urls', namespace='article')),
] ]
# 把媒体文件的路由注册了
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1006 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 943 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Loading…
Cancel
Save