DRF 快速入门指南 – wiki基地


Django REST Framework (DRF) 快速入门指南:从零构建一个简单的 RESTful API

欢迎来到构建 Web API 的世界!如果你已经熟悉 Django,那么 Django REST Framework (DRF) 将是你构建强大、灵活的 RESTful API 的绝佳工具。DRF 在 Django 的基础上提供了大量开箱即用的功能,极大地简化了 API 的开发过程。

本指南将带你从零开始,一步步构建一个简单的 API,用于管理代码片段(snippets)。我们将学习 DRF 的核心组件:序列化器 (Serializers)、视图 (Views) 和 URL 路由 (URL Routing),并逐步引入更高级的概念,如泛型视图、ViewSets、认证和权限。

目标:

  • 理解 RESTful API 的基本概念。
  • 学会使用 DRF 的核心组件构建 API。
  • 了解 DRF 如何简化开发。
  • 构建一个可以对代码片段进行增、删、改、查 (CRUD) 的 API。

预备知识:

  • 对 Python 有基本了解。
  • 熟悉 Django 的基本概念,包括模型 (Models)、视图 (Views)、URL 配置 (URLconf) 和模板(虽然 API 开发中模板用得少)。
  • 已安装 Python 和 pip。

所需环境:

  • Python 3.6+
  • Django 3.2+
  • Django REST Framework

好的,让我们开始吧!

第一步:设置项目和安装 DRF

首先,创建一个新的 Django 项目和应用。

“`bash

确保你有一个虚拟环境,这是推荐的做法

python -m venv venv

source venv/bin/activate # macOS/Linux

venv\Scripts\activate # Windows

安装 Django 和 DRF

pip install Django djangorestframework Pygments

创建 Django 项目

django-admin startproject tutorialapi . # 注意末尾的点,表示在当前目录创建项目

创建 Django 应用

python manage.py startapp snippets

运行初始迁移

python manage.py migrate

创建一个超级用户,方便后续管理和测试(可选,但推荐)

python manage.py createsuperuser
“`

现在,将 rest_framework 和你刚刚创建的 snippets 应用添加到项目的 settings.py 文件中的 INSTALLED_APPS 中。

“`python

tutorialapi/settings.py

INSTALLED_APPS = [
‘django.contrib.admin’,
‘django.contrib.auth’,
‘django.contrib.contenttypes’,
‘django.contrib.sessions’,
‘django.contrib.messages’,
‘django.contrib.staticfiles’,
‘rest_framework’, # 添加 DRF
‘snippets’, # 添加你的应用
]

… 其他设置

“`

Pygments 是一个代码高亮库,DRF 的浏览器可浏览 API (Browsable API) 会使用它来美化代码片段的显示,所以我们也安装了它。

第二步:创建模型 (Model)

我们需要一个模型来存储我们的代码片段数据。在 snippets/models.py 中定义一个简单的 Snippet 模型。

“`python

snippets/models.py

from django.db import models
from pygments.lexers import get_all_lexers
from pygments.styles import get_all_styles
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):
created = 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, null=True)
highlighted = models.TextField(default=”, blank=True) # 存储代码高亮后的 HTML

class Meta:
    ordering = ['created'] # 默认按创建时间排序

def save(self, *args, **kwargs):
    """
    使用 `pygments` 库创建高亮的代码 HTML 表示。
    """
    lexer = get_all_lexers()[self.language] # Modified this line
    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().save(*args, **kwargs)

Fix for get_all_lexers issue: the lookup should be by language name, not index.

Also, need to handle potential KeyError if language is not found.

from pygments.lexers import get_lexer_by_name

class Snippet(models.Model):
created = 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, null=True)
highlighted = models.TextField(default=”, blank=True)

class Meta:
    ordering = ['created']

def save(self, *args, **kwargs):
    """
    使用 `pygments` 库创建高亮的代码 HTML 表示。
    """
    # Get the lexer for the chosen language
    try:
        lexer = get_lexer_by_name(self.language)
    except: # Fallback to a default lexer if the chosen one is not found (shouldn't happen with choices)
        lexer = get_lexer_by_name('python') # Or raise an error

    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().save(*args, **kwargs)

``
*(注:上面对
save方法中的get_all_lexers查找方式做了修正,使用get_lexer_by_name` 更稳健)*

