Django REST framework 學(xué)習(xí)紀(jì)要 Tutorial 4 Authentication & Permissions

目前為止,我們的代碼沒(méi)有限制誰(shuí)可以編輯和刪除代碼片段,此節(jié)我們需要實(shí)現(xiàn)以下功能

  • 代碼片段需要與創(chuàng)建者關(guān)聯(lián)
  • 只有通過(guò)驗(yàn)證的用戶才能創(chuàng)建代碼片段
  • 只有創(chuàng)建者才能修改或刪除代碼片段
  • 沒(méi)有通過(guò)驗(yàn)證的用戶擁有只讀權(quán)限

給model添加字段

我們需要添加兩個(gè)字段,一個(gè)用于存儲(chǔ)代碼片段的創(chuàng)建者信息,一個(gè)用于存儲(chǔ)代碼的高亮信息

    style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=100)
    owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE)
    highlighted = models.TextField()

同時(shí),我們需要在該模型類執(zhí)行保存操作時(shí),自動(dòng)填充highlighted字段,使用pygments庫(kù)。
首先,導(dǎo)入一些包

from pygments.lexers import get_lexer_by_name
from pygments.formatters.html import HtmlFormatter
from pygments import highlight

然后為Snippet重寫父類的save方法

    def save(self, *args, **kwargs):
        """
        use the 'pygments' library to create a highlighted HTML
        representation of code snippet
        """
        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)

接下來(lái)需要遷移數(shù)據(jù)庫(kù),方便起見(jiàn),刪庫(kù),然后重新遷移

(django_rest_framework) [root@localhost tutorial]# rm -f tmp.db db.sqlite3 && \
> rm -rf snippets/migrations/ && \
> python manage.py makemigrations snippets && \
> python manage.py migrate
Migrations for 'snippets':
  snippets/migrations/0001_initial.py
    - Create model Snippet
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions, snippets
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying sessions.0001_initial... OK
  Applying snippets.0001_initial... OK

為了測(cè)試API,我們需要?jiǎng)?chuàng)建一些用戶,最快的方式就是通過(guò)createsuperuser命令

(django_rest_framework) [root@localhost tutorial]# python manage.py createsuperuser
Username (leave blank to use 'root'): song
Email address: shelmingsong@gmail.com
Password: 
Password (again): 
Superuser created successfully.
(django_rest_framework) [root@localhost tutorial]# python manage.py createsuperuser
Username (leave blank to use 'root'): user_1
Email address: user_1@gmail.com
Password: 
Password (again): 
Superuser created successfully.
(django_rest_framework) [root@localhost tutorial]# python manage.py createsuperuser
Username (leave blank to use 'root'): user_2
Email address: user_2@gmail.com
Password: 
Password (again): 
Superuser created successfully.

為用戶模型添加接口

我們已經(jīng)創(chuàng)建了三個(gè)用戶,現(xiàn)在我們需要添加用戶相關(guān)的接口,修改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')

因?yàn)?code>snippets和user是一種反向的關(guān)聯(lián),默認(rèn)不會(huì)包含入ModelSerializer類中,所以需要我們手動(dòng)添加

我們也需要對(duì)views.py進(jìn)行修改,由于用戶頁(yè)面為只讀,所以繼承于ListAPIViewRetrieveAPIView

from django.contrib.auth.models import User
from snippets.serializers import UserSerializer


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


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

配置url.py

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

關(guān)聯(lián)User和Snippet

此時(shí)我們創(chuàng)建一個(gè)代碼片段,是無(wú)法與用戶關(guān)聯(lián)的,因?yàn)橛脩粜畔⑹峭ㄟ^(guò)request獲取的。
因此我們需要重寫snippet的view中perform_create()方法,這個(gè)方法允許我們?cè)趯?duì)象保存前進(jìn)行相關(guān)操作,處理任何有requestrequested URL傳遞進(jìn)來(lái)的數(shù)據(jù)

修改views.py中的SnippetList類,添加perform_create()方法

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

如此,新建代碼片段時(shí),會(huì)添加owner字段,該字段存儲(chǔ)了request中的用戶信息

更新serializer

之前我們?cè)?code>views中的SnippetList類中添加了perform_create方法,保存了owner信息,因而也需要在serializer中的SnippetSerializer類中添加owner信息,同時(shí)將owner添加進(jìn)Meta子類的fields字段中

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

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

這里我們使用了ReadOnlyField類型,這個(gè)類型是只讀的,不能被更新,和Charfield(read_only=True)是一樣的效果

添加權(quán)限認(rèn)證

我們希望只有登錄的用戶能夠去增加代碼片段,未登錄則只有查看的權(quán)限,此時(shí)我們需要用到IsAuthenticatedOrReadOnly

