Django Rest Framework之Tutorial 4分析與實踐

前言

在DRF所有的練習中,Tutorial 4算是比較難以理解的一部分,因此對這一節做一分析。在開始閱讀之前,強烈建議你完成Tutorial 1-3的所有內容,我們以下所有的內容都會用到前面三個練習的代碼。

知識準備

  1. 主鍵:若某一個屬性組(注意是組)能唯一標識一條記錄,該屬性組就是一個主鍵。主鍵不能重複,且只能有一個,也不允許爲空。定義主鍵主要是爲了維護關係數據庫的完整性。
  2. 外鍵:外鍵用於與另一張表的關聯,是能確定另一張表記錄的字段。外鍵是另一個表的主鍵,可以重複,可以有多個,也可以是空值。定義外鍵主要是爲了保持數據的一致性。
    這裏對Tutorial 4中的User表說明一下:
    auth_user表
    在這個表中,參閱Django官方文檔中提到,如果沒有特別聲明,將默認添加一個id字段並以該字段作爲主鍵。雖然auth_user不是我們主動創建的,在auth_user中,id還是作爲主鍵:
    這裏寫圖片描述

正式開始

Adding information to our model

這一節首先向models.py中的Snippetmodle中寫入了兩個新的字段:

owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE)
highlighted = models.TextField()

我們重點關注第一行代碼。其中涉及了一個函數models.ForeignKey,先看Django官方的描述:

A many-to-one relationship. Requires two positional arguments: the class to which the model is related and the on_delete option.
一種多對一的關係。需要兩個參數:需要建立關係的model 類和on_delete選項。

model類和參數on_delete很好理解:

  • 我們需要告知model Sneppet要和model User建立關係
  • on_delete=models.CASCADE的意思如下:
    刪除:刪除主表時自動刪除從表。刪除從表,主表不變
    更新:更新主表時自動更新從表。更新從表,主表不變
  • 最後,related_name參數,如果看字面意思應該是關係名,這個有什麼用呢?官方的解釋太拗口,可以參考這篇博客,後面我會用代碼解釋這個關係名的具體用法。暫時可以這麼理解,我們的Snappet model和User model是一對多的關係,我們可以通過外鍵表明某段“snappet”屬於某個“user”,我們還需要從某個“user”入手查找屬於他的“snappet”,這就是related_name的用處。

現在我們可以這樣說:
Snippets表中的owner字段是Snippets表的外鍵
這裏寫圖片描述
這一節剩下的部分很好理解,不再贅述。
我們來解決上面提到的related_name參數的問題。
首先完成所有需要的代碼,我下面給出的代碼均來源於Tutorial 4,你的snipetts/models.py應該是這樣:

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

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):
    create=models.DateTimeField(auto_now_add=True)
    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)
    owner=models.ForeignKey('auth.User',related_name='snippets',on_delete=models.CASCADE)
    highlighted=models.TextField()

    class Meta:
        ordering = ('create',)
    def save(self,*args,**kwargs):
        lexer=get_lexer_by_name(self.language)
        linenos='table' if self.linenos else False
        options={'title':self.title} if self.title else {}
        formatter = HtmlFormatter(style=self.style,linenos=linenos,full=True,**options)
        self.highlighted=highlight(self.code,lexer,formatter)
        super(Snippet,self).save(*args,**kwargs)

snippets/serializers.py應該是這樣

from rest_framework import serializers
from snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES
from django.contrib.auth.models import User
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')

然後運行:

rm -f db.sqlite3
rm -r snippets/migrations
python manage.py makemigrations snippets
python manage.py migrate

然後使用

python manage.py createsuperuser --email admin@admin.com --username
admin
python manage.py createsuperuser --email admin@admin.com --username
root

創建兩個用戶,用戶名可隨意
然後進入shell:

python manage.py shell

首先import:

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

然後我們直接操作Snippet寫入一部分數據:

u=User.objects.get(pk=1) //這是爲了獲取一個用戶
s=Snippet(owner=u,code="print hello") //爲獲取到的用戶寫入一段snippet
s.save() //保存

查看數據庫:

成功寫入了一條數據
最後,我們來使用related_name

u.snippets.all() //這裏就是使用related_name進行反向查詢
<QuerySet [<Snippet: Snippet object (1)>]> //執行結果