这个模型很简单:包含代码、标题、语言、样式等字段。我们还添加了一个 highlighted 字段来存储代码高亮后的 HTML,并在 save 方法中利用 pygments 库自动生成它。owner 字段将关联代码片段到创建它的用户。

保存模型后,我们需要生成并运行迁移:

bash
python manage.py makemigrations snippets
python manage.py migrate

第三步:创建序列化器 (Serializers)

API 的核心任务之一是将复杂的数据类型(如 Django 模型实例或查询集)转换为可供 API 消费的格式(通常是 JSON),以及将接收到的数据(如 JSON)转换回复杂类型以便处理(如保存到数据库)。这个过程就是序列化 (Serialization)反序列化 (Deserialization)

DRF 提供了 Serializer 类来处理这个过程。Serializer 的工作方式与 Django 的 Form 类非常相似,它可以验证数据并将其转换成目标格式。

snippets 应用目录下创建一个 serializers.py 文件,并创建 SnippetSerializer

“`python

snippets/serializers.py

from rest_framework import serializers
from snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES

class SnippetSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True) # 自动生成的 ID,只读
title = serializers.CharField(required=False, allow_blank=True, max_length=100)
code = serializers.CharField(style={‘base_template’: ‘textarea.html’}) # 使用 textarea widget 在浏览器可浏览 API 中显示
linenos = serializers.BooleanField(required=False)
language = serializers.ChoiceField(choices=LANGUAGE_CHOICES, default=’python’)
style = serializers.ChoiceField(choices=STYLE_CHOICES, default=’friendly’)
owner = serializers.ReadOnlyField(source=’owner.username’) # 通过 source 指定关联字段,read_only=True 隐含

def create(self, validated_data):
    """
    根据验证后的数据创建并返回一个新的 `Snippet` 实例。
    """
    return Snippet.objects.create(**validated_data)

def update(self, instance, validated_data):
    """
    根据验证后的数据更新并返回一个已存在的 `Snippet` 实例。
    """
    instance.title = validated_data.get('title', instance.title)
    instance.code = validated_data.get('code', instance.code)
    instance.linenos = validated_data.get('linenos', instance.linenos)
    instance.language = validated_data.get('language', instance.language)
    instance.style = validated_data.get('style', instance.style)
    instance.save()
    return instance

“`

这个 SnippetSerializer 定义了需要序列化/反序列化的字段。id 是自动生成的,我们设为只读。其他字段对应模型中的字段。create()update() 方法分别处理创建和更新操作的逻辑,这是反序列化(将输入数据转换为模型实例)时会调用的方法。

使用 ModelSerializer 简化

手动定义每个字段并实现 create()update() 方法对于简单的模型来说有些繁琐。DRF 提供了一个强大的快捷方式:ModelSerializer。它会自动检查模型字段并生成对应的序列化器字段,并默认实现 create()update() 方法。

让我们用 ModelSerializer 重写 SnippetSerializer

“`python

snippets/serializers.py (使用 ModelSerializer)

from rest_framework import serializers
from snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES
from django.contrib.auth.models import User # 导入 User 模型

class SnippetSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source=’owner.username’) # 显示关联用户的用户名,只读

class Meta:
    model = Snippet
    fields = ['id', 'title', 'code', 'linenos', 'language', 'style', 'owner'] # 指定需要序列化/反序列化的字段
    # fields = '__all__' # 或者包含所有字段
    # exclude = ['highlighted'] # 或者排除某些字段

class UserSerializer(serializers.ModelSerializer):
# User 模型通过 Snippet 模型的 owner 字段与 Snippet 关联,
# related_name=’snippets’ 使得我们可以通过 user.snippets 访问该用户拥有的所有代码片段。
# 在序列化 User 时,我们希望显示这些相关的 snippet 的链接或数据。
snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all()) # 或者使用 HyperlinkedRelatedField

class Meta:
    model = User
    fields = ['id', 'username', 'snippets']

“`

使用 ModelSerializer 后,代码简洁了很多。我们在 Meta 类中指定了要关联的模型 (model = Snippet) 和要包含的字段 (fields = [...]). owner 字段我们仍然手动指定为 ReadOnlyField 并通过 source='owner.username' 来显示关联用户的用户名。