修改views.py,為snippet的兩個(gè)類views添加permission_classes字段

from rest_framework import permissions


class SnippetList(generics.ListCreateAPIView):
    permission_classes = (permissions.IsAuthenticatedOrReadOnly, )


class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
    permission_classes = (permissions.IsAuthenticatedOrReadOnly, )

添加登陸接口

修改項(xiàng)目的urls.py

urlpatterns = [
    url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]

r'^api-auth/'可以自定,namespace在Django 1.9 + 的版本中可以省略

運(yùn)行Django服務(wù)器,訪問(wèn)your.domain/snippets/,點(diǎn)擊右上角的登陸按鈕,登陸我們之前創(chuàng)建的用戶后,就可以創(chuàng)建代碼片段了

創(chuàng)建完幾個(gè)代碼片段后,再訪問(wèn)your.domain/users/時(shí),就可以看到每個(gè)用戶創(chuàng)建了哪幾個(gè)代碼片段了

對(duì)象級(jí)別的權(quán)限

現(xiàn)在用戶都可以對(duì)所有的snippets進(jìn)行增刪改查,我們要確保只有創(chuàng)建者可以對(duì)snippets進(jìn)行改動(dòng)或刪除。

snippetsapp中,創(chuàng)建permissions.py

from rest_framework import permissions


class IsOwnerOrReadOnly(permissions.BasePermission):
    """
    custom permission to only allow owners of an object to edit it
    """
    def has_object_permission(self, request, view, obj):
        # allow all user to read
        if request.method in permissions.SAFE_METHODS:
            return True

        # only allow owner to edit
        return obj.owner == request.user

views.py中添加權(quán)限

from snippets.permissions import IsOwnerOrReadOnly

class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
    permission_classes = (permissions.IsAuthenticatedOrReadOnly,
                          IsOwnerOrReadOnly)

此時(shí)我們?cè)L問(wèn)your.domain/snippets/1/,若用戶未登錄或登錄用戶不是該snippets的創(chuàng)建者,則只有讀的權(quán)限,頁(yè)面上表現(xiàn)為沒(méi)有DELETE(上方中間)和PUT(右下角)按鈕

通過(guò)接口進(jìn)行權(quán)限認(rèn)證

之前我們是通過(guò)瀏覽器頁(yè)面進(jìn)行登錄的,而當(dāng)我們直接使用接口去請(qǐng)求時(shí),如果沒(méi)有進(jìn)行登錄,而對(duì)某個(gè)snippet進(jìn)行修改或是創(chuàng)建一個(gè)新的snippet,則會(huì)報(bào)錯(cuò)

(django_rest_framework) [root@localhost django_rest_framework]# http POST http://127.0.0.1:80/snippets/ code="hahah"
HTTP/1.0 403 Forbidden
Allow: GET, POST, HEAD, OPTIONS
Content-Length: 58
Content-Type: application/json
Date: Tue, 28 Nov 2017 14:56:18 GMT
Server: WSGIServer/0.2 CPython/3.6.3
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN

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

(django_rest_framework) [root@localhost django_rest_framework]# http POST http://127.0.0.1:80/snippets/1/ code="hahah"
HTTP/1.0 403 Forbidden
Allow: GET, PUT, PATCH, DELETE, HEAD, OPTIONS
Content-Length: 58
Content-Type: application/json
Date: Tue, 28 Nov 2017 14:56:26 GMT
Server: WSGIServer/0.2 CPython/3.6.3
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN

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

我們?cè)诎l(fā)送請(qǐng)求時(shí),提供用戶名和密碼,就可以進(jìn)行操作了

(django_rest_framework) [root@localhost django_rest_framework]# http -a your_username:your_password POST http://127.0.0.1:80/snippets/ code="hahah"
HTTP/1.0 201 Created
Allow: GET, POST, HEAD, OPTIONS
Content-Length: 104
Content-Type: application/json
Date: Tue, 28 Nov 2017 14:58:10 GMT
Server: WSGIServer/0.2 CPython/3.6.3
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN

{
    "code": "hahah",
    "id": 4,
    "language": "python",
    "linenos": false,
    "owner": "song",
    "style": "friendly",
    "title": ""
}

關(guān)于

本人是初學(xué)Django REST framework,Django REST framework 學(xué)習(xí)紀(jì)要系列文章是我從官網(wǎng)文檔學(xué)習(xí)后的初步消化成果,如有錯(cuò)誤,歡迎指正。

學(xué)習(xí)用代碼Github倉(cāng)庫(kù):shelmingsong/django_rest_framework

本文參考的官網(wǎng)文檔:Tutorial 4: Authentication & Permissions

博客更新地址

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容