DRF 框架基础入门

作者:user 发布日期: 浏览量:173

前言

  • 使用DRF开发RESTful API接口
  • 内容包括:序列化(serializers)、视图集(viewsets)、路由(routers)、认证(authentication)、权限(permission)
  • 掌握DRF的多种视图实现信息的增删改查

1、理解前后端分离

  • 交互形式:前后端分离时,前端(客户端)只需要进行页面展示,后端(服务端)主要为前端实现业务逻辑和数据准备,前后端分离的架构里,后端需要按照约定的数据格式向前端提供可调用的API接口,前端通过不同的
    HTTP Method 与后端进行交互获取数据,然后组装渲染
  • 代码组织方式:前后端未分离、前后端半分离(部分分离)、前后端分离(前后端独立部署)
  • 开发模式:不分离时,前端开发,然后翻译为模版,前后端对接

  • 数据接口规范流程

2、深入理解 RESTful API

  • 协议:API与用户的通信协议,尽量都使用 HTTPs 协议
  • 域名:应该尽量将 API 部署在专用域名之下
https://api.example.com
# 如果确定API很简单,不会有进一步扩展,可以考虑放在主域名下
https://example.com/api/
  • 版本(Versioning):应该将API的版本号放入URL
https://api.example.com/v1/

-

路径(Endpoint):路径又称“终点”,表示API的具体网址,每一个网址代表一种资源(resource),所以网址中不能有动词,只能有名词,一般数据库表中都是同种记录的“集合(collection)”,所以API中的名词也应该使用复数

https://api.example.com/api/v1/employees
  • HTTP动词:对于资源的具体操作类型,由 HTTP 动词表示,常用的 HTTP 动词有下面五个(括号里是对应的SQL命令)

GET(SELECT):从服务器取出资源(一项或多项)
POST(CREATE):在服务器新建一个资源
PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)
PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)
DELETE(DELETE):从服务器删除资源

  • 过滤信息(Filtering):如果记录数量很多,服务器不可能将它们全部返回给用户。API应该提供参数,过滤返回结果

?limit=10:指定返回记录的数量
?offset=10:指定返回记录开始的位置
?page=2&per_page=100:指定第几页,以及每页的记录数
?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序
?animal_type_id=1:指定筛选条件

  • 状态码(Status Codes):服务器向用户返回的状态码和提示信息
200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的
201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功
202 Accepted - [*]:表示请求已经进入后台排队(异步任务)
204 NO CONTENT - [DELETE]:用户删除数据成功
400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有响应
401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)
403 Forbidden - [*]:表示用户得到授权(与401错误相对),但是访问被禁止的
404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作
406 Not Acceptable - [GET]:用户请求的格式不可得
410 Gone - [GET]:用户请求的资源被永久删除,且不会再得到
422 Unprocesable entity - [POST/PUT/PATCH]:当创建一个对象时,发生验证错误
500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户无法判断发出的请求是否成功
  • 错误处理(Error handling):如果状态码是 4XX,就应该向用户返回错误信息。一般在返回信息中将 error 作为键名,错误信息作为键值
{
  "error": "Invalid API key"
}
  • 返回结果:针对不同的操作,服务器向用户返回的结果应该符合以下规范

GET /collection:返回资源对象的列表(数组)
GET /collection/resource:返回单个资源对象
POST /collection:返回新生成的资源对象
PUT /collection/resource:返回完整的资源对象
PATCH /collection/resource:返回完整的资源对象
DELETE /collection/resource:返回一个空文档

  • Hypermedia API:RESTful API 最好做到 Hypermedia,即返回结果中提供链接,连向其他API方法

一、初始化 Django 环境

1、初始化 Django 环境

  • django 安装
pip install django
  • django 创建项目
django-admin startproject <项目名>
  • 修改 settings.py 文件
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = ["*"]


TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates'],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]


LANGUAGE_CODE = 'zh-hans'  # 'en-us'

TIME_ZONE = 'Asia/Shanghai'  # 'UTC'

USE_I18N = True

USE_TZ = False  # True:使用UTC时间,False:使用UTC时间

STATIC_URL = 'static/'
STATIC_ROOT = BASE_DIR / 'static'  # 用于指定生产环境中静态文件的最终收集路径