我们还添加了一个 UserSerializer,用于序列化 Django 的内置 User 模型。通过 snippets 字段,我们展示了如何处理模型之间的关系。这里使用了 PrimaryKeyRelatedField,它会通过主键 ID 来表示关联的对象。

第四步:创建视图 (Views)

视图是 API 的核心逻辑所在,它接收请求,调用序列化器处理数据,与模型交互,并返回响应。DRF 的视图与 Django 的视图类似,但提供了更多的功能,特别是在处理请求和生成响应方面。

DRF 提供了两种主要的视图类型:基于函数的视图 (Function-Based Views, FBVs) 和基于类的视图 (Class-Based Views, CBVs)。DRF 的 CBVs 又基于 Django 的 Viewgeneric CBVs 进行了扩展。

让我们先从基于函数的视图开始。在 snippets/views.py 中创建视图。

“`python

snippets/views.py

from rest_framework import status
from rest_framework.decorators import api_view # 用于 FBV
from rest_framework.response import Response # DRF 的 Response 类

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

@api_view([‘GET’, ‘POST’]) # 装饰器,指定该视图接受的 HTTP 方法
def snippet_list(request, format=None): # format 参数用于支持多种响应格式,如 .json 或 .api
“””
列出所有代码片段,或创建一个新的代码片段。
“””
if request.method == ‘GET’:
snippets = Snippet.objects.all()
serializer = SnippetSerializer(snippets, many=True) # many=True 表示序列化一个查询集(多个对象)
return Response(serializer.data) # 返回序列化后的数据

elif request.method == 'POST':
    serializer = SnippetSerializer(data=request.data) # request.data 自动处理 JSON 等多种输入格式
    if serializer.is_valid(): # 验证数据
        serializer.save() # 调用 Serializer 的 create() 方法保存到数据库
        return Response(serializer.data, status=status.HTTP_201_CREATED) # 返回创建成功的响应和数据
    return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) # 返回验证失败的错误信息

@api_view([‘GET’, ‘PUT’, ‘DELETE’])
def snippet_detail(request, pk, format=None):
“””
检索、更新或删除一个代码片段。
“””
try:
snippet = Snippet.objects.get(pk=pk)
except Snippet.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND) # 对象不存在返回 404

if request.method == 'GET':
    serializer = SnippetSerializer(snippet) # 序列化单个对象
    return Response(serializer.data)

elif request.method == 'PUT':
    serializer = SnippetSerializer(snippet, data=request.data) # 更新时传入实例和新数据
    if serializer.is_valid():
        serializer.save() # 调用 Serializer 的 update() 方法更新对象
        return Response(serializer.data)
    return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

elif request.method == 'DELETE':
    snippet.delete() # 删除对象
    return Response(status=status.HTTP_204_NO_CONTENT) # 删除成功返回 204 No Content

“`

这里的 api_view 装饰器是 DRF 提供的,它包装了标准的 Django 视图,使其能够接收 DRF 的 Request 对象并返回 DRF 的 Response 对象。Request 对象扩展了 Django 的 HttpRequest,提供了更灵活的请求解析功能 (request.data 自动处理 JSON, XML 等)。Response 对象则用于返回响应,它会自动渲染数据并设置合适的 Content-Type。

第五步:配置 URL 路由

现在我们需要将 URL 映射到我们刚刚创建的视图上。

首先,在项目根目录的 tutorialapi/urls.py 中包含 snippets 应用的 URL 配置:

“`python

tutorialapi/urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
path(‘admin/’, admin.site.urls),
path(”, include(‘snippets.urls’)), # 包含 snippets 应用的 URLconf
]
“`

然后,在 snippets 应用目录下创建一个 urls.py 文件,并定义具体的 URL 模式:

“`python

snippets/urls.py

from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns # 引入格式后缀模式
from snippets import views

urlpatterns = [
path(‘snippets/’, views.snippet_list), # 对应 /snippets/
path(‘snippets//’, views.snippet_detail), # 对应 /snippets/1/, /snippets/2/ 等
]

使用 format_suffix_patterns 包装 URL 模式,支持 URL 中带格式后缀,如 .json, .api

urlpatterns = format_suffix_patterns(urlpatterns)
“`

