diff --git a/article/models.py b/article/models.py index 0e4e054..7ac92d1 100644 --- a/article/models.py +++ b/article/models.py @@ -41,9 +41,13 @@ class Article(models.Model): # 作者 author = models.ForeignKey( - User, + User, # 父表 null=True, on_delete=models.CASCADE, + # 一个作者可以对应多篇文章,作者是文章的一个外键 + # 访问一个作者的所有文章可以用user.article_set.all() + # 也可以用到下面的related_name, user.articles.all(),和上面的语句作用相同 + # 两者不能同时使用 related_name='articles' ) diff --git a/article/urls.py b/article/urls.py deleted file mode 100644 index b244e33..0000000 --- a/article/urls.py +++ /dev/null @@ -1,9 +0,0 @@ -from django.urls import path -from article import views - -app_name = 'article' - -urlpatterns = [ - path('', views.ArticleList.as_view(), name='list'), - path('/', views.ArticleDetail.as_view(), name='detail') -] diff --git a/comment/migrations/0002_comment_parent.py b/comment/migrations/0002_comment_parent.py new file mode 100644 index 0000000..9647b0a --- /dev/null +++ b/comment/migrations/0002_comment_parent.py @@ -0,0 +1,19 @@ +# Generated by Django 4.1.1 on 2022-09-24 18:36 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('comment', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='comment', + name='parent', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='children', to='comment.comment'), + ), + ] diff --git a/comment/models.py b/comment/models.py index 8d1d5c5..375f536 100644 --- a/comment/models.py +++ b/comment/models.py @@ -5,6 +5,13 @@ from django.contrib.auth.models import User class Comment(models.Model): + parent = models.ForeignKey( + 'self', + null=True, + blank=True, + on_delete=models.SET_NULL, + related_name='children', + ) # 评论人 author = models.ForeignKey( User, diff --git a/comment/permissions.py b/comment/permissions.py index 1da9b33..dd4070c 100644 --- a/comment/permissions.py +++ b/comment/permissions.py @@ -24,3 +24,4 @@ class IsOwnerOrReadOnly(BasePermission): lambda: obj.author == request.user # 验证当前评论的作者和当前登录的用户是否为同一个人 ) + diff --git a/comment/serializers.py b/comment/serializers.py index fd9dca0..56f5624 100644 --- a/comment/serializers.py +++ b/comment/serializers.py @@ -3,13 +3,41 @@ from user_info.serializers import UserDescSerializer from comment.models import Comment +class CommentChildrenSerializer(serializers.ModelSerializer): + url = serializers.HyperlinkedIdentityField(view_name='comment-detail') + author = UserDescSerializer(read_only=True) + + class Meta: + model = Comment + exclude = [ # 不需要的字段 + 'parent', + 'article', + ] + + class CommentSerializer(serializers.ModelSerializer): """评论的序列化器""" + # HyperlinkedIdentityField用于自身 url = serializers.HyperlinkedIdentityField(view_name='comment-detail') author = UserDescSerializer(read_only=True) + # 设置为超链接,HyperlinkedRelatedField用于对外键关系 + article = serializers.HyperlinkedRelatedField(view_name='article-detail', read_only=True) + # 需要知道是哪篇文章的评论 + article_id = serializers.IntegerField(write_only=True, allow_null=False, required=True) + # parent为父评论 + parent = CommentChildrenSerializer(read_only=True) + # 本身是父评论则不需要该字段 + parent_id = serializers.IntegerField(write_only=True, allow_null=True, required=False) class Meta: model = Comment fields = '__all__' extra_kwargs = {'created': {'read_only': True}} + # 父评论只能在创建时被关联,后续不能更改 + def update(self, instance, validated_data): + validated_data.pop('parent_id', None) # 更新时忽略parent_id参数 + validated_data.pop('article_id', None) # 更新时忽略article_id参数 + return super(CommentSerializer, self).update(instance, validated_data) + + diff --git a/db.sqlite3 b/db.sqlite3 index 5a31d26..3bbddfd 100644 Binary files a/db.sqlite3 and b/db.sqlite3 differ diff --git a/drf_vue_blog/settings.py b/drf_vue_blog/settings.py index f319c73..e7cc66d 100644 --- a/drf_vue_blog/settings.py +++ b/drf_vue_blog/settings.py @@ -1,5 +1,7 @@ from pathlib import Path import os +from datetime import timedelta + # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent @@ -120,6 +122,9 @@ REST_FRAMEWORK = { 'PAGE_SIZE': 5, # 使用django-filter后端过滤引擎 'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'], + # 'DEFAULT_AUTHENTICATION_CLASSES': ( + # 'rest_framework_simplejwt.authentication.JWTAuthentication', + # ), } # 媒体文件 @@ -127,3 +132,10 @@ MEDIA_URL = '/media/' MEDIA_ROOT = os.path.join(BASE_DIR, 'media') DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + +# + +# SIMPLE_JWT = { +# 'ACCESS_TOKEN_LIFETIME': timedelta(days=1), # 令牌有效时间为1天 +# 'REFRESH_TOKEN_LIFETIME': timedelta(days=10), # 令牌每10天刷新一次 +# } diff --git a/drf_vue_blog/urls.py b/drf_vue_blog/urls.py index 15b91da..af7ddfb 100644 --- a/drf_vue_blog/urls.py +++ b/drf_vue_blog/urls.py @@ -5,6 +5,11 @@ from article import views from django.conf import settings from django.conf.urls.static import static from comment.views import CommentViewSet +from user_info.views import UserViewSet +from rest_framework_simplejwt.views import ( + TokenObtainPairView, + TokenRefreshView, +) router = DefaultRouter() router.register(r'article', views.ArticleViewSet) @@ -12,12 +17,14 @@ router.register(r'category', views.CategoryViewSet) router.register(r'tag', views.TagViewSet) router.register(r'avatar', views.AvatarViewSet) router.register(r'comment', CommentViewSet) +router.register(r'user', UserViewSet) urlpatterns = [ path('admin/', admin.site.urls), path('api-auth/', include('rest_framework.urls')), - path('api/', include(router.urls)) - # path('api/article/', include('article.urls', namespace='article')), + path('api/', include(router.urls)), + path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), # 获取token + path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), # 刷新token ] # 把媒体文件的路由注册了 diff --git a/user_info/permissions.py b/user_info/permissions.py new file mode 100644 index 0000000..f1786e4 --- /dev/null +++ b/user_info/permissions.py @@ -0,0 +1,10 @@ +from rest_framework.permissions import BasePermission, SAFE_METHODS + + +class IsSelfOrReadOnly(BasePermission): + + def has_object_permission(self, request, view, obj): + if request.method in SAFE_METHODS: + return True + """确保非安全方法只能由本人操作""" + return obj == request.user diff --git a/user_info/serializers.py b/user_info/serializers.py index 3783888..0147d5d 100644 --- a/user_info/serializers.py +++ b/user_info/serializers.py @@ -13,3 +13,44 @@ class UserDescSerializer(serializers.ModelSerializer): # 'last_login', # 'date_joined' ] + + +class UserRegisterSerializer(serializers.ModelSerializer): + # lookup_field字段将作为url中的地址 + url = serializers.HyperlinkedIdentityField(view_name='user-detail', lookup_field='username') + + class Meta: + model = User + fields = [ + 'url', + 'id', + 'username', + 'password', + ] + extra_kwargs = { + 'password': {'write_only': True} + } + + def create(self, validated_data): + user = User.objects.create_user(**validated_data) + return user + + def update(self, instance, validated_data): + if 'password' in validated_data: + password = validated_data.pop('password') # 对密码加密后再存入数据库 + instance.set_password(password) + return super().update(instance, validated_data) + + +class UserDetailSerializer(serializers.ModelSerializer): + class Meta: + model = User + fields = [ + 'id', + 'username', + 'last_name', + 'first_name', + 'email', + 'last_login', + 'date_joined', + ] diff --git a/user_info/views.py b/user_info/views.py index 91ea44a..c0338cb 100644 --- a/user_info/views.py +++ b/user_info/views.py @@ -1,3 +1,44 @@ -from django.shortcuts import render +from django.contrib.auth.models import User +from rest_framework import viewsets +from rest_framework.permissions import AllowAny, IsAuthenticatedOrReadOnly -# Create your views here. +from user_info.serializers import UserRegisterSerializer +from user_info.permissions import IsSelfOrReadOnly + +from rest_framework.decorators import action +from rest_framework.response import Response +from user_info.serializers import UserDetailSerializer + + +class UserViewSet(viewsets.ModelViewSet): + queryset = User.objects.all() + serializer_class = UserRegisterSerializer + lookup_field = 'username' # 和UserRegisterSerializer中的url中lookup_field对应 + + # http://127.0.0.1:8000/api/user/admin/info/ + @action(detail=True, methods=['get']) + def info(self, request, username=None): + queryset = User.objects.get(username=username) + serializer = UserDetailSerializer(queryset, many=False) + return Response(serializer.data) + + # http://127.0.0.1:8000/api/user/sorted/ + @action(detail=False) + def sorted(self, request): + users = User.objects.all().order_by('-username') + # 是否分页 + page = self.paginate_queryset(users) + if page is not None: + serializer = self.get_serializer(page, many=True) + return self.get_paginated_response(serializer.data) + serializer = self.get_serializer(users, many=True) + return Response(serializer.data) + + def get_permissions(self): + # 注册用户的POST请求是允许所有人都可以操作的 + if self.request.method == 'POST': + self.permission_classes = [AllowAny] + else: + self.permission_classes = [IsAuthenticatedOrReadOnly, IsSelfOrReadOnly] + + return super(UserViewSet, self).get_permissions()