STATICFILES_DIRS = [  # 用于开发阶段 ,指定额外的静态文件搜索路径 (除各应用的 static/ 目录外)
    BASE_DIR / 'staticfiles'
]
  • 修改 urls.py 文件
from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import path

urlpatterns = [
    path('admin/', admin.site.urls),
]
# 开发环境:将 STATIC_URL 映射到 STATIC_ROOT 或 STATICFILES_DIRS
if settings.DEBUG:
    urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
  • 创建数据表
python manage.py makemigrations

python manage.py migrate
  • 创建超级管理员用户
python manage.py createsuperuser

# admin Admin123@
  • 运行项目
python manage.py runserver [[0.0.0.0:]8000]

2、安装 DRF(Django REST Framework)

pip install djangorestframework
  • 在 settings.py 中添加
INSTALLED_APPS = [
    ...
    'rest_framework',
    'rest_framework.authtoken', # DRF自带的Token认证
]

# DRF 相关的全局配置
REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', # 使用 DRF 的分页
    'PAGE_SIZE': 50, # 每页条数
    'DATETIME_FORMAT': '%Y-%m-%d %H:%M:%S', # 接口返回的时间格式
    'DEFAULT_RENDER_CLASSES': [ # 当DRF返回Response对象时使用哪个 render 类,这里可以不用写,默认就是这个配置
        'rest_framework.render.JSONRenderer',
        'rest_framework.render.BrowsableAPIRenderer',
    ],
    'DEFAULT_PARSER_CLASSES': [ # 解析request.data
        'rest_framework.parsers.JSONParser',
        'rest_framework.parsers.FormParser',
        'rest_framework.parsers.MultiPartParser',
    ],
    'DEFAULT_PERMISSION_CLASSES': [ # 权限相关
        'rest_framework.permissions.IsAuthenticated'  # 要求用户登录
    ],
    'DEFAULT_AUTHENTICATION_CLASSES': [ # 认证相关
        'rest_framework.authentication.BasicAuthentication',
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.TokenAuthentication',
    ]
}
  • 在 urls.py 中配置
from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('api-auth/', include('rest_framework.urls')),  # DRF的登录和退出
    path('admin/', admin.site.urls),
]
# 开发环境:将 STATIC_URL 映射到 STATIC_ROOT 或 STATICFILES_DIRS
if settings.DEBUG:
    urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

二、Django 与 DRF 开发

1、DRF 序列化

  • 序列化:序列化就是将 queryset、instance 等Django的数据类型转化为 JSON、XML、YML 的数据类型
  • 反序列化:就是将序列化的过程反过来
  • Django 序列化:
from django.core import serializers

json_data = serializers.serialize('json', queryset_data)
  • DRF 序列化
from django.db import models
from django.conf import settings


# Create your models here.
class CourseModel(models.Model):
    name = models.CharField(max_length=255, unique=True, help_text="课程名称", verbose_name="课程名称")
    introduction = models.TextField(help_text="课程介绍", verbose_name="课程介绍")
    price = models.DecimalField(max_digits=6, decimal_places=2, help_text="课程价格", verbose_name="课程价格")
    teacher = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, help_text="课程讲师",
                                verbose_name="课程讲师")
    create_time = models.DateTimeField(auto_now_add=True, help_text="创建时间", verbose_name="创建时间")
    update_time = models.DateTimeField(auto_now=True, help_text="更新时间", verbose_name="更新时间")

    class Meta:
        verbose_name = "课程信息"
        verbose_name_plural = verbose_name
        ordering = ('price',)

    def __str__(self):
        return self.name
from rest_framework import serializers

from .models import CourseModel
from django.contrib.auth.models import User


class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = '__all__'


class CourseSerializer(serializers.ModelSerializer):
    teacher = serializers.ReadOnlyField(source='teacher.username')

    class Meta:
        model = CourseModel
        # exclude = ('id',)
        fields = ('id', 'name', 'introduction', 'teacher', 'price', 'create_time', 'update_time')
        # fields = "__all__"

2、DRF视图开发RESTful API接口