也就是說我們通過user中的一條記錄,查詢到了該用戶具有的snippet

Adding endpoints for our User models

在這一節開始,我們要向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')

如果你對serializer理解不太深,可暫且認爲serializer是將model和json互相轉換的過程。
顯然,User model中沒有snippets這個字段,當然,我們也大可不必再User的serializer中加入snippets這個字段,User model仍然會很好的運行。這裏加入的原因是我們想在從model中獲取全部或某個用戶的時候同時獲得他擁有的snippets。
我們對serializers.PrimaryKeyRelatedField的作用描述如下:使用 PrimaryKeyRelatedField 將返回一個對應關係 model 的主鍵,參考知乎
對應到我們的練習中,snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all())這句代碼將返回當前序列化USER的在model Snippets中的記錄的所有主鍵。
我們用實驗來說明。不必修改代碼,繼續上面的shell。
首先我們還是再加入一段snippet,不過這次使用另一個用戶:

>>> u=User.objects.get(pk=2)
>>> s=Snippet(owner=u,code="whoami")
>>> s.save()
>>> s=Snippet(owner=u,code="ifconfig")
>>> s.save()
>>> us=UserSerializer(u)
>>> us.data

如上,我們先使用id=2的用戶建立了兩條sneppts記錄,然後使用UserSerializer序列化了該用戶,最後我們輸出序列化結果:

{'id': 2, 'username': 'root', 'snippets': [2, 3]}

顯然,截止目前,我們能夠做的事情有:
- 能夠在Snippet model中插入和獲取數據,且能夠實現owner作爲外鍵
- 能夠使用User的Serializer對User model進行序列化和反序列化,且能夠反查snippet
這一節其他的代碼很簡單,不再贅述。

Associating Snippets with Users

這一節只做了一件事:

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

將上面的代碼寫入到了snippets的view.py中的SnippetList類中。爲什麼要這麼寫?參考Tutorial 3
首先(WHAT),我們知道SnippetList繼承於ListCreateAPIView類,這個類中替我們實現了列出序列化所有數據和反序列化創建一條記錄,也就是數據庫的select allinsert。函數perform_create在這裏被重寫了一次,這裏的重寫是OOP裏面的含義。可以在執行繼承來的ListCreateAPIView中的create()函數時添加一個額外的owner字段。因此,serializer (注意上面的這段代碼中serializer只是一個參數名而已,不是類名)也將首先反序列化owner字段並保存。當然,對應到我們實際的代碼中,serializer實際上是SnippetSerializer,因爲我們在SnippetList類中顯式聲明瞭serializer_class = SnippetSerializer。這裏可能有點繞,關於serializer.save這個用法,可以參考Tutorial 1中的代碼,如下所示:
這裏寫圖片描述
其次(WHY),我們要問,既然ListCreateAPIView替我們完成了select allinsert功能,那麼爲什麼我們要單獨把owner提出來?單獨爲它寫一段代碼?Good Question。事實上,我們可以不寫這一段代碼,前提是,owner作爲一個POST 數據包的參數而不是HTTP HEADER傳入
最後(WHERE),請記住,我們寫這段代碼的位置是view.py,也就是說,我們應當從瀏覽器或者HTTP協議的角度去考慮這個函數,而不是從命令行。我們後面會有一個實驗解釋這個問題,現在,請把它寫入到你的view.py。

Updating our serializer

按照這節的要求,我們的serializer.py應該是下面這個樣子:

from rest_framework import serializers
from snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES
from django.contrib.auth.models import User
class SnippetSerializer(serializers.ModelSerializer):
    owner = serializers.ReadOnlyField(source='owner.username')
    class Meta:
        model = Snippet
        fields = ('id', 'title', 'code', 'linenos', 'language', 'style', 'owner')
class UserSerializer(serializers.ModelSerializer):
    snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all())
    class Meta:
        model = User
        fields = ('id', 'username', 'snippets')

我們來研究下owner = serializers.ReadOnlyField(source='owner.username')這行代碼。
首先從整體來說,這行代碼和我們在Tutorial 1 這一節中做的類似,我們在序列化器中聲明瞭一個owner字段。但是同樣的問題,爲什麼SnippetSerializer在繼承了serializers.ModelSerializer類後還要再次聲明呢?我們用實驗來解釋。
第一步:先註釋掉這行代碼,然後進入python manage.py shell
第二步:執行下面的代碼:

from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
s=Snippet.objects.get(pk=1)
ser=SnippetSerializer(s)
ser.data

我們看到返回的數據爲:

{'linenos': False, 'title': '', 'language': 'python', 'owner': 1, 'style': 'friendly', 'code': 'print hello', 'id': 1}

注意owner這個值,顯然不需要這一行代碼,我們的序列化器依舊可以正常工作,這裏owner值爲1,我們知道它代表User model中id=1的記錄。
第三步我們取消被註釋的代碼,重複上面的步驟,返回的結果是:

{'title': '', 'code': 'print hello', 'owner': 'admin', 'linenos': False, 'style': 'friendly', 'language': 'python', 'id': 1}

這裏有一些有意思的變化,owner的值變爲了admin。爲什麼會有這樣的變化?原因在source='owner.username'這裏。
先看DRF官方關於source關鍵字的解釋:

The name of the attribute that will be used to populate the field. May be a method that only takes a self argument, such as URLField(source=’get_absolute_url’), or may use dotted notation to traverse attributes, such as EmailField(source=’user.email’). When serializing fields with dotted notation, it may be necessary to provide a default value if any object is not present or is empty during attribute traversal.
The value source=’*’ has a special meaning, and is used to indicate that the entire object should be passed through to the field. This can be useful for creating nested representations, or for fields which require access to the complete object in order to determine the output representation.
Defaults to the name of the field.

爲了方便我翻譯下這段話:

(source參數)是指用來填充字段的屬性的名稱。可以是一個採用self關鍵字的方法,例如URLField(source='get_absolute_url'),或者可以使用點符號來遍歷屬性,例如EmailField(source =’user.email’),在使用點符號序列化字段時,如果在屬性遍歷期間對象不存在或爲空,則可能需要提供缺省值。
source ='*'具有特殊含義,表示整個對象應該傳遞到該字段。……
默認爲該字段的名稱

回到我們的代碼owner = serializers.ReadOnlyField(source='owner.username')這行代碼中。也就是說,如果我們將這行代碼寫爲上面的形式的時候,序列化器將從owner指向的User model的某條記錄中獲取其username字段的值,如果我們寫爲owner = serializers.ReadOnlyField(source='*'),那麼最終序列化出的owner的值就是當前Snippet對象,是的你沒有看錯,可以自行實驗一下。如果我們寫爲owner = serializers.ReadOnlyField(),也就是默認值情況下,那麼最終序列化出的owner的值就是當前User對象,是的你也沒有看錯,可以自行實驗一下。DRF的文檔說的也不是那麼明白。
OK,現在我的的序列化器會將owner序列化爲User.username,並且是一個ReadOnly字段。我們來看一下serializers.ReadOnlyField這段的意思。先通俗的說明一下,ReadOnlyField表示具有該屬性的字段在從model序列化時可以正常返回其值,但是在從json反序列化爲model時,不接受json中對該值的修改。結合我們的例子看,owner也應該是隻讀的。我們用實驗來解釋:
第一步修改serializer.py中的代碼:

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

也就是沿用Tutorial 4中的代碼。
第二步進入python manage.py shell,執行:

from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
s=Snippet.objects.get(pk=1)
ser=SnippetSerializer(s)
ser.data

這裏沒有什麼變化,輸出的內容和之前一樣:

{'title': '', 'code': 'print hello', 'style': 'friendly', 'id': 1, 'owner': 'admin', 'language': 'python', 'linenos': False}

還是注意owner的值,表明這行代碼屬於admin用戶,現在,我們更新Snippet model中的這條記錄,嘗試將它的owner修改爲root,並且爲了對比,我們也修改code值。
第三步,繼續執行:

data=ser.data
data['owner']='root'
data['code']='env'
ser=SnippetSerializer(s,data=data)
ser.is_valid()
ser.save()
ser.data

這裏的輸出爲:

{'id': 1, 'title': '', 'language': 'python', 'owner': 'admin', 'linenos': False, 'code': 'env', 'style': 'friendly'}

我們可以重新序列化一下s:

s=Snippet.objects.get(pk=1)
ser=SnippetSerializer(s)
ser.data

輸出爲:

{'id': 1, 'title': '', 'language': 'python', 'owner': 'admin', 'linenos': False, 'code': 'env', 'style': 'friendly'}

