跳至内容

综合实践

本文通过一个省份信息管理系统综合运用前面所学的 DRF 知识,涵盖模型设计、序列化器、视图集、路由、跨域(CORS)配置等完整链路。文末附有思考题,供进一步练习。

项目目标

实现一个省份信息的 REST API,支持:

  • 列表查询、单条查询、新增、修改、删除(标准 CRUD)
  • 按 GDP 排序、按名称搜索
  • 分页返回
  • 前端(Vue + axios)跨域访问

示例数据结构:

id省份面积(万 km²)人口(亿)GDP(万亿)
1广东17.981.129.73
2江苏10.260.809.26
3山东15.701.007.65

后端实现

模型定义

# provinces/models.py
from django.db import models


class Province(models.Model):
    name       = models.CharField(max_length=50, verbose_name="省份名称", unique=True)
    area       = models.FloatField(verbose_name="占地面积(万 km²)")
    population = models.FloatField(verbose_name="人口(亿)")
    gdp        = models.FloatField(verbose_name="GDP(万亿)")

    class Meta:
        db_table      = "tb_province"
        verbose_name  = "省份"
        ordering      = ["-gdp"]   # 默认按 GDP 降序

序列化器

# provinces/serializers.py
from rest_framework import serializers
from .models import Province


class ProvinceSerializer(serializers.ModelSerializer):
    class Meta:
        model  = Province
        fields = "__all__"
        extra_kwargs = {
            "id": {"read_only": True},
        }

视图集

# provinces/views.py
from rest_framework.viewsets import ModelViewSet
from rest_framework.filters import SearchFilter, OrderingFilter
from rest_framework.pagination import PageNumberPagination
from .models import Province
from .serializers import ProvinceSerializer


class StandardPagination(PageNumberPagination):
    page_size             = 10
    page_size_query_param = "size"
    max_page_size         = 100


class ProvinceViewSet(ModelViewSet):
    queryset         = Province.objects.all()
    serializer_class = ProvinceSerializer
    pagination_class = StandardPagination
    filter_backends  = [SearchFilter, OrderingFilter]
    search_fields    = ["name"]
    ordering_fields  = ["id", "gdp", "population", "area"]
    ordering         = ["-gdp"]    # 默认 GDP 降序

路由配置

# provinces/urls.py
from rest_framework.routers import DefaultRouter
from . import views

router = DefaultRouter()
router.register("provinces", views.ProvinceViewSet)

urlpatterns = router.urls
# 项目总路由 urls.py
from django.urls import path, include

urlpatterns = [
    path("api/", include("provinces.urls")),
]

跨域(CORS)配置

前后端分离时,浏览器会阻止跨域请求。DRF 推荐使用 django-cors-headers 解决:

pip install django-cors-headers
# settings.py
INSTALLED_APPS = [
    ...
    'corsheaders',
]

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',   # 必须放在 CommonMiddleware 之前
    'django.middleware.common.CommonMiddleware',
    ...
]

# 允许特定来源(生产环境替换为实际域名)
CORS_ALLOWED_ORIGINS = [
    "http://localhost:5173",    # Vite 开发服务器
    "http://localhost:3000",    # Create React App 开发服务器
]

# 或者开发阶段直接放开所有(生产禁用)
# CORS_ALLOW_ALL_ORIGINS = True

也可以通过自定义响应头手动实现 CORS(不依赖第三方包):

class CORSMixin:
    """在视图类中混入,为响应添加 CORS 头"""
    def finalize_response(self, request, response, *args, **kwargs):
        response = super().finalize_response(request, response, *args, **kwargs)
        response["Access-Control-Allow-Origin"]  = "*"
        response["Access-Control-Allow-Methods"] = "GET, POST, PUT, PATCH, DELETE, OPTIONS"
        response["Access-Control-Allow-Headers"] = "Content-Type, Authorization"
        return response

前端调用示例(Vue + axios)

安装依赖:

npm create vite@latest frontend -- --template vue
cd frontend
npm install axios

封装请求:

// src/api/provinces.js
import axios from 'axios'

const api = axios.create({
  baseURL: 'http://127.0.0.1:8000/api/',
  timeout: 5000,
})

export const getProvinces = (params) => api.get('provinces/', { params })
export const createProvince = (data) => api.post('provinces/', data)
export const updateProvince = (id, data) => api.put(`provinces/${id}/`, data)
export const deleteProvince = (id) => api.delete(`provinces/${id}/`)

在组件中使用:

// src/components/ProvinceTable.vue
<script setup>
import { ref, onMounted } from 'vue'
import { getProvinces, deleteProvince } from '../api/provinces'

const provinces = ref([])
const total = ref(0)
const page = ref(1)

async function load() {
  const res = await getProvinces({ page: page.value, size: 10 })
  provinces.value = res.data.results
  total.value = res.data.count
}

async function remove(id) {
  await deleteProvince(id)
  load()
}

onMounted(load)
</script>

思考题

思考题 1:基础 CRUD 实现

参考本文后端示例,完成以下任务:

  1. 创建 Django 项目并添加省份应用。
  2. 根据示例数据向数据库插入初始记录(可用 python manage.py shell 或 fixtures)。
  3. 启动 DRF 服务,通过 DRF 可视化页面(Browsable API)依次测试:
    • GET /api/provinces/ — 查看列表
    • POST /api/provinces/ — 新增一条(如"北京")
    • GET /api/provinces/1/ — 查看单条
    • PATCH /api/provinces/1/ — 修改 GDP
    • DELETE /api/provinces/1/ — 删除
思考题 2:前端 Vue 集成与跨域

在本文后端基础上:

  1. 创建 Vue 项目,使用 axios 调用省份列表接口,将数据以表格形式展示。
  2. 为表格添加新增、编辑、删除按钮,实现完整的前端 CRUD 交互。
  3. 配置 CORS,确保前端(localhost:5173)能够正常访问后端(localhost:8000)。

提示:编辑/删除操作完成后记得重新调用列表接口刷新数据。

思考题 3:GDP 排序与搜索
  1. 在前端表格的"GDP"列表头上添加点击事件,实现升序/降序切换(将 ordering 参数附加在请求 URL 中)。
  2. 添加搜索框,支持按省份名称模糊搜索(将 search 参数附加在请求 URL 中)。
  3. 搜索和排序同时生效时,参数如何组合传递?
思考题 4:认证保护写操作

要求未登录用户只能查看(GET),已登录用户才能新增/修改/删除。

  1. 配置 rest_framework.authtoken 并为测试用户生成 Token。
  2. ProvinceViewSet 上添加权限:IsAuthenticatedOrReadOnly
  3. 用 Postman 测试:不带 Token 的 GET 能否成功?不带 Token 的 POST 是否返回 401?携带正确 Token 的 POST 是否成功?
最后更新于