2-1、函数式编程 Function Based View

  • Django 函数视图开发
import json

from django.http import HttpResponse, JsonResponse
from django.views.decorators.csrf import csrf_exempt

from .models import CourseModel

"""
函数视图 Function Based View
"""


# csrf_exempt 的作用是在POST请求时取消CSRF的限制
@csrf_exempt
def query_course(request):
    course_dict = {
        'name': '课程名称',
        'introduction': '课程介绍',
        'price': '12.50'
    }
    if request.method == 'GET':
        # return HttpResponse(json.dumps(course_dict), content_type="application/json")
        # 等价于
        return JsonResponse(course_dict)
    if request.method == 'POST':
        course = json.loads(request.body.decode('utf-8'))
        return JsonResponse(course, safe=False)
  • DRF 函数视图
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response

from .models import CourseModel
from .serializers import CourseSerializer

"""
DRF 函数视图 Function Based View
"""


@api_view(['GET', 'POST'])
def course_list(request):
    if request.method == 'GET':
        course_list = CourseSerializer(instance=CourseModel.objects.all(), many=True)
        return Response(data=course_list.data, status=status.HTTP_200_OK)
    elif request.method == 'POST':
        r = CourseSerializer(data=request.data, partial=True)  # partial=True 表示可以部分更新
        if r.is_valid():
            r.save(teacher=request.user)  # 在保存时,可以单独对某个字段传值
            return Response(data=r.data, status=status.HTTP_201_CREATED)
        return Response(r.errors, status=status.HTTP_400_BAD_REQUEST)


@api_view(['GET', 'PUT', 'DELETE'])
def course_detail(request, pk):
    """
    获取、更新、删除一个课程
    :param request:
    :param pk:
    :return:
    """
    try:
        course = CourseModel.objects.get(pk=pk)
    except Exception as e:
        return Response(data={'msg:'"没有此课程信息"}, status=status.HTTP_404_NOT_FOUND)
    else:
        if request.method == 'GET':
            course_list = CourseSerializer(instance=course)
            return Response(data=course_list.data, status=status.HTTP_200_OK)
        elif request.method == 'PUT':
            r = CourseSerializer(instance=course, data=request.data, partial=True)  # partial=True 表示可以部分更新
            if r.is_valid():
                r.save()  # 在更新时,已经有用户了
                return Response(data=r.data, status=status.HTTP_200_OK)
            return Response(r.errors, status=status.HTTP_400_BAD_REQUEST)
        elif request.method == 'DELETE':
            course.delete()
            return Response(status=status.HTTP_204_NO_CONTENT)
  • urls.py
from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    # path('api-auth/', include('rest_framework.urls')),  # DRF的登录和退出
    path('admin/', admin.site.urls),
    path('course/', include('course.urls'))
]
# 开发环境:将 STATIC_URL 映射到 STATIC_ROOT 或 STATICFILES_DIRS
if settings.DEBUG:
    urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
from django.urls import path
from .views import query_course, course_detail

urlpatterns = [
    path('fbv/list/', query_course, name="fbv-list"),
    path('fbv/detail/<int:pk>', course_detail, name="fbv-detail")
]

2-2、类视图 Classed Based View

  • Django 类视图开发
import json

from django.http import HttpResponse, JsonResponse
from django.views import View
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt

from .models import CourseModel

"""
类视图 Classed Based View
"""

# 既可以用方法装饰器在类上声明使用 csrf_exempt
@method_decorator(csrf_exempt, name='dispatch')
class CourseView(View):
    course_dict = {
        'name': '课程名称',
        'introduction': '课程介绍',
        'price': '12.50'
    }

    def get(self):
        return JsonResponse(self.course_dict)

    # 也可以直接在方法上使用
    # @csrf_exempt
    def post(self, request):
        course = json.loads(request.body.decode('utf-8'))
        return JsonResponse(course, safe=False)
  • DRF 类视图
from rest_framework import status
# from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework.views import APIView

from .models import CourseModel
from .serializers import CourseSerializer


"""
DRF 类视图 Classed Based View
"""