format_suffix_patterns 是一个方便的工具,它允许我们在 URL 中使用 .json.api 等后缀来指定请求的响应格式。例如,访问 /snippets/.json 将返回 JSON 格式的数据,访问 /snippets/.api 将返回 DRF 的浏览器可浏览 API 界面。

第六步:运行服务器并测试 API

到目前为止,我们已经完成了基本的 API 构建。现在可以运行服务器并测试了。

bash
python manage.py runserver

打开浏览器,访问 http://127.0.0.1:8000/snippets/。你会看到 DRF 提供的浏览器可浏览 API (Browsable API) 界面。这是一个非常方便的调试工具,可以直接在浏览器中查看数据、发送 POST/PUT/DELETE 请求。

  • 访问 http://127.0.0.1:8000/snippets/:查看所有代码片段(目前应该是空的)。
  • /snippets/ 页面,可以使用界面下方的表单创建一个新的代码片段(例如,填写 code: "print('hello')"language: "python"),然后点击 POST。如果成功,会返回新创建的代码片段的数据。
  • 访问 http://127.0.0.1:8000/snippets/1/ (如果第一个代码片段的 ID 是 1):查看特定代码片段的详情,也可以在该页面进行 PUT (更新) 或 DELETE (删除) 操作。

你也可以使用 curl 或 Postman 等工具来测试 API:

“`bash

获取所有 snippet

curl http://127.0.0.1:8000/snippets/

创建一个 snippet

curl -X POST -H “Content-Type: application/json” -d ‘{“code”:”print(\”another\”)”,”language”:”python”}’ http://127.0.0.1:8000/snippets/

获取 ID 为 1 的 snippet

curl http://127.0.0.1:8000/snippets/1/

更新 ID 为 1 的 snippet

curl -X PUT -H “Content-Type: application/json” -d ‘{“code”:”print(\”updated\”)”,”title”:”Updated Snippet”,”language”:”python”}’ http://127.0.0.1:8000/snippets/1/

删除 ID 为 1 的 snippet

curl -X DELETE http://127.0.0.1:8000/snippets/1/ -I # -I 只看响应头
“`

第七步:使用基于类的视图 (CBVs) 简化代码

虽然基于函数的视图易于理解,但当逻辑变得复杂或需要在多个视图之间共享行为时,基于类的视图通常是更好的选择。DRF 提供了 APIView 以及一系列更强大的泛型视图和混合类 (Mixins)。

让我们用 APIView 重写上面的视图。

“`python

snippets/views.py (使用 APIView)

from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from django.http import Http404
from rest_framework.views import APIView # 导入 APIView
from rest_framework.response import Response
from rest_framework import status

class SnippetList(APIView):
“””
列出所有代码片段,或创建一个新的代码片段。
“””
def get(self, request, format=None):
snippets = Snippet.objects.all()
serializer = SnippetSerializer(snippets, many=True)
return Response(serializer.data)

def post(self, request, format=None):
    serializer = SnippetSerializer(data=request.data)
    if serializer.is_valid():
        serializer.save()
        return Response(serializer.data, status=status.HTTP_201_CREATED)
    return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

class SnippetDetail(APIView):
“””
检索、更新或删除一个代码片段。
“””
def get_object(self, pk):
try:
return Snippet.objects.get(pk=pk)
except Snippet.DoesNotExist:
raise Http404 # 使用 Django 的 Http404 异常

def get(self, request, pk, format=None):
    snippet = self.get_object(pk)
    serializer = SnippetSerializer(snippet)
    return Response(serializer.data)

def put(self, request, pk, format=None):
    snippet = self.get_object(pk)
    serializer = SnippetSerializer(snippet, data=request.data)
    if serializer.is_valid():
        serializer.save()
        return Response(serializer.data)
    return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

def delete(self, request, pk, format=None):
    snippet = self.get_object(pk)
    snippet.delete()
    return Response(status=status.HTTP_204_NO_CONTENT)

“`

snippets/urls.py 中更新 URL 配置以指向这些基于类的视图:

“`python

snippets/urls.py (使用基于类的视图)

from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views

urlpatterns = [
path(‘snippets/’, views.SnippetList.as_view()), # 注意这里使用 .as_view()
path(‘snippets//’, views.SnippetDetail.as_view()),
]

urlpatterns = format_suffix_patterns(urlpatterns)
“`

