From 8e1a25a9c091a54e65dd5ef053282aaae9fb3d76 Mon Sep 17 00:00:00 2001 From: barney <15270405776@163.com> Date: Sat, 24 Sep 2022 21:32:03 +0800 Subject: [PATCH] =?UTF-8?q?=E7=94=A8=E6=88=B7=E7=AE=A1=E7=90=86,JWT?= =?UTF-8?q?=E9=AA=8C=E8=AF=81,=E8=AF=84=E8=AE=BA=E5=9B=9E=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- article/models.py | 6 ++- article/urls.py | 9 ----- comment/migrations/0002_comment_parent.py | 19 +++++++++ comment/models.py | 7 ++++ comment/permissions.py | 1 + comment/serializers.py | 28 ++++++++++++++ db.sqlite3 | Bin 192512 -> 196608 bytes drf_vue_blog/settings.py | 12 ++++++ drf_vue_blog/urls.py | 11 +++++- user_info/permissions.py | 10 +++++ user_info/serializers.py | 41 ++++++++++++++++++++ user_info/views.py | 45 +++++++++++++++++++++- 12 files changed, 175 insertions(+), 14 deletions(-) delete mode 100644 article/urls.py create mode 100644 comment/migrations/0002_comment_parent.py create mode 100644 user_info/permissions.py 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 5a31d26efa54f23bc5583ac22e9840d591797d72..3bbddfd11fa8cfa37c0dfd0c5c29a790150df6d9 100644 GIT binary patch delta 1348 zcmai!ZD?Cn7{~8DH!->CmeV<8%((4ZtP{JW=brnLo9G5HY0|VeY4f(V`I5DHX__=| z%}d*SX{y7CFQ9}i%;6grzsPhhVyE+yA5|y{Lxmyg>{8RCqJ#W~vK@bE|=IxPw;Rr`=ymN99QU;o?YdQr$(@k6FC)!6W1~p;e zY|&gM6VH|qLO8Ouj^xLtl`V`gHX@4=^9(ClInl}sCQ(30#7)QkkM6p6orFKYL3jmL z;4?4*1^5R15*naJzuvr``CNa`-hEULd0TSptFCK$eY~*8SD#ZxRg07#ru*YkBLR+19L&nl??cTa(}wo9}tEz z-qi45NbnWKsN{}2lR{}SmJEgmJpAa5{#Ys^D@^&ic*OYL=S~$x$|0z`js<)MGBr%!b5>OBzQx zdCZY;M@noqTNppiW(0Y@Vs})hdDh{vO!EnIjCG_6Udvc|u9z9jWcvK}nC(PgChZ<@ zhWc|A_n5~vlCC2AgdC2KddFSH5now!&H3f*G>%L-hNe=n87rHM3bwp8F_RCMOJ?UV z4$YQh=85z~Bpmhl0$#Zg^CJsaj?7g?4xu5VU!G~zjYUQzF@M!f;4B4hBMe^r}{-5m9EeJrZoBEo7WQ~A!{kv+p<5Asf8W;U3&Fm_6e4#%?)^?w!+^QwT zqhV6_gmYN4ucwE6(!R7-2T_tf!gRIG**JF=9DqCA)nFRnaKl{(XH?o}iLV;p)xlM2 z;qE{~u7mrwz{q4QGA^=Mz&_F<4l~=oGjJ>)iITZuq#T$e<i|Q zcCvPEwl#KeywaN{NsxGv*8&7I#Nj)z9Rz0;kcs1vS-@J*OKOPj%|E?yLHGK~<;(3% zjqjHMqgTW01pFQT0&gns$Mxo({AaLNO_8Kd-HNKssHIvFs3?-w>Xg^)-yqO}Knnsb q2(%#3hE@sq2mA^C3fJMPlIbekzp1NIt0;n0sVJJHHUpq2g8mbSMxT)Y delta 774 zcmaiyTS!xJ9LLYu(rsS$cNItqIVoSV>A7D<1-l;4+{Mi~*IwpsXZM}C9qnu{4k?1_ zAsOaVgdn|D<|4a6(3?>B&_g{%fr#}Y;Y;9y4ej>U7ry-9%kRq%KKyF6j@sw;+r7GX z5Cjc3{afjU^EmwO@ze@X_GqkiRTtUl*Q+%R>!~-bJ$o?t3P!<8Fbi%10g&K6_y~G| zO1szSN8W2s9`9BTYGPtOpD0M-U$d?n{sH%_ArCGA1To^M5kZCsiltG!{;IH|)?=8F zV2xxwB@Rrnb|jhNvN9)6xTEP{%1s1<40_gQnh|rOR@y-oo$(2K#74PDZ?5PvW4^en zk|Z%K9+PKmcqWs)h#?qeKv)At=#S2!^bkd&Bu-a_;*xh0KCxE(1{TIz8oP6Qr{}We zEGv}CMWKRBN1_!%W<@z8N!7GmW+V!ehY}7skg=9MS28BA&l65_xjZ)M50%Der(=Ae zNV}_P(}*Q6SPFLBmd@JUb`O*AyJwLwN98yw9#4pzD-iQnJ^7#`Mvj$&<&>48aU>Cu zozk4gOOMi*MG}=f#IzwQ3UnMb6HcidF_bN0h+*6|7nkEhEQ8q`X}f)VgfROUf70tQ zyK&CzbyE($5aYu^hv1~mk!qYbqGB``VT~M15E5tO5=f*Ps(A6FWC$9x#v;`RtggdvFlG*13LT zK@o%3M;8^(&$Yr}0|FZjUGX*OCE->GH^K1Mv)cCEM*-v7j^qge41rG@Y_ 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()