class CourseList(APIView):
    def get(self, request):
        """
        查询课程信息
        :param request:
        :return:
        """
        print("here...")
        queryset = CourseModel.objects.all()
        r = CourseSerializer(instance=queryset, many=True)
        return Response(data=r.data, status=status.HTTP_200_OK)

    def post(self, request):
        """
        添加课程信息
        :param request:
        :return:
        """
        r = CourseSerializer(data=request.data)
        if r.is_valid():
            r.save(teacher=self.request.user)
            return Response(data=r.data, status=status.HTTP_201_CREATED)
        return Response(r.errors, status=status.HTTP_400_BAD_REQUEST)


class CourseDetail(APIView):
    @staticmethod
    def get_course_instance(pk):
        """
        根据id查询课程
        :param pk:课程ID
        :return:
        """
        try:
            return CourseModel.objects.get(pk=pk)
        except Exception as e:
            return

    def get(self, request, pk):
        """
        获取课程信息
        :param request:
        :param pk:
        :return:
        """
        course = self.get_course_instance(pk=pk)
        if not course:
            return Response(data={'msg:'"没有此课程信息"}, status=status.HTTP_404_NOT_FOUND)
        r = CourseSerializer(instance=course)
        return Response(data=r.data, status=status.HTTP_200_OK)

    def put(self, request, pk):
        """
        更新课程信息
        :param request:
        :param pk:
        :return:
        """

        course = self.get_course_instance(pk=pk)
        if not course:
            return Response(data={'msg:'"没有此课程信息"}, status=status.HTTP_404_NOT_FOUND)
        r = CourseSerializer(instance=course, data=request.data)
        if r.is_valid():
            r.save()
            return Response(data=r.data, status=status.HTTP_201_CREATED)
        return Response(r.errors, status=status.HTTP_400_BAD_REQUEST)

    def delete(self, request, pk):
        """
        删除课程
        :param request:
        :param pk:
        :return:
        """
        r = self.get_course_instance(pk=pk)
        if not r:
            return Response(data={'msg:'"没有此课程信息"}, status=status.HTTP_404_NOT_FOUND)
        r.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)
  • urls.py
from django.urls import path
from .views import course_list, course_detail, CourseList, CourseDetail

urlpatterns = [
    path('fbv/list/', course_list, name="fbv-list"),
    path('fbv/detail/<int:pk>', course_detail, name="fbv-detail"),
    path('cbv/list/', CourseList.as_view(), name="cbv-list"),
    path('cbv/detail/<int:pk>', CourseDetail.as_view(), name="cbv-detail"),
]

2-3、通用类视图 Generic Classed Based View

  • DRF 通用类视图
# from rest_framework import status
# from rest_framework.decorators import api_view
# from rest_framework.response import Response
# from rest_framework.views import APIView
from rest_framework import generics

from .models import CourseModel

from .serializers import CourseSerializer


"""
DRF 通用类视图 Generic Classed Based View
"""


class GCourseList(generics.ListCreateAPIView):
    queryset = CourseModel.objects.all()
    serializer_class = CourseSerializer

    def perform_create(self, serializer):
        """
        重写该方法以实现新增时使用当前登录用户
        :param serializer:
        :return:
        """
        serializer.save(teacher=self.request.user)


class GCourseDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = CourseModel.objects.all()
    serializer_class = CourseSerializer
  • urls.py
from django.urls import path
from .views import course_list, course_detail, CourseList, CourseDetail, GCourseList, GCourseDetail

urlpatterns = [
    path('fbv/list/', course_list, name="fbv-list"),
    path('fbv/detail/<int:pk>', course_detail, name="fbv-detail"),
    path('cbv/list/', CourseList.as_view(), name="cbv-list"),
    path('cbv/detail/<int:pk>', CourseDetail.as_view(), name="cbv-detail"),
    path('gcbv/list/', GCourseList.as_view(), name="gcbv-list"),
    path('gcbv/detail/<int:pk>', GCourseDetail.as_view(), name="gcbv-detail"),
]

2-4、DRF的视图集 viewsets

  • DRF 视图集
# from rest_framework import status
# from rest_framework.decorators import api_view
# from rest_framework.response import Response
# from rest_framework.views import APIView
# from rest_framework import generics
from rest_framework import viewsets

from .models import CourseModel