现在,再次运行服务器并测试,你会发现 API 的行为是完全一样的,但视图代码使用了更结构化的方式。

第八步:使用泛型视图和混合类 (Generic Views & Mixins) 进一步简化

DRF 的泛型视图和混合类是对 APIView 的进一步抽象,它们提供了常用的视图行为,如列表、创建、检索、更新、删除等。通过继承这些混合类,可以非常快速地构建出常用的 API 视图。

常用的混合类有:
* ListModelMixin:处理列表查询 (GET)
* CreateModelMixin:处理创建 (POST)
* RetrieveModelMixin:处理详情查询 (GET)
* UpdateModelMixin:处理更新 (PUT/PATCH)
* DestroyModelMixin:处理删除 (DELETE)

DRF 提供了一些内置的泛型视图,它们是 GenericAPIView 和一个或多个混合类的组合。例如:
* ListCreateAPIView = GenericAPIView + ListModelMixin + CreateModelMixin (用于处理列表和创建)
* RetrieveUpdateDestroyAPIView = GenericAPIView + RetrieveModelMixin + UpdateModelMixin + DestroyModelMixin (用于处理详情、更新和删除)

让我们使用这些泛型视图重写我们的视图。

“`python

snippets/views.py (使用泛型视图和混合类)

from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from rest_framework import generics # 导入泛型视图模块

class SnippetList(generics.ListCreateAPIView):
“””
列出所有代码片段,或创建一个新的代码片段。
“””
queryset = Snippet.objects.all() # 指定查询集
serializer_class = SnippetSerializer # 指定使用的序列化器

class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
“””
检索、更新或删除一个代码片段。
“””
queryset = Snippet.objects.all() # 指定查询集
serializer_class = SnippetSerializer # 指定使用的序列化器
“`

哇!代码变得非常简洁。泛型视图帮我们处理了大部分繁琐的逻辑。我们只需要指定 queryset (视图应该操作的数据集合) 和 serializer_class (视图应该使用哪个序列化器)。

URL 配置保持不变,因为它仍然指向这些类视图的 .as_view() 方法。

“`python

snippets/urls.py (保持不变)

from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views

urlpatterns = [
path(‘snippets/’, views.SnippetList.as_view()),
path(‘snippets//’, views.SnippetDetail.as_view()),
]

urlpatterns = format_suffix_patterns(urlpatterns)
“`

再次测试,功能完全正常。

第九步:添加用户关联和权限控制