顯然,我們的確修改code的值,但是owner的值並沒有變爲root。當然,如果我們修改代碼爲:

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

取消ReadOnly屬性,然後重複上面的步驟,就會在save的時候報錯,錯誤信息爲”update()函數無法對採用了source='owner.username'這類形式的變量進行save,需要重寫update函數或者加入readonly屬性。”
現在,我們解決最後一個疑問,爲什麼要寫這行代碼?

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

查看我們的model.py:

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

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):
    create=models.DateTimeField(auto_now_add=True)
    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)
    owner=models.ForeignKey('auth.User',related_name='snippets',on_delete=models.CASCADE)
    highlighted=models.TextField()

    class Meta:
        ordering = ('create',)
    def save(self,*args,**kwargs):
        lexer=get_lexer_by_name(self.language)
        linenos='table' if self.linenos else False
        options={'title':self.title} if self.title else {}
        formatter = HtmlFormatter(style=self.style,linenos=linenos,full=True,**options)
        self.highlighted=highlight(self.code,lexer,formatter)
        super(Snippet,self).save(*args,**kwargs)

我們在聲明owner時代碼如下:

owner=models.ForeignKey('auth.User',related_name='snippets',on_delete=models.CASCADE)

我們沒有指明owner爲readonly,也沒有指明owner在序列化時要以User.username進行填充,這導致了兩個問題:

  • 我們可以像上一節一樣修改owner的值
  • 在序列化一個snippet對象後,owner的值是User.id

最重要的是,在model中,我們無法顯式聲明一個字段爲’read-only’,因爲這是序列化器的事。所以,我們無法在model中設置owner爲只讀。

小結

截止目前,我們實現了:

  • 向snippet model中加入了一個owner外鍵,關聯到User model的主鍵
  • 我們在snippet model中將這種關係命名爲snippets,從而我們可以由User反查Snippet
  • 我們在Snippet的序列化器中,聲明瞭owner字段在序列化和反序列化時的行爲如readonly,填充User.username
  • 我們在User的序列化器中加入了snippet字段,使得序列化和反序列化一個user對象時將snippets加入到其中

思考一下,我們完成了嗎?我們上面所有的操作,都沒有到達VIEW層面,也就是HTTP層面,現在不論是誰,都可以查詢任意用戶的snippets,也可以更新任意用戶的snippets,除了你不能將其他用戶的snippet佔爲己有。我們需要在VIEW層加入訪問權限

Object level permissions

我們跳過了Adding required permissions to viewsAdding login to the Browsable API兩節,這兩節內容很簡單,但你還是應該完成其中要求的代碼。完成之後,我們遇到一個問題,我們加入的權限控制爲permissions.IsAuthenticatedOrReadOnly,也就是說,現在如果你通過瀏覽器登錄成功之後,對任意用戶的snippets均具有讀寫權限,如果登錄失敗,則僅具有讀權限。這不是我們想要的,至少只是其中一部分。我們想要實現的是登錄之後的用戶,對自己的snippets具有完全的控制權,但是對其他用戶的snippets,則僅具有可讀權。我們需要實現自定義的permissions類:

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):
        # Read permissions are allowed to any request,
        # so we'll always allow GET, HEAD or OPTIONS requests.
        if request.method in permissions.SAFE_METHODS:
            return True

        # Write permissions are only allowed to the owner of the snippet.
        return obj.owner == request.user

我們研究一下這段代碼,首先我們創建了一個類IsOwnerOrReadOnly,它繼承於permissions.BasePermission,然後我們重寫了方法has_object_permission。這個方法有三個參數,request參數很好理解,代表我們發送的http請求對象,view參數,可以將其理解爲我們在snippets/urls.py定義的各個url,snippets/urls.py如下:

from django.conf.urls import url
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views

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

urlpatterns=format_suffix_patterns(urlpatterns)

view參數代表上面的每一個url指向的view類,最終我們會在每個view類中聲明permission_classes = (permissions.IsAuthenticatedOrReadOnly,),這樣每次調用has_object_permission函數時參數view會對應到各個view類上。最後一個參數obj代表我們每個訪問請求的對象,對應到我們的代碼中就是某個SnippetSerializer對象。可以將其理解爲資源。
我們繼續研究has_object_permission函數:

def has_object_permission(self, request, view, obj):
        # Read permissions are allowed to any request,
        # so we'll always allow GET, HEAD or OPTIONS requests.
        if request.method in permissions.SAFE_METHODS:
            return True

        # Write permissions are only allowed to the owner of the snippet.
        return obj.owner == request.user

對於if request.method in permissions.SAFE_METHODS:,我們首先要知道permissions.SAFE_METHODS,代表什麼,啓動命令行,我們看一下:
這裏寫圖片描述
顯然是一個元祖,其中定義了三種請求方式:GETHEADOPTIONS,如果你對HTTP的方法的定義比較理解,你會提出一個問題,GET方法依舊可以攜帶參數,很多時候POST的內容也可以以GET的方式作爲參數提交,如果僅僅以請求方式來判斷權限,是否能夠保證安全?答案是可以,因爲我們在urls.py中僅接受pk參數以GET的方式提交。
現在,不管你是否登錄成功,是否是你要訪問的SnippetSerializer對象的owner,你都可以查看SnippetSerializer對象序列化的內容(return True)。
最後我們對於request.methodPUTPOSTDELETE方式的請求,做了一個判斷,如果是某個SnippetSerializer對象的擁有者則返回True,否則返回False。
OK,完善後面的代碼,我們來測試一下是否成功了!

測試

你可以使用DRF文檔中介紹的方式來測試,我這裏採用burpsuite測試。

GET測試(List)

這裏寫圖片描述
成功獲取到了6組記錄

POST測試(Create)

這裏需要在HTTP頭中加入參數Authorization: Basic YWRtaW46cXdlcjEyMzQ=YWRtaW46cXdlcjEyMzQ=這段是【用戶名:密碼】的Base64編碼,在我測試時,即爲admin:qwer1234的Base64編碼,這是BasicAuthentication的基本要求,具體請百度。同時也要加入Content-Type: application/json爲HTTP頭。然後,我們嘗試創建一個屬於admin用戶的snippets:
這裏寫圖片描述
創建成功,你也可以測試一下將owner修改爲root,看最後創建的Snippets對象的owner是否會隨着你的修改而變化。

PUT測試(Update)

這裏寫圖片描述
修改成功,我們嘗試修改一下以admin用戶的身份修改一下root用戶的snippet:
這裏寫圖片描述
很不錯,權限控制代碼成功發揮了作用。
對於其他的測試如DELETE可以自行測試。

完了嗎?

不要忘記,我們還有一個問題沒有解答。
view.py中的SnippetList類中爲什麼要有下面這行代碼?

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

不多說,我們先去把這個代碼註釋掉,然後運行我們的程序,進行測試:
我們嘗試create一條snippets記錄:
這裏寫圖片描述
這裏寫圖片描述
報錯了,什麼意思呢?告訴我們owner_id這個字段不能爲空,至於爲什麼是owner_id而不是owner字段,參考Django官方文檔,在這裏不再贅述。也就是說,代碼沒有從我們我們的數據包中獲取到owner字段的值,但是我們POST的數據中明明包含了owner的值爲admin。
我們先通過一個實驗來解釋:
首先進入命令行,然後導入我們需要的包:

python manage.py shell
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from snippets.serializers import UserSerializer
from django.contrib.auth.models import User

然後,我們嘗試直接寫入一條記錄到 model Snippet中:

 s=Snippet(code="print helloworld",owner="admin")

看起來很美好,我們寫入了一個屬於admin用戶的snippet,結果卻報錯了:
這裏寫圖片描述
原因是owner應該是一個User記錄而不是一個字符串。
也就是說,如果我們修改view.py中內容如下:

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.data['owner'])

我們從POST過去的json中獲取owner的話,仍然會報錯,應爲它是一個字符串而不是一個User記錄。
這裏我們需要理解request.user的含義,它從我們POST的數據包的HTTP頭中獲取用戶名,並且將它轉換爲一個User記錄對象。如果我們將view.py修改爲:

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

然後POST一條數據的話,可以看到request.user是一個User對象。
這裏寫圖片描述
這樣就能夠序列化並保存了。

結語

寫這篇文章的目的,是因爲我自己也是一個DRF的初學者,在做練習的時候發現練習4還是比較難理解,尤其對我這種只用過FLASK的人來說,因此,將我學習過程中的一些分析和理解分享出來,希望對初學者有用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章