from .serializers import CourseSerializer


"""
DRF DRF的视图集 viewsets
"""


class CourseViewSet(viewsets.ModelViewSet):
    queryset = CourseModel.objects.all()
    serializer_class = CourseSerializer

    def perform_create(self, serializer):
        """
        重写该方法以实现新增时使用当前登录用户
        :param serializer:
        :return:
        """
        serializer.save(teacher=self.request.user)
  • urls.py
from django.urls import path, include
from .views import course_list, course_detail, CourseList, CourseDetail, GCourseList, GCourseDetail, CourseViewSet

# 视图集方式二 start
from rest_framework.routers import DefaultRouter

router = DefaultRouter()
router.register(prefix="viewsets2", viewset=CourseViewSet)
# 视图集方式二 end

urlpatterns = [
    # 函数视图
    path('fbv/list/', course_list, name="fbv-list"),
    path('fbv/detail/<int:pk>', course_detail, name="fbv-detail"),
    # 类视图
    path('cbv/list/', CourseList.as_view(), name="cbv-list"),
    path('cbv/detail/<int:pk>', CourseDetail.as_view(), name="cbv-detail"),
    # 通用类视图
    path('gcbv/list/', GCourseList.as_view(), name="gcbv-list"),
    path('gcbv/detail/<int:pk>', GCourseDetail.as_view(), name="gcbv-detail"),
    # 视图集
    # 视图集方式一
    path('viewsets/', CourseViewSet.as_view({
        "get": "list",
        "post": "create"
    }), name="viewsets-list"),
    path('viewsets/<int:pk>',
         CourseViewSet.as_view({
             "get": "retrieve",
             "put": "update",
             "patch": "partial_update",
             "delete": "destroy"
         }), name="viewsets-detail"),
    # 视图集方式二: v1/viewsets2/
    path("", include(router.urls)),
    # 视图集方式二: v1/viewsets2/
    path("v1/", include(router.urls)),
]

3、DRF 的认证和权限

  • 认证:是指对用户登录的身份进行校验
  • 权限:指的是一个登录验证通过的用户能够访问哪些接口,或者是某一个接口能够拿到什么级别权限的数据
  • DRF 认证和权限的核心是 request.user 和 request.auth

3-1、用户名密码认证 BasicAuthentication

3-2、Session认证 SessionAuthentication

3-3、Token认证 TokenAuthentication

  • 首先需要在 settings.py 配置 authtoken
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'rest_framework.authtoken',  # DRF自带的Token认证
    'course.apps.CourseConfig'
]
# DRF 相关的全局配置
REST_FRAMEWORK = {
    ...
    'DEFAULT_AUTHENTICATION_CLASSES': [  # 认证相关
        # 'rest_framework.authentication.BasicAuthentication',
        # 'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.TokenAuthentication', # token认证时需要加上
    ]
}
  • 生成Token

1、通过Django manage.py生成Token
python manage.py drf_create_token <用户名>
2、通过Django的信号机制生成Token

  • 通过信号机制生成Token
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth.models import User
from django.conf import settings
from rest_framework.authtoken.models import Token


# sender 可以使用 User,也可以使用 settings.AUTH_USER_MODEL
@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def generate_token(sender, instance=None, created=False, **kwargs):
    """
    创建用户时自动生成Token
    :param sender:
    :param instance:
    :param created:
    :param kwargs:
    :return:
    """
    if created:
        Token.objects.create(user=instance)
  • 获取Token 需要在 urls.py 进行配置,然后 POST 访问获取
from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import path, include
# 获取Token的视图
from rest_framework.authtoken import views

urlpatterns = [
    path('api-token-auth/', views.obtain_auth_token),  # 获取Token的接口
    # path('api-auth/', include('rest_framework.urls')),  # DRF的登录和退出
    path('admin/', admin.site.urls),
    path('course/', include('course.urls'))
]
  • 在 settings.py 中配置的 REST_FRAMEWORK 是全局配置,如果对某个接口使用不同的认证方式,可以参考下列案例
# 函数视图
from rest_framework.decorators import api_view, authentication_classes
from rest_framework.authentication import BasicAuthentication,SessionAuthentication,TokenAuthentication

