django与vue3的对接流程详解(下)

  • 在前一篇文章中,我们已经掌握了 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() 会查询模型的所有字段(如 idtitlecontentpub_date),但前端可能只需部分字段(如列表页只需 idtitlepub_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'),
]
相关推荐
nightunderblackcat8 小时前
四大名著智能可视化推演平台
前端·网络·爬虫·python·状态模式
Cikiss8 小时前
图解 bulkProcessor(调度器 + bulkAsync() + Semaphore)
java·分布式·后端·elasticsearch·搜索引擎
Mintopia8 小时前
Next.js 与 Serverless 架构思维:无状态的优雅与冷启动的温柔
前端·后端·全栈
小蕾Java8 小时前
PyCharm入门级详细使用手册(Python新手快速上手篇)
ide·python·pycharm
动能小子ohhh8 小时前
AI智能体(Agent)大模型入门【9】--如何在pycharm等其他编译软件调用ocr工具【只写后端代码不演示】
人工智能·python·深度学习·机器学习·pycharm·ocr
mudtools8 小时前
.NET驾驭Word之力:基于规则自动生成及排版Word文档
后端·.net
王中阳Go8 小时前
面试官:“聊聊最复杂的项目?”90%的人开口就凉!我面过最牛的回答,就三句话
java·后端·面试
廖广杰8 小时前
java虚拟机-虚拟机栈OOM(StackOverflowError/OutOfMemoryError)
后端
MOON404☾8 小时前
Rust 与 传统语言:现代系统编程的深度对比
开发语言·后端·python·rust