目前我们的 API 是完全公开的,任何人都可以创建、修改和删除代码片段。在真实的 API 中,我们需要添加认证和权限控制。

  1. 关联用户到 Snippet:
    我们在模型中已经添加了 owner 字段。现在需要在创建 Snippet 时自动将当前登录用户设置为 owner。

    SnippetSerializer 中,owner 字段是只读的 (ReadOnlyField),这意味着它不会被用于反序列化时的验证和保存。我们需要在视图中处理设置 owner 的逻辑。

    在使用泛型视图时,可以通过重写 perform_create()perform_update() 方法来实现。

    首先,确保你的视图类导入了 permission_classes
    “`python

    snippets/views.py (添加权限)

    from snippets.models import Snippet
    from snippets.serializers import SnippetSerializer
    from rest_framework import generics
    from rest_framework import permissions # 导入权限模块
    from django.contrib.auth.models import User # 导入 User 模型
    from snippets.serializers import UserSerializer # 导入 UserSerializer
    ``
    更新
    SnippetListSnippetDetail` 视图:

    “`python

    snippets/views.py (添加权限和关联用户)

    … (之前的导入)

    from rest_framework import permissions # 导入权限模块

    class SnippetList(generics.ListCreateAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer
    # 只有认证用户才能创建,匿名用户只能读取
    permission_classes = [permissions.IsAuthenticatedOrReadOnly]

    def perform_create(self, serializer):
        # 在创建时,将请求的用户设置为代码片段的 owner
        serializer.save(owner=self.request.user)
    

    class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer
    # 只有代码片段的 owner 才能修改和删除,其他用户只能读取
    permission_classes = [permissions.IsAuthenticatedOrReadOnly,
    permissions.IsAuthenticated] # 这个权限类组合有问题,后面会修正

    # 正确的权限类组合应该是自定义一个权限类,或者使用DRF内置的 Object-level permissions
    # 比如只允许 owner 修改/删除
    # permission_classes = [permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly] # 需要实现 IsOwnerOrReadOnly
    
    # 先使用一个简单的组合,要求认证且是 owner
    # 暂时不实现自定义权限,先用 IsAuthenticatedOrReadOnly 配合下面的 queryset 过滤
    

    … 添加 UserList 和 UserDetail 视图 (用于查看用户及其关联的 snippets)

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

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

    ``
    我们将
    permission_classes添加到视图类中。permissions.IsAuthenticatedOrReadOnly` 表示已认证的用户拥有完全权限(读写),未认证的用户只有读取权限。

    perform_create() 方法在序列化器的 save() 方法被调用时执行。我们重写它来在保存之前,将请求中的用户 (self.request.user) 作为 owner 传递给 serializer.save()

  2. 添加用户相关的 URL:
    snippets/urls.py 中为 UserListUserDetail 添加 URL:

    “`python

    snippets/urls.py (添加用户 URL)

    from django.urls import path
    from rest_framework.urlpatterns import format_suffix_patterns
    from snippets import views

    urlpatterns = [
    path(‘snippets/’, views.SnippetList.as_view()),
    path(‘snippets//’, views.SnippetDetail.as_view()),
    path(‘users/’, views.UserList.as_view()), # 用户列表
    path(‘users//’, views.UserDetail.as_view()), # 用户详情
    ]

    urlpatterns = format_suffix_patterns(urlpatterns)
    “`

  3. 测试权限:
    运行服务器。尝试用浏览器访问 /snippets/,你会看到一个登录框 (因为未认证用户只能读取)。尝试 POST 一个新的 snippet,DRF 会要求你登录。

    使用 curl 测试:

    “`bash

    未认证用户获取列表 (应该成功)

    curl http://127.0.0.1:8000/snippets/

    未认证用户创建 snippet (应该失败,返回 403 Forbidden)

    curl -X POST -H “Content-Type: application/json” -d ‘{“code”:”print(\”unauthenticated\”)”,”language”:”python”}’ http://127.0.0.1:8000/snippets/
    “`

    要测试认证用户的权限,你需要在请求中包含认证凭据。DRF 提供了多种认证方式,比如 Token 认证、Session 认证等。浏览器可浏览 API 默认使用 Session 认证(如果你已登录 Django 后台)。使用 curl 可以使用 Token 认证(需要先配置 Token 认证并在 admin 后台为用户生成 token)或 Basic Auth。

    例如,假设你配置了 Basic Auth 并在 admin 创建了用户 testuser 密码 password
    “`bash

    认证用户创建 snippet (应该成功)

    curl -u testuser:password -X POST -H “Content-Type: application/json” -d ‘{“code”:”print(\”authenticated\”)”,”language”:”python”}’ http://127.0.0.1:8000/snippets/
    “`

  4. 对象级权限 (Object-level Permissions):
    IsAuthenticatedOrReadOnly 只能控制用户是否能访问视图,但不能控制用户是否能访问或修改特定的对象。我们想要实现的是:只有代码片段的 owner 才能修改或删除它,其他用户只能查看。

    为了实现这一点,我们需要定义一个自定义权限类。在 snippets 应用目录下创建一个 permissions.py 文件:

    “`python

    snippets/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
    
        # 写入权限只允许对象的所有者。
        return obj.owner == request.user
    

    ``
    这个权限类继承自
    permissions.BasePermission并实现了has_object_permission方法。这个方法在调用像get_object()queryset.get()` 这样的对象级操作时会被 DRF 调用。它接收请求、视图和对象实例作为参数。

    现在,在 snippets/views.py 中导入并使用这个自定义权限类:

    “`python

    snippets/views.py (使用自定义权限类)

    … (之前的导入)

    from snippets.permissions import IsOwnerOrReadOnly # 导入自定义权限类

    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
    # 未认证只能读,认证用户可以读,但只有 owner 才能修改/删除
    permission_classes = [permissions.IsAuthenticatedOrReadOnly,
    IsOwnerOrReadOnly] # 同时使用多个权限类,必须所有都通过才允许访问
    ``
    现在,
    SnippetDetail视图结合了IsAuthenticatedOrReadOnlyIsOwnerOrReadOnly两个权限。未认证用户只能通过IsAuthenticatedOrReadOnly的读取部分;认证用户需要同时通过两个权限类:IsAuthenticatedOrReadOnly(已认证) 和IsOwnerOrReadOnly(是否是对象 owner)。对于非安全方法 (PUT, DELETE),只有 owner 才能通过IsOwnerOrReadOnly` 的检查。