@api_view(['GET', 'POST'])
@authentication_classes((BasicAuthentication, TokenAuthentication)) # 元组里可以填一个或多个,优先级高于全局设置
def course_list(request):
    if request.method == 'GET':
        course_list = CourseSerializer(instance=CourseModel.objects.all(), many=True)
        return Response(data=course_list.data, status=status.HTTP_200_OK)
    elif request.method == 'POST':
        r = CourseSerializer(data=request.data, partial=True)  # partial=True 表示可以部分更新
        if r.is_valid():
            r.save(teacher=request.user)  # 在保存时,可以单独对某个字段传值
            return Response(data=r.data, status=status.HTTP_201_CREATED)
        return Response(r.errors, status=status.HTTP_400_BAD_REQUEST)


# 类视图
class CourseList(APIView):
    # 配置使用的认证方式
    authentication_classes = (TokenAuthentication,)

    def get(self, request):
        """
        查询课程信息
        :param request:
        :return:
        """
        print("here...")
        queryset = CourseModel.objects.all()
        r = CourseSerializer(instance=queryset, many=True)
        return Response(data=r.data, status=status.HTTP_200_OK)

    def post(self, request):
        """
        添加课程信息
        :param request:
        :return:
        """
        r = CourseSerializer(data=request.data)
        if r.is_valid():
            r.save(teacher=self.request.user)
            return Response(data=r.data, status=status.HTTP_201_CREATED)
        return Response(r.errors, status=status.HTTP_400_BAD_REQUEST)

3-4、DRF 权限

  • 首先需要在 settings.py 配置
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'rest_framework.authtoken',  # DRF自带的Token认证
    'course.apps.CourseConfig'
]
  • 全局权限配置
# DRF 相关的全局配置
REST_FRAMEWORK = {
    ...
    'DEFAULT_PERMISSION_CLASSES': [  # 权限相关
        'rest_framework.permissions.IsAuthenticated',  # 只有登录的用户才能执行操作
        'rest_framework.permissions.IsAuthenticatedOrReadOnly', # 登录的用户可以增删改查,不登录的用户可以查询
        'rest_framework.permissions.IsAdminUser', # 如果auth_user表中用户的 is_staff为True,有权限访问
        'rest_framework.permissions.AllowAny', # 默认权限方式,所有用户都可以访问
    ],
    'DEFAULT_AUTHENTICATION_CLASSES': [  # 认证相关
        # 'rest_framework.authentication.BasicAuthentication',
        # 'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.TokenAuthentication', # token认证时需要加上
    ]
}
  • 在接口或对象视图进行权限控制
# 函数视图
from rest_framework.decorators import api_view, authentication_classes, permission_classes
from rest_framework.authentication import BasicAuthentication,SessionAuthentication,TokenAuthentication
from rest_framework.permissions import IsAuthenticated, IsAuthenticatedOrReadOnly, IsAdminUser, AllowAny

@api_view(['GET', 'POST'])
@authentication_classes((BasicAuthentication, TokenAuthentication)) # 元组里可以填一个或多个,优先级高于全局设置
@permission_classes((IsAuthenticated,))
def course_list(request):
    if request.method == 'GET':
        course_list = CourseSerializer(instance=CourseModel.objects.all(), many=True)
        return Response(data=course_list.data, status=status.HTTP_200_OK)
    elif request.method == 'POST':
        r = CourseSerializer(data=request.data, partial=True)  # partial=True 表示可以部分更新
        if r.is_valid():
            r.save(teacher=request.user)  # 在保存时,可以单独对某个字段传值
            return Response(data=r.data, status=status.HTTP_201_CREATED)
        return Response(r.errors, status=status.HTTP_400_BAD_REQUEST)


# 类视图
class CourseList(APIView):
    # 配置使用的认证方式
    authentication_classes = (TokenAuthentication,)
    permission_classes = (IsAuthenticated,)

    def get(self, request):
        """
        查询课程信息
        :param request:
        :return:
        """
        print("here...")
        queryset = CourseModel.objects.all()
        r = CourseSerializer(instance=queryset, many=True)
        return Response(data=r.data, status=status.HTTP_200_OK)

    def post(self, request):
        """
        添加课程信息
        :param request:
        :return:
        """
        r = CourseSerializer(data=request.data)
        if r.is_valid():
            r.save(teacher=self.request.user)
            return Response(data=r.data, status=status.HTTP_201_CREATED)
        return Response(r.errors, status=status.HTTP_400_BAD_REQUEST)
  • 自定义控制权限
