- 在前一篇文章中,我们已经掌握了 Django5 表单的构建逻辑(Form/ModelForm)和前后端数据传递方案(axios 发送 + Django 接收)。但完整的前后端交互链路,还需要"数据库操作"和"前端数据渲染"两个关键环节------Django 需要将接收的前端数据存入数据库,并在前端需要时查询数据并以 JSON 格式响应;Vue3 则需要接收响应数据,渲染到页面上,形成"输入-处理-存储-展示"的闭环。本文作为系列第二篇,将聚焦这两大环节,并通过"产品管理系统"综合案例,完整演示前后端协同的实现流程。
一、Django5 操作数据库并响应数据:完成后端数据流转
Django 与数据库的交互依赖于 ORM(对象关系映射)机制 ------它允许开发者用 Python 代码替代 SQL 语句,轻松实现数据的查询、新增、更新、删除,且无需关注底层数据库类型(如 MySQL、PostgreSQL)的差异。同时,Django 提供 JsonResponse
类,可将查询结果转为 JSON 格式,供 Vue3 接收和渲染。
1.1 Django ORM:简化数据库查询操作
ORM 的核心价值是"屏蔽数据库差异,降低操作复杂度"。针对表单交互场景,最常用的是"数据查询"操作------需根据前端需求,从数据库中筛选、排序、分页数据,再返回给前端。以下是几种高频查询场景及实现方式。
(1)基础查询:获取全部/单条/条件数据
基础查询是最常用的场景,涵盖"获取所有数据""按条件筛选""获取单条数据"等,通过 ORM 提供的 all()
、filter()
、get()
等方法即可实现。
示例:文章模型基础查询
python
# views.py
from django.utils import timezone
from datetime import timedelta
from .models import Article
# 1. 获取所有数据(返回查询集,支持链式调用)
all_articles = Article.objects.all()
# 2. 条件查询:筛选符合条件的数据(返回查询集)
# 场景1:获取标题包含"Django"的文章(模糊查询,用__contains)
django_articles = Article.objects.filter(title__contains='Django')
# 场景2:获取近7天发布的文章(时间条件,用__gte表示"大于等于")
recent_7_days = timezone.now() - timedelta(days=7)
recent_articles = Article.objects.filter(pub_date__gte=recent_7_days)
# 场景3:获取标题不为空且内容长度大于100的文章(多条件组合)
valid_articles = Article.objects.filter(
title__isnull=False, # 标题不为空
content__length__gt=100 # 内容长度大于100(__gt表示"大于")
)
# 3. 获取单条数据(返回模型实例,若不存在则抛出DoesNotExist异常)
# 场景:根据文章ID获取详情(常用于"编辑/删除"功能)
try:
article = Article.objects.get(id=1) # id=1的文章
except Article.DoesNotExist:
# 处理"数据不存在"的情况(如返回错误提示)
print("该文章不存在")
(2)高级查询:排序与分页
当数据量较大时,需对查询结果进行"排序"和"分页",提升前端渲染效率和用户体验。ORM 提供 order_by()
方法实现排序,Django 内置的 Paginator
类实现分页。
示例:排序与分页查询
python
# views.py
from django.core.paginator import Paginator
from .models import Article
# 1. 排序查询:按指定字段排序(正序/倒序)
# 场景1:按发布时间正序(从旧到新):order_by('pub_date')
# 场景2:按发布时间倒序(从新到旧):字段前加负号(-)
ordered_articles = Article.objects.all().order_by('-pub_date')
# 场景3:多字段排序:先按发布时间倒序,再按标题正序
multi_order_articles = Article.objects.all().order_by('-pub_date', 'title')
# 2. 分页查询:拆分数据为多页,减少单次数据传输量
# 步骤1:创建分页器实例(参数1:查询集,参数2:每页数据量)
paginator = Paginator(ordered_articles, 10) # 每页10条文章
# 步骤2:获取指定页码的数据(如第1页、第2页)
page_number = 1 # 前端可通过URL参数传递页码(如?page=1)
page_obj = paginator.page(page_number)
# 步骤3:提取当前页的具体数据(供后续响应给前端)
current_page_articles = page_obj.object_list # 当前页的文章列表
# 步骤4:分页辅助信息(返回给前端,用于渲染分页控件)
pagination_info = {
'total_count': paginator.count, # 总数据量
'total_pages': paginator.num_pages, # 总页数
'current_page': page_number, # 当前页码
'has_next': page_obj.has_next(), # 是否有下一页
'has_previous': page_obj.has_previous() # 是否有上一页
}
(3)字段筛选:仅查询所需字段
默认情况下,Article.objects.all()
会查询模型的所有字段(如 id
、title
、content
、pub_date
),但前端可能只需部分字段(如列表页只需 id
、title
、pub_date
)。此时可通过 values()
方法筛选字段,减少数据传输量。
示例:字段筛选查询
python
# 仅查询id、title、pub_date三个字段(返回字典列表)
articles_simple = Article.objects.all().values('id', 'title', 'pub_date')
# 若需排序+字段筛选:链式调用即可
articles_sorted_simple = Article.objects.all()\
.order_by('-pub_date')\
.values('id', 'title', 'pub_date')
1.2 Django 以 JSON 格式响应数据:适配前端渲染需求
查询到数据后,Django 需要将其转为 JSON 格式返回给 Vue3。核心工具是 JsonResponse
类,但需注意:Django 的查询集(如 Article.objects.all()
)是"可迭代对象",无法直接被 JSON 序列化,需先通过 list()
方法转为列表;若包含 Decimal
(如价格)、datetime
(如时间)等特殊类型,需先转为字符串或浮点数,避免序列化错误。
(1)基础 JSON 响应:自定义视图实现
适用于简单场景,手动处理数据序列化和响应格式。
示例:返回文章列表 JSON 数据
python
# views.py
from django.http import JsonResponse
from .models import Article
def get_articles_api(request):
# 1. 查询数据(字段筛选+排序)
articles = Article.objects.all()\
.order_by('-pub_date')\
.values('id', 'title', 'pub_date') # 仅查询所需字段
# 2. 处理特殊类型:将datetime转为字符串(便于JSON序列化)
# 方法:遍历查询结果,格式化pub_date
articles_list = []
for item in articles:
# 将datetime对象转为"YYYY-MM-DD HH:MM:SS"格式字符串
item['pub_date'] = item['pub_date'].strftime('%Y-%m-%d %H:%M:%S')
articles_list.append(item)
# 3. 返回JSON响应(包含状态、消息、数据)
return JsonResponse({
'status': 'success', # 状态:success/error
'message': '文章列表获取成功', # 提示消息
'data': articles_list, # 核心数据(文章列表)
'count': len(articles_list) # 数据总量(辅助前端显示)
})
(2)带分页的 JSON 响应:适配大量数据场景
当数据量较大时,需在响应中包含分页信息,供前端渲染分页控件(如下一页、上一页按钮)。
示例:返回带分页的文章列表
python
# views.py
from django.http import JsonResponse
from django.core.paginator import Paginator
from .models import Article
def get_articles_with_pagination_api(request):
# 1. 获取前端传递的页码(默认第1页)
page_number = request.GET.get('page', 1) # 从URL参数?page=获取
try:
page_number = int(page_number) # 转为整数(避免非数字参数)
except ValueError:
page_number = 1 # 若参数非法,默认第1页
# 2. 查询并分页数据
articles_queryset = Article.objects.all()\
.order_by('-pub_date')\
.values('id', 'title', 'pub_date')
paginator = Paginator(articles_queryset, 10) # 每页10条
page_obj = paginator.get_page(page_number) # 自动处理"页码超出范围"(返回最后一页)
# 3. 处理数据格式(格式化时间+提取当前页数据)
current_page_articles = []
for item in page_obj.object_list:
item['pub_date'] = item['pub_date'].strftime('%Y-%m-%d %H:%M:%S')
current_page_articles.append(item)
# 4. 构造分页信息
pagination = {
'total_count': paginator.count,
'total_pages': paginator.num_pages,
'current_page': page_number,
'has_next': page_obj.has_next(),
'has_previous': page_obj.has_previous(),
'next_page': page_obj.next_page_number() if page_obj.has_next() else None,
'previous_page': page_obj.previous_page_number() if page_obj.has_previous() else None
}
# 5. 返回JSON响应(包含数据和分页信息)
return JsonResponse({
'status': 'success',
'message': '带分页的文章列表获取成功',
'data': current_page_articles,
'pagination': pagination # 分页信息
})
(3)DRF 自动 JSON 响应:复杂场景优选
若使用 Django REST framework(DRF),无需手动处理数据序列化和分页------DRF 会自动将查询集转为 JSON 格式,并默认支持分页,大幅减少代码量。
示例:DRF 实现文章列表响应
python
# 1. 序列化器(复用前一篇的ArticleSerializer)
# serializers.py
from rest_framework import serializers
from .models import Article
class ArticleSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = ['id', 'title', 'content', 'pub_date']
read_only_fields = ['pub_date']
# 2. 视图:使用DRF的ListAPIView(专门用于返回列表数据)
# views.py
from rest_framework import generics
from .models import Article
from .serializers import ArticleSerializer
class ArticleListView(generics.ListAPIView):
# 数据源:按发布时间倒序
queryset = Article.objects.all().order_by('-pub_date')
# 关联的序列化器(自动处理数据转换)
serializer_class = ArticleSerializer
# 配置分页(每页10条)
pagination_class = generics.pagination.PageNumberPagination
pagination_class.page_size = 10 # 每页数据量
# 3. URL配置(urls.py)
from django.urls import path
from .views import ArticleListView
urlpatterns = [
# 文章列表接口:GET /api/articles/
path('api/articles/', ArticleListView.as_view(), name='article-list'),
]
DRF 自动返回的 JSON 格式
访问 GET /api/articles/
,DRF 会自动返回包含分页信息的 JSON 数据,格式如下:
json
{
"count": 120, // 总数据量
"next": "http://example.com/api/articles/?page=2", // 下一页URL
"previous": null, // 上一页URL(第一页为null)
"results": [ // 当前页数据列表
{
"id": 120,
"title": "Django5 ORM 高级用法",
"content": "本文讲解Django5 ORM的复杂查询...",
"pub_date": "2024-10-01T14:30:00Z" // 时间自动转为ISO格式
},
// ... 其他9条文章数据
]
}
Vue3 可直接使用 response.data.results
获取当前页数据,response.data.next
/response.data.previous
处理分页跳转,无需手动解析分页逻辑。
1.3 Vue3 接收并渲染数据:完成前端展示闭环
Vue3 接收 Django 响应的 JSON 数据后,需通过"响应式数据存储-条件渲染-列表渲染"的流程,将数据展示到页面上。同时,为提升用户体验,需添加"加载状态"(避免用户等待时无反馈)和"错误提示"(处理请求失败场景)。
(1)基础数据渲染:文章列表展示
核心逻辑是:在组件挂载时(onMounted
)调用 axios 请求 Django 接口,将返回的数据存入响应式变量(articles
),再通过 v-for
循环渲染列表。
示例:Vue3 文章列表渲染
vue
<template>
<div class="articles-container">
<h2>文章列表</h2>
<!-- 1. 加载状态:请求未完成时显示 -->
<div class="loading" v-if="loading">
<span>加载中...</span>
</div>
<!-- 2. 错误提示:请求失败时显示 -->
<div class="error" v-else-if="error">
<span>❌ {{ error }}</span>
</div>
<!-- 3. 数据渲染:请求成功且有数据时显示列表 -->
<div class="articles-list" v-else-if="articles.length > 0">
<div class="article-item" v-for="article in articles" :key="article.id">
<h3 class="article-title">{{ article.title }}</h3>
<div class="article-meta">
<span>发布时间:{{ formatDate(article.pub_date) }}</span>
</div>
<!-- 可选:添加"查看详情"按钮,跳转至文章详情页 -->
<button @click="goToDetail(article.id)" class="btn-detail">
查看详情
</button>
</div>
</div>
<!-- 4. 空数据提示:请求成功但无数据时显示 -->
<div class="empty" v-else>
<span>暂无文章数据,请先发布文章</span>
</div>
</div>
</template>
<script setup>
// 1. 导入所需工具
import { ref, onMounted } from 'vue';
import axios from 'axios';
import { useRouter } from 'vue-router'; // 用于页面跳转(查看详情)
// 2. 初始化响应式数据
const articles = ref([]); // 存储文章列表数据
const loading = ref(false); // 存储加载状态(true:加载中,false:加载完成)
const error = ref(''); // 存储错误信息(请求失败时赋值)
const router = useRouter(); // 初始化路由实例
// 3. 定义"获取文章列表"的异步函数
const fetchArticles = async () => {
loading.value = true; // 开始加载:显示加载状态
error.value = ''; // 清空之前的错误信息
try {
// 发送GET请求到Django接口(若有分页,可添加?page=1参数)
const response = await axios.get('/api/articles/');
// 处理响应数据:
// - 若为DRF接口:数据在response.data.results中
// - 若为自定义视图:数据在response.data.data中
const articleData = response.data.results || response.data.data;
articles.value = articleData; // 将数据存入响应式变量
} catch (err) {
// 请求失败:捕获错误并赋值给error
error.value = '获取文章列表失败,请刷新页面重试';
console.error('请求错误详情:', err); // 控制台打印错误,便于调试
} finally {
// 无论成功/失败,都结束加载:隐藏加载状态
loading.value = false;
}
};
// 4. 定义"时间格式化"函数(处理ISO格式时间)
const formatDate = (dateString) => {
// 若为DRF返回的ISO时间(如2024-10-01T14:30:00Z),需转为本地时间
const date = new Date(dateString);
// 格式化为"YYYY-MM-DD HH:MM"(适配中文环境)
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
});
};
// 5. 定义"跳转至文章详情"函数
const goToDetail = (articleId) => {
// 跳转到详情页(路由需提前定义,如/articles/:id)
router.push(`/articles/${articleId}`);
};
// 6. 组件挂载时自动调用"获取文章列表"函数
onMounted(() => {
fetchArticles();
});
</script>
<style scoped>
/* 样式优化:提升页面美观度和用户体验 */
.articles-container { max-width: 1200px; margin: 20px auto; padding: 0 20px; }
.loading { padding: 50px; text-align: center; color: #666; }
.error { padding: 50px; text-align: center; color: #dc3545; }
.empty { padding: 50px; text-align: center; color: #666; }
.articles-list { margin-top: 20px; }
.article-item { padding: 20px; border: 1px solid #eee; border-radius: 8px; margin-bottom: 15px; }
.article-title { margin: 0 0 10px; color: #007bff; cursor: pointer; }
.article-title:hover { text-decoration: underline; }
.article-meta { color: #666; font-size: 14px; margin-bottom: 15px; }
.btn-detail { padding: 8px 16px; background: #007bff; color: #fff; border: none; border-radius: 4px; cursor: pointer; }
.btn-detail:hover { background: #0056b3; }
</style>
(2)带分页的 data 渲染:适配大量数据
若 Django 接口返回分页信息,Vue3 需在页面上渲染分页控件(如页码按钮、上一页/下一页按钮),允许用户切换页码。
示例:Vue3 带分页的文章列表
vue
<template>
<div class="articles-container">
<!-- 文章列表渲染(同基础版,省略重复代码) -->
<div class="articles-list" v-else-if="articles.length > 0">
<!-- ... 文章列表项 ... -->
</div>
<!-- 分页控件:请求成功且有数据时显示 -->
<div class="pagination" v-if="pagination && articles.length > 0">
<!-- 上一页按钮:若有上一页则启用,否则禁用 -->
<button
@click="changePage(pagination.current_page - 1)"
:disabled="!pagination.has_previous"
class="page-btn"
>
上一页
</button>
<!-- 页码按钮:仅显示当前页前后2页(避免页码过多) -->
<button
v-for="page in visiblePages"
:key="page"
@click="changePage(page)"
class="page-btn"
:class="{ active: page === pagination.current_page }"
>
{{ page }}
</button>
<!-- 下一页按钮:若有下一页则启用,否则禁用 -->
<button
@click="changePage(pagination.current_page + 1)"
:disabled="!pagination.has_next"
class="page-btn"
>
下一页
</button>
<!-- 分页信息:显示当前页/总页数 -->
<span class="page-info">
{{ pagination.current_page }} / {{ pagination.total_pages }} 页(共 {{ pagination.total_count }} 条)
</span>
</div>
</div>
</template>
<script setup>
// 1. 新增响应式变量:存储分页信息
const pagination = ref(null); // 存储分页信息(如total_count、current_page)
// 2. 修改fetchArticles函数:接收并存储分页信息
const fetchArticles = async (page = 1) => { // 新增page参数,默认第1页
loading.value = true;
error.value = '';
try {
// 发送请求时携带页码参数(?page=page)
const response = await axios.get(`/api/articles/?page=${page}`);
// 存储文章数据(同基础版)
articles.value = response.data.results || response.data.data;
// 存储分页信息:
// - 若为DRF接口:分页信息在response.data中(count、next、previous)
// - 若为自定义视图:分页信息在response.data.pagination中
if (response.data.results) {
// DRF分页信息适配
pagination.value = {
total_count: response.data.count,
total_pages: Math.ceil(response.data.count / 10), // 每页10条,计算总页数
current_page: page,
has_next: !!response.data.next, // next不为null则有下一页
has_previous: !!response.data.previous // previous不为null则有上一页
};
} else {
// 自定义视图分页信息适配
pagination.value = response.data.pagination;
}
} catch (err) {
error.value = '获取文章列表失败,请刷新页面重试';
console.error(err);
} finally {
loading.value = false;
}
};
// 3. 定义"切换页码"函数
const changePage = (targetPage) => {
// 边界校验:避免页码小于1或大于总页数
if (targetPage < 1 || targetPage > pagination.value.total_pages) {
return;
}
// 重新请求目标页码的数据
fetchArticles(targetPage);
};
// 4. 定义"计算可见页码"函数(避免页码过多,仅显示当前页前后2页)
const visiblePages = ref([]);
// 监听pagination变化,动态计算可见页码
watch(pagination, (newPagination) => {
if (!newPagination) return;
const { current_page, total_pages } = newPagination;
const pages = [];
// 计算起始页码(当前页-2,最小为1)
const startPage = Math.max(1, current_page - 2);
// 计算结束页码(当前页+2,最大为总页数)
const endPage = Math.min(total_pages, current_page + 2);
// 生成可见页码列表
for (let i = startPage; i <= endPage; i++) {
pages.push(i);
}
visiblePages.value = pages;
}, { immediate: true }); // immediate: true:初始时立即执行
// 5. 组件挂载时调用:默认获取第1页数据
onMounted(() => {
fetchArticles();
});
</script>
<style scoped>
/* 分页控件样式 */
.pagination { display: flex; align-items: center; gap: 8px; margin-top: 20px; justify-content: center; }
.page-btn { padding: 6px 12px; border: 1px solid #ddd; border-radius: 4px; background: #fff; cursor: pointer; }
.page-btn:disabled { background: #f5f5f5; color: #666; cursor: not-allowed; }
.page-btn.active { background: #007bff; color: #fff; border-color: #007bff; }
.page-info { margin-left: 15px; color: #666; }
</style>
二、综合案例:产品管理系统(前后端完整实现)
为了将前面所学的知识点串联起来,我们以"产品管理系统"为例,实现一个完整的前后端交互功能:Vue3 前端提供"添加产品"表单和"产品列表"展示;Django5 后端接收前端数据,存储到 MySQL 数据库,并在前端需要时查询数据并响应。
2.1 项目前提准备
(1)Django 项目配置(MySQL)
首先确保 Django 已配置 MySQL 数据库(若未配置,需修改 settings.py
):
python
# settings.py
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql', # 数据库引擎
'NAME': 'product_db', # 数据库名称(需提前在MySQL中创建)
'USER': 'root', # MySQL用户名
'PASSWORD': 'your_password', # MySQL密码
'HOST': 'localhost', # 数据库主机(本地为localhost)
'PORT': '3306', # 数据库端口(默认3306)
'OPTIONS': {
'charset': 'utf8mb4' # 字符集(支持emoji等特殊字符)
}
}
}
(2)Vue3 项目准备
确保 Vue3 项目已安装 axios(用于 HTTP 请求)和 vue-router(用于页面跳转):
bash
# 安装axios
npm install axios
# 安装vue-router(若需页面跳转)
npm install vue-router@4
2.2 Django 后端实现(产品管理接口)
(1)定义产品模型(models.py)
python
# models.py
from django.db import models
class Product(models.Model):
"""产品模型:存储产品名称、价格、描述、创建时间"""
name = models.CharField(max_length=200, verbose_name='产品名称') # 产品名称(最大200字符)
price = models.DecimalField(
max_digits=10,
decimal_places=2,
verbose_name='产品价格'
) # 产品价格(最大10位数字,保留2位小数)
description = models.TextField(blank=True, null=True, verbose_name='产品描述') # 产品描述(可选)
created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') # 自动填充创建时间
class Meta:
verbose_name = '产品' # 后台管理显示的单数名称
verbose_name_plural = '产品' # 后台管理显示的复数名称
ordering = ['-created_at'] # 默认排序:按创建时间倒序
def __str__(self):
return self.name # 后台管理中显示产品名称
定义完成后,执行数据库迁移命令,创建 product
表:
bash
# 生成迁移文件
python manage.py makemigrations
# 执行迁移(创建表)
python manage.py migrate
(3)定义视图函数(views.py)
实现两个核心接口:create_product
(添加产品,POST 请求)和 get_products
(获取产品列表,GET 请求,支持分页)。
python
# views.py
import json
from django.http import JsonResponse
from django.core.paginator import Paginator
from .models import Product
def create_product(request):
"""添加产品接口:接收Vue3发送的JSON数据,存储到数据库"""
if request.method == 'POST':
try:
# 1. 解析JSON数据
data = json.loads(request.body.decode('utf-8'))
# 2. 提取并校验字段
name = data.get('name', '').strip()
price = data.get('price', 0)
description = data.get('description', '').strip()
# 校验逻辑:名称非空,价格为正数
errors = {}
if not name:
errors['name'] = '产品名称不能为空'
if not isinstance(price, (int, float)) or price <= 0:
errors['price'] = '产品价格必须为正数'
# 若有错误,返回错误信息
if errors:
return JsonResponse({
'status': 'error',
'message': '数据校验失败',
'errors': errors
}, status=400)
# 3. 存储数据到数据库(DecimalField需转为float)
product = Product.objects.create(
name=name,
price=float(price),
description=description
)
# 4. 返回成功响应
return JsonResponse({
'status': 'success',
'message': '产品添加成功',
'data': {
'id': product.id,
'name': product.name,
'price': float(product.price), # Decimal转float,避免JSON序列化错误
'created_at': product.created_at.strftime('%Y-%m-%d %H:%M:%S')
}
})
# 处理JSON格式错误
except json.JSONDecodeError:
return JsonResponse({
'status': 'error',
'message': '数据格式错误,请发送合法的JSON'
}, status=400)
# 非POST请求响应
return JsonResponse({
'status': 'error',
'message': '仅支持POST请求'
}, status=405)
def get_products(request):
"""获取产品列表接口:支持分页,返回JSON数据"""
if request.method == 'GET':
# 1. 获取页码(默认第1页)
page_number = request.GET.get('page', 1)
try:
page_number = int(page_number)
except ValueError:
page_number = 1
# 2. 查询产品数据(字段筛选:仅返回所需字段)
products_queryset = Product.objects.all().values(
'id', 'name', 'price', 'created_at'
)
# 3. 分页处理(每页8条数据)
paginator = Paginator(products_queryset, 8)
page_obj = paginator.get_page(page_number)
# 4. 处理数据格式(格式化时间+Decimal转float)
products_list = []
for item in page_obj.object_list:
products_list.append({
'id': item['id'],
'name': item['name'],
'price': float(item['price']), # Decimal转float
'created_at': item['created_at'].strftime('%Y-%m-%d %H:%M:%S')
})
# 5. 构造分页信息
pagination_info = {
'total_count': paginator.count,
'total_pages': paginator.num_pages,
'current_page': page_number,
'has_next': page_obj.has_next(),
'has_previous': page_obj.has_previous(),
'next_page': page_obj.next_page_number() if page_obj.has_next() else None,
'previous_page': page_obj.previous_page_number() if page_obj.has_previous() else None
}
# 6. 返回JSON响应
return JsonResponse({
'status': 'success',
'message': '产品列表获取成功',
'data': products_list,
'pagination': pagination_info
})
# 非GET请求响应
return JsonResponse({
'status': 'error',
'message': '仅支持GET请求'
}, status=405)
(4)配置 URL 路由(urls.py)
将视图函数映射到具体的 URL 路径:
python
# 项目urls.py(或应用urls.py)
from django.urls import path
from .views import create_product, get_products
urlpatterns = [
# 添加产品接口:POST /api/products/
path('api/products/', create_product, name='create-product'),
# 获取产品列表接口:GET /api/products/list/
path('api/products/list/', get_products, name='get-products'),
]