django-rest-framework学习之路-5-认证和权限


1、模型添加字段

我们想要实现以下功能:

用户可以看到所有人的代码,但是只能修改和删除自己的代码

只有登录的用户可以添加代码

未登录的用户只有读的权限

代码应该有一个创建者的字段,表示代码是属于用户的,就是作者了

因此,我们进行如下操作

Snippet模型中增加字段

修改snippets/models.py的Snippet模型,添加下面两个字段

owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE, null=True, default=None)
highlighted = models.TextField(null=True, blank=True, default=None)

owner表示创建代码的用户

highlighted用于保存高亮的代码

设置自动生成高亮代码

重构一下model.Model父类的save()方法,用于每次保存代码时,生成高亮的代码

因此snippets/models.py变成了如下代码

from django.db import models
from pygments import highlight
from pygments.formatters.html import HtmlFormatter
from pygments.lexers import get_all_lexers, get_lexer_by_name
from pygments.styles import get_all_styles

LEXERS = [item for item in get_all_lexers() if item[1]]
LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS])
STYLE_CHOICES = sorted((item, item) for item in get_all_styles())


class Snippet(models.Model):
    owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE, null=True, default=None)
    highlighted = models.TextField(null=True, blank=True, default=None)
    title = models.CharField(max_length=100, blank=True, default='')
    code = models.TextField()
    linenos = models.BooleanField(default=False)
    language = models.CharField(choices=LANGUAGE_CHOICES, default='python', max_length=100)
    style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=100)

    created = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ('created',)

    def save(self, *args, **kwargs):
        """
        使用`pygments`库创建一个高亮显示的HTML表示代码段。
        """
        lexer = get_lexer_by_name(self.language)
        linenos = self.linenos and 'table' or False
        options = self.title and {'title': self.title} or {}
        formatter = HtmlFormatter(style=self.style, linenos=linenos,
                                  full=True, **options)
        self.highlighted = highlight(self.code, lexer, formatter)
        super(Snippet, self).save(*args, **kwargs)

迁移数据库

打开一个cmd,cd到此项目路径下(有manage.py的目录),正如以前一样,执行一下命令,

python .\manage.py makemigrations
python .\manage.py migrate

数据库

我们既然创建了外键owner,关联到auth.User下,那么我们来创建几个用户吧

为用户模特添加序列化视图

我们在snippets/serializers.py添加用户序列化视图

from django.contrib.auth.models import User

class UserSerializer(serializers.ModelSerializer):
    snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all())

    class Meta:
        model = User
        fields = ('id', 'username', 'snippets')

snippets/serializers.py代码就变成了这样

from django.contrib.auth.models import User
from rest_framework import serializers

from snippets.models import Snippet


class SnippetSerializer(serializers.ModelSerializer):
    class Meta:
        model = Snippet
        fields = ('id', 'title', 'code', 'linenos', 'language', 'style')


class UserSerializer(serializers.ModelSerializer):
    snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all())

    class Meta:
        model = User
        fields = ('id', 'username', 'snippets')

我们通过snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all())来显示该用户对应的Snippet对象集合,因为在Snippet模型中,有个外键是User表,而User想要显示它,就只能是显示的添加这个字段

我们再添加一下序列化User的视图,当然也是在snippets/views.py文件中

