You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何编写DRF Serializer输出适配react-table的扁平化序列化数据

纠正后的问题标题:Django Rest Framework序列化打平一对多关系 + React Table渲染带分组表头表格方案

问题1:如何编写Serializer直接输出符合要求格式的数据?

你期望的打平格式本质是把Item和关联的PZItem做左外连接合并,没有关联PZItem的Item也保留一条记录,不需要用嵌套Serializer实现,直接在视图层构造查询集即可,性能远高于你当前用SerializerMethodField实现的N+1查询方案:

from itertools import chain
from rest_framework.response import Response
from rest_framework.views import APIView

class FlatItemListView(APIView):
    def get(self, request, *args, **kwargs):
        # 第一步:查询所有有有效库存的PZItem,带出关联Item字段
        valid_pz_items = PZItem.objects.filter(
            quantity_available_to_order__gt=0
        ).values(
            "item__title", "item__category", "id", "quantity_available_to_order"
        )
        # 格式转换为期望的key
        pz_item_list = [
            {
                "title": item["item__title"],
                "category": item["item__category"],
                "pzitem_id": item["id"],
                "pzitem_quantity_available_to_order": item["quantity_available_to_order"],
            }
            for item in valid_pz_items
        ]

        # 第二步:查询所有没有关联有效PZItem的Item
        items_without_valid_pz = Item.objects.exclude(
            pz_items__quantity_available_to_order__gt=0
        ).values("title", "category")
        # 补全空字段
        empty_pz_item_list = [
            {
                "title": item["title"],
                "category": item["category"],
                "pzitem_id": None,
                "pzitem_quantity_available_to_order": None,
            }
            for item in items_without_valid_pz
        ]

        # 合并两个列表返回
        return Response(chain(pz_item_list, empty_pz_item_list))

如果一定要用Serializer实现,可以重写ListSerializer的to_representation方法,逻辑和上面一致即可。

问题2:基于当前的原始数据结构,如何用react-table渲染表格并插入分组表头?

方案1:用你现有的前端转换函数+react-table分组功能

你写的前端转换逻辑完全可用,前端转换的开销可以忽略,还能减少接口传输的数据冗余,结合react-table的useGroupBy钩子就能自动生成分组表头:

import { useTable, useGroupBy } from 'react-table'

// 优化后的转换函数,避免全局变量污染
const transformData = (rawData) => {
  const newData = []
  rawData.forEach((item) => {
    const { pzitems, title, category } = item
    if (pzitems.length) {
      pzitems.forEach(pzitem => {
        newData.push({
          title,
          category,
          pzitem_id: pzitem.id,
          pzitem_quantity_available_to_order: pzitem.quantity_available_to_order
        })
      })
    } else {
      newData.push({
        title,
        category,
        pzitem_id: null,
        pzitem_quantity_available_to_order: null
      })
    }
  })
  return newData
}

// 表格组件
function ItemTable({ rawData }) {
  const data = transformData(rawData)
  const columns = [
    {
      Header: '商品名称',
      accessor: 'title',
      groupBy: true // 开启按商品名称分组
    },
    {
      Header: '分类ID',
      accessor: 'category'
    },
    {
      Header: 'PZItem ID',
      accessor: 'pzitem_id'
    },
    {
      Header: '可订购数量',
      accessor: 'pzitem_quantity_available_to_order'
    }
  ]

  const {
    getTableProps,
    getTableBodyProps,
    headerGroups,
    rows,
    prepareRow
  } = useTable({ columns, data }, useGroupBy)

  return (
    <table {...getTableProps()} style={{ width: '100%', borderCollapse: 'collapse' }}>
      <thead>
        {headerGroups.map(headerGroup => (
          <tr {...headerGroup.getHeaderGroupProps()}>
            {headerGroup.headers.map(column => (
              <th {...column.getHeaderProps()} style={{ border: '1px solid #ddd', padding: '8px' }}>
                {column.render('Header')}
              </th>
            ))}
          </tr>
        ))}
      </thead>
      <tbody {...getTableBodyProps()}>
        {rows.map(row => {
          prepareRow(row)
          // 渲染分组表头行
          if (row.isGrouped) {
            return (
              <tr {...row.getRowProps()} style={{ background: '#f5f5f5', fontWeight: 'bold' }}>
                <td {...row.cells[0].getCellProps()} colSpan={columns.length} style={{ border: '1px solid #ddd', padding: '8px' }}>
                  {row.render('GroupValue')}
                </td>
              </tr>
            )
          }
          // 渲染普通数据行
          return (
            <tr {...row.getRowProps()}>
              {row.cells.map(cell => (
                <td {...cell.getCellProps()} style={{ border: '1px solid #ddd', padding: '8px' }}>
                  {cell.render('Cell') ?? '-'}
                </td>
              ))}
            </tr>
          )
        })}
      </tbody>
    </table>
  )
}

方案2:不转换数据直接渲染

如果你不想做前端数据转换,也可以直接在react-table的行渲染逻辑里遍历嵌套的pzitems字段,手动插入分组行,逻辑和上面的转换逻辑类似,只是把转换步骤放到渲染阶段即可。

内容的提问来源于stack exchange,提问作者skelaw

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.25 22:36:03