再次测试,你会发现未认证用户无法创建或修改/删除任何 snippet;认证用户可以创建 snippet (owner 是自己),可以修改/删除自己的 snippet,但无法修改/删除其他用户的 snippet。

第十步:使用 ViewSets 和 Routers 进一步简化

当你的 API 遵循标准的 CRUD 操作时,ViewSets 可以极大地减少代码量。ViewSets 将列表、创建、检索、更新、删除等操作逻辑集中到一个类中,而不是像泛型视图那样为每个操作定义一个单独的类。

ModelViewSet 是一个常用的 ViewSet,它直接对应于模型,提供了完整的 CRUD 操作。

让我们用 ModelViewSet 重写视图。

“`python

snippets/views.py (使用 ViewSets)

from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from django.contrib.auth.models import User
from snippets.serializers import UserSerializer
from rest_framework import viewsets # 导入 ViewSets 模块
from rest_framework import permissions
from snippets.permissions import IsOwnerOrReadOnly # 导入自定义权限类

class SnippetViewSet(viewsets.ModelViewSet):
“””
这个 ViewSet 自动提供 list, create, retrieve, updatedestroy 操作。

它还提供了一个额外的 `highlight` 操作。
"""
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly,
                      IsOwnerOrReadOnly]

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

# 添加一个自定义的 action 来获取高亮的代码
from rest_framework.decorators import action
from rest_framework.response import Response
from pygments import highlight
from pygments.formatters.html import HtmlFormatter
from pygments.lexers import get_lexer_by_name

@action(detail=True, renderer_classes=[HtmlRenderer]) # detail=True 表示作用于单个对象
def highlight(self, request, *args, **kwargs):
    snippet = self.get_object() # 获取当前对象
    return Response(snippet.highlighted) # 返回高亮后的 HTML

需要导入 HtmlRenderer

from rest_framework.renderers import HTMLRenderer

class UserViewSet(viewsets.ReadOnlyModelViewSet):
“””
这个 ViewSet 自动提供 listretrieve 操作。
“””
queryset = User.objects.all()
serializer_class = UserSerializer
``
我们将
SnippetListSnippetDetail的逻辑合并到了SnippetViewSet中。同样,UserListUserDetail合并到了UserViewSet中,这里使用了ReadOnlyModelViewSet,因为它只提供读取 (listretrieve`) 操作。

ModelViewSet 会自动提供标准的 CRUD 操作。我们仍然重写了 perform_create 来设置 owner,并添加了自定义的 highlight action 来演示如何为 ViewSet 添加额外的操作。@action 装饰器用于标记一个方法是一个自定义的 action。detail=True 表示这个 action 应用于一个特定的对象实例 (需要提供 pk),否则 (detail=False) 表示应用于整个列表。renderer_classes=[HtmlRenderer] 指定了这个 action 的响应应该使用 HTML 渲染器。

使用 Routers 自动配置 URL:

使用 ViewSets 的最大好处是可以结合 DRF 的 Routers 来自动生成 URL 配置。这极大地简化了 URL 的管理。

snippets/urls.py 中修改 URL 配置,使用 Router:

“`python

snippets/urls.py (使用 ViewSets 和 Routers)

from django.urls import path, include
from rest_framework.routers import DefaultRouter # 导入 Router
from snippets import views

创建一个 Router 实例

router = DefaultRouter()

注册 ViewSet 到 Router

第一个参数是 URL 前缀(用于生成 URL),第二个参数是 ViewSet 类,第三个参数是可选的 base_name

base_name 会用于生成 URL 名称,例如 ‘snippet-list’, ‘snippet-detail’ 等

router.register(r’snippets’, views.SnippetViewSet, basename=’snippet’)
router.register(r’users’, views.UserViewSet, basename=’user’)

Router 会自动生成 URL 模式

urlpatterns = [
path(”, include(router.urls)), # 将 Router 生成的 URL 模式包含到主 URL 配置中
]

不再需要 format_suffix_patterns,Router 默认支持格式后缀

urlpatterns = format_suffix_patterns(urlpatterns) # 移除这行

``
tutorialapi/urls.py` 中,如果你的 snippets URL 是直接包含在根 URLconf 中的,结构会是这样:

“`python

tutorialapi/urls.py (使用 Router)

from django.contrib import admin
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from snippets import views # 在这里导入 views

router = DefaultRouter()
router.register(r’snippets’, views.SnippetViewSet, basename=’snippet’)
router.register(r’users’, views.UserViewSet, basename=’user’)

urlpatterns = [
path(‘admin/’, admin.site.urls),
path(”, include(router.urls)), # 直接在项目 URLconf 中包含 Router 生成的 URL
# 或者保持之前的结构,如果 snippets 有自己的 urls.py:
# path(”, include(‘snippets.urls’)),
]
``
*(建议将 Router 相关的代码放在应用内部的
urls.py中,然后在项目 URLconf 中 include 它,这样更模块化。上面的snippets/urls.py` 示例是更推荐的方式。)*

运行服务器,访问 http://127.0.0.1:8000/,你会看到 Router 自动生成的 API 根页面,列出了可用的端点:/snippets//users/

测试 /snippets/1/highlight/ 端点,你会看到高亮后的代码片段的 HTML 版本。

至此,我们已经使用 ViewSets 和 Routers 极大地简化了代码,并且实现了完整的 CRUD 功能、用户关联、以及对象级的权限控制。

超越入门:更多 DRF 特性

本指南仅仅是 DRF 的冰山一角。DRF 还提供了许多强大的功能来帮助你构建更复杂的 API:

  • 认证 (Authentication): 除了 Session 认证,DRF 还支持 Token 认证、OAuth2 等。
  • 限流 (Throttling): 控制请求频率,防止滥用。
  • 过滤 (Filtering): 允许客户端根据字段过滤数据(例如,/snippets/?language=python)。
  • 搜索 (Searching): 允许客户端进行全文搜索。
  • 分页 (Pagination): 分页返回大量数据。
  • 版本控制 (Versioning): 管理 API 的不同版本。
  • 测试 (Testing): 方便地测试 API 端点。
  • 文档 (Documentation): 集成 API 文档生成工具(如 Swagger/OpenAPI)。
  • 渲染器 (Renderers): 支持多种响应格式(JSON, XML, HTML 等)。
  • 解析器 (Parsers): 支持多种请求体格式。

在实际开发中,你会经常使用这些功能来构建健壮、易于维护和扩展的 API。

总结

在本快速入门指南中,我们从零开始,学习了如何使用 Django REST Framework 构建一个简单的 RESTful API。我们介绍了 DRF 的核心概念:

  • 序列化器 (Serializers): 将 Django 模型或其他数据类型转换为可供 API 使用的格式(如 JSON),并处理反序列化和数据验证。我们看到了 Serializer 和更方便的 ModelSerializer
  • 视图 (Views): 处理 API 请求和响应的逻辑。我们经历了从基于函数的视图到基于类的 APIView,再到高度抽象的泛型视图 (ListCreateAPIView, RetrieveUpdateDestroyAPIView),最终使用了 ModelViewSet 这种将所有标准操作集合在一起的方式。
  • URL 路由 (URL Routing): 将 URL 映射到视图。我们学习了如何手动配置 URL,以及如何使用 Routers 自动生成 ViewSet 的 URL。
  • 认证和权限 (Authentication & Permissions): 学习了如何限制 API 的访问,关联用户到数据,并实现了对象级的权限控制 (IsOwnerOrReadOnly)。

通过这个过程,你应该对 DRF 的基本工作流程有了清晰的认识,并掌握了构建简单 API 所需的核心技能。DRF 的设计哲学是可组合性和灵活性,你可以根据项目的需求选择不同级别的抽象(从低层的 APIView 到高层的 ModelViewSet)。

现在,你可以继续深入学习 DRF 的其他强大功能,并开始构建更复杂、更实用的 API 了!

祝你编码愉快!

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部