class UserList(generics.ListAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer


class UserDetail(generics.RetrieveAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer

最后snippets/views.py文件变成了这样

from django.contrib.auth.models import User
from rest_framework import generics

from snippets.models import Snippet
from snippets.serializers import SnippetSerializer, UserSerializer


class SnippetList(generics.ListCreateAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer


class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer


class UserList(generics.ListAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer


class UserDetail(generics.RetrieveAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer

接下来注册一下User的路由到snippets/urls.py中

re_path(r'^users/$', views.UserList.as_view()),
re_path(r'^users/(?P[0-9]+)/$', views.UserDetail.as_view()),

snippets/urls.py文件变成了这样

from django.urls import re_path
from rest_framework.urlpatterns import format_suffix_patterns

from snippets import views

urlpatterns = [
    re_path(r'^snippets_list/$', views.SnippetList.as_view()),
    re_path(r'^snippets_detail/(?P[0-9]+)/$', views.SnippetDetail.as_view()),
    re_path(r'^users/$', views.UserList.as_view()),
    re_path(r'^users/(?P[0-9]+)/$', views.UserDetail.as_view()),
]

urlpatterns = format_suffix_patterns(urlpatterns)

浏览器查看信息

http://127.0.0.1:8000/snippets/users/

2、设置一个登录接口

因为我们要用户登录后才可以进行添加,修改,删除代码,所以我们需要一个登录接口

我们只需要在tutorial/urls.py中添加以下代码即可

re_path(r'^api-auth/', include('rest_framework.urls')),

这样用的就是rest framework的登录接口了,而用户信息就是我们刚才创建的用户

浏览器查看

我们先浏览器查看我们的根目录 http://127.0.0.1:8000/

可以看到我们有三个子路由,其中一个是django自带的admin路由,还有一个snippets是我们的代码片段模型的路由,还有一个就是刚才添加的api-auth登录路由

我们访问这个登录路由

http://127.0.0.1:8000/api-auth/

又出来了两级子路由,可以看到是登录和注销,我们先看看登录吧

http://127.0.0.1:8000/api-auth/login/

登录就出来啦~~~

试着登录一下,用之前创建的用户和密码

输入用户名密码后点击登录

发现自动跳转到了http://127.0.0.1:8000/accounts/profile/接口

先不管他,这个应该是rest framework自己约定的登录后跳转的默认路由。

我们访问一下http://127.0.0.1:8000/snippets/snippets_list/接口,可以看到右上角有登录的标识了,刚才我用的是admin用户登录的

我们点击右上角就会发现,这里可以登录和注销,真是好用哇!

我们在看看详情页

http://127.0.0.1:8000/snippets/snippets_detail/2/

以上我们看了列表页和详情页,发现都非常好用,但是有一个缺陷,就是没登录也可以添加修改删除代码,我们接下来要验证一下权限,当用户登陆了,就可以增加修改删除代码了,没登录的话只能查看一下代码,只有只读的权限。

3、添加权限认证

我们在之前添加了作者以及自动生成高亮代码,接下来我们应该把作者添加到序列化中

如果我们按照以前的方式直接添加ower到fields字段中,如下

页面就变成了这样子

那么我们就变成了每次创建代码,需要连同作者一起添加,而这样不是我们想要的,我们想要的是用户登录,然后添加自己的代码,我们根据登录的用户,把他与他的代码绑定,因此我们设置owner为只读字段

设置owner为只读字段

添加以下代码到SnippetSerializer类中,同时添加pwner到fields中

owner = serializers.ReadOnlyField(source='owner.username')

因此snippets/serializers.py变成了

from django.contrib.auth.models import User
from rest_framework import serializers

from snippets.models import Snippet


class SnippetSerializer(serializers.ModelSerializer):
    owner = serializers.ReadOnlyField(source='owner.username')

    class Meta:
        model = Snippet
        fields = ('id', 'owner', 'title', 'code', 'linenos', 'language', 'style')


class UserSerializer(serializers.ModelSerializer):
    snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all())

    class Meta:
        model = User
        fields = ('id', 'username', 'snippets')

将Snippet和用户关联

我们需要把登录的用户关联到他的代码,因此我们会进行登录,登录后就有了用户的信息。我们把用户与代码关联上

我们处理的方式是在我们的代码片段视图中重写一个.perform_create()方法,这样我们可以修改实例保存的方法,并处理传入请求或请求URL中隐含的任何信息。

在SnippetList视图类中,添加以下方法:

def perform_create(self, serializer):
    serializer.save(owner=self.request.user)

这个snippets/views.py就变成了

from django.contrib.auth.models import User
from rest_framework import generics

from snippets.models import Snippet
from snippets.serializers import SnippetSerializer, UserSerializer


class SnippetList(generics.ListCreateAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer

    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)


class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer


class UserList(generics.ListAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer


class UserDetail(generics.RetrieveAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer

接下来就是设置只读权限了,用户不登录只读,登录后可以增加修改删除代码

设置只读权限

在SnippetList和SnippetDetail两个视图中,添加一句代码,限制一下必须登录才可以更新,删除,修改代码

from rest_framework import permissions
permission_classes = [permissions.IsAuthenticatedOrReadOnly, ]

所以snippets/views.py又变成了

from django.contrib.auth.models import User
from rest_framework import generics, permissions

from snippets.models import Snippet
from snippets.serializers import SnippetSerializer, UserSerializer


class SnippetList(generics.ListCreateAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer
    permission_classes = [permissions.IsAuthenticatedOrReadOnly, ]
    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)


class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer
    permission_classes = [permissions.IsAuthenticatedOrReadOnly, ]

class UserList(generics.ListAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer


class UserDetail(generics.RetrieveAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer

这时候,我们添加了登录接口,添加了只读权限,添加了代码和作者关联,我们来看一下浏览器

http://127.0.0.1:8000/snippets/snippets_detail/2/

可以看到,没有登录就没有删除,创建,修改接口

列表页也是一样的

如果登录了,就会出现对应的接口了

这里是浏览器的显示,如果你用ApiPost工具调用接口创建信息或者更新信息就会收到这样的提示

{    "detail": "Authentication credentials were not provided." }

自定义权限

我们想代码可以被所有人看见,因此如果是GET请求,我们应该不校验权限

在snippets app中,创建一个新文件permissions.py。

添加代码

from rest_framework import permissions


class IsOwnerOrReadOnly(permissions.BasePermission):
    """
    自定义权限只允许对象的所有者编辑它。
    """

    def has_object_permission(self, request, view, obj):
        # 读取权限允许任何请求,
        # 所以我们总是允许GET,HEAD或OPTIONS请求。
        if request.method in permissions.SAFE_METHODS:
            return True

        # 只有该snippet的所有者才允许写权限。
        return obj.owner == request.user

把IsOwnerOrReadOnly权限添加到SnippetDetail视图中

permission_classes = [permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly]

所以snippets/views.py又变成了

from django.contrib.auth.models import User
from rest_framework import generics, permissions

from snippets.models import Snippet
from snippets.permissions import IsOwnerOrReadOnly
from snippets.serializers import SnippetSerializer, UserSerializer


class SnippetList(generics.ListCreateAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer
    permission_classes = [permissions.IsAuthenticatedOrReadOnly, ]

    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)


class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer
    permission_classes = [permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly]


class UserList(generics.ListAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer


class UserDetail(generics.RetrieveAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer

4、通过requests库提交请求带用户名密码通过认证

如果需要通过requests发送POST请求,来达到登录的效果,即登录了就可以添加修改和删除代码了。

需要使用HTTPBasicAuth认证,带上用户名和密码,这样就可以访问了

import requests
from requests.auth import HTTPBasicAuth

url = 'http://127.0.0.1:8000/snippets/snippets_list/'
headers = {}

payload = {
    "title": "2",
    "code": "3",
    "linenos": False,
    "language": 'abap',
    "style": 'abap',
}

user = 'admin'
password = 'admin'
re = requests.post(url=url, json=payload, headers=headers, auth=HTTPBasicAuth(user, password))
print(re.headers)
print(re.text)