图片上传,评论系统

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
class Avatar(models.Model):
"""标题图"""
content = models.ImageField(upload_to='avatar/%Y%m%d')
# 博客文章 model
class Article(models.Model):
class Meta:
@ -59,6 +64,15 @@ class Article(models.Model):
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)
# 正文

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

@ -1,7 +1,7 @@
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 comment.serializers import CommentSerializer
class CategorySerializer(serializers.ModelSerializer):
"""所有分类的序列化器"""
@ -16,6 +16,14 @@ class CategorySerializer(serializers.ModelSerializer):
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):
"""将原来的ArticleSerializer抽象出一个父类"""
author = UserDescSerializer(read_only=True)
@ -32,10 +40,45 @@ class ArticleBaseSerializer(serializers.HyperlinkedModelSerializer):
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是否正确
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))
self.check_obj_exists_or_fail(
model=Category,
value=value,
message='incorrect_category_id'
)
return value
def to_internal_value(self, data):
@ -58,6 +101,8 @@ class ArticleSerializer(ArticleBaseSerializer):
class ArticleDetailSerializer(ArticleBaseSerializer):
id = serializers.IntegerField(read_only=True)
comments = CommentSerializer(many=True, read_only=True)
# 渲染后的正文
body_html = serializers.SerializerMethodField()
# 渲染后的目录
@ -102,10 +147,10 @@ class CategoryDetailSerializer(serializers.ModelSerializer):
class TagSerializer(serializers.ModelSerializer):
"""所有标签序列化器"""
# 显示url
# url = serializers.HyperlinkedIdentityField(view_name='tag-detail')
"""所有标签序列化器"""
class Meta:
model = Tag
@ -126,3 +171,4 @@ class TagSerializer(serializers.ModelSerializer):
return super(TagSerializer, self).update(instance, validated_data)

@ -1,7 +1,7 @@
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.serializers import TagSerializer,ArticleDetailSerializer, AvatarSerializer
from article.models import Article, Category, Tag , Avatar
from article.permissions import IsAdminUserOrReadOnly
from rest_framework.filters import SearchFilter
@ -47,4 +47,11 @@ class TagViewSet(viewsets.ModelViewSet):
"""标签视图集"""
queryset = Tag.objects.all()
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
import os
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
@ -29,6 +29,7 @@ INSTALLED_APPS = [
'article',
'user_info',
'django_filters',
'comment',
]
MIDDLEWARE = [
@ -115,11 +116,14 @@ STATIC_URL = '/static/'
# 使用django_rest_framework中的分页功能
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
# 每页3条记录
'PAGE_SIZE': 3,
# 每页5条记录
'PAGE_SIZE': 5,
# 使用django-filter后端过滤引擎
'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'

@ -2,12 +2,16 @@ from django.contrib import admin
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from article import views
from django.conf import settings
from django.conf.urls.static import static
from comment.views import CommentViewSet
router = DefaultRouter()
router.register(r'article', views.ArticleViewSet)
router.register(r'category', views.CategoryViewSet)
router.register(r'tag', views.TagViewSet)
router.register(r'avatar', views.AvatarViewSet)
router.register(r'comment', CommentViewSet)
urlpatterns = [
path('admin/', admin.site.urls),
@ -15,3 +19,7 @@ urlpatterns = [
path('api/', include(router.urls))
# 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