from rest_framework import permissions


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

    def has_object_permission(self, request, view, obj):
        print("permission...", request.user, obj.teacher, request.method)
        """
        所有的request请求都有读权限,因此允许GET/HEAD/OPTIONS方法
        :param request:
        :param view:
        :param obj:
        :return:
        """
        if request.method in permissions.SAFE_METHODS:
            return True
        # 对象的所有者才有写权限
        return request.user == obj.teacher
from .permissions import IsOwnerReadOnly


class CourseDetail(APIView):
    # 使用自定义权限
    permission_classes = (IsOwnerReadOnly,)

    @staticmethod
    def get_course_instance(pk):
        """
        根据id查询课程
        :param pk:课程ID
        :return:
        """
        try:
            return CourseModel.objects.get(pk=pk)
        except Exception as e:
            return

    def get(self, request, pk):
        """
        获取课程信息
        :param request:
        :param pk:
        :return:
        """
        course = self.get_course_instance(pk=pk)
        if not course:
            return Response(data={'msg:'"没有此课程信息"}, status=status.HTTP_404_NOT_FOUND)
        r = CourseSerializer(instance=course)
        return Response(data=r.data, status=status.HTTP_200_OK)

    def put(self, request, pk):
        """
        更新课程信息
        :param request:
        :param pk:
        :return:
        """

        course = self.get_course_instance(pk=pk)
        if not course:
            return Response(data={'msg:'"没有此课程信息"}, status=status.HTTP_404_NOT_FOUND)
        r = CourseSerializer(instance=course, data=request.data)
        if r.is_valid():
            r.save()
            return Response(data=r.data, status=status.HTTP_201_CREATED)
        return Response(r.errors, status=status.HTTP_400_BAD_REQUEST)

    def delete(self, request, pk):
        """
        删除课程
        :param request:
        :param pk:
        :return:
        """
        r = self.get_course_instance(pk=pk)
        if not r:
            return Response(data={'msg:'"没有此课程信息"}, status=status.HTTP_404_NOT_FOUND)
        r.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

4、生成API接口文档

4-1 方式一:使用 coreapi (推荐)

# DRF 相关的全局配置
REST_FRAMEWORK = {
    'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',  # 自动生成接口文档
    ...
}
  • 在 urls.py 配置
from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import path, include
from rest_framework.authtoken import views

# 生成API接口文档
from rest_framework.documentation import include_docs_urls

urlpatterns = [
    path('api-token-auth/', views.obtain_auth_token),  # 获取Token的接口
    # path('api-auth/', include('rest_framework.urls')),  # DRF的登录和退出
    path('admin/', admin.site.urls),
    path('course/', include('course.urls')),
    path('docs/', include_docs_urls('course.urls')),
]
# 开发环境:将 STATIC_URL 映射到 STATIC_ROOT 或 STATICFILES_DIRS
if settings.DEBUG:
    urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

4-2 方式二:使用 openapi

  • 在 settings.py 中配置 REST_FRAMEWORK
# DRF 相关的全局配置
REST_FRAMEWORK = {
    'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.openapi.AutoSchema',  # 自动生成接口文档
    ...
}
  • 在 urls.py 配置
from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import path, include
from rest_framework.authtoken import views

# 文档相关
from rest_framework import schemas
schema_view = schemas.get_schema_view(title='APISchema', description="描述信息")

urlpatterns = [
    path('api-token-auth/', views.obtain_auth_token),  # 获取Token的接口
    # path('api-auth/', include('rest_framework.urls')),  # DRF的登录和退出
    path('admin/', admin.site.urls),
    path('course/', include('course.urls')),
    path('docs/', schema_view),
]
# 开发环境:将 STATIC_URL 映射到 STATIC_ROOT 或 STATICFILES_DIRS
if settings.DEBUG:
    urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)