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

如何用Django实现表格新增行列并将数据保存至数据库?

Django 动态表格数据存储方案(行列可新增)

数据库表结构是静态的,绝对不要尝试动态新增数据库列——这会导致数据迁移混乱、维护成本飙升。推荐两种成熟的动态数据存储方案,根据你的业务需求选择:


方案一:JSON字段存储(适合结构简单、查询需求少的场景)

把整行数据以键值对形式存在JSON字段中,列名作为键,单元格值作为值,无需修改数据库结构。

1. 模型定义

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

class DynamicTable(models.Model):
    name = models.CharField(max_length=100, verbose_name="表格名称")
    created_by = models.ForeignKey(User, on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.name

class TableRow(models.Model):
    table = models.ForeignKey(DynamicTable, on_delete=models.CASCADE, related_name="rows")
    row_data = models.JSONField(default=dict, verbose_name="行数据")  # 存储动态列的键值对
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return f"Row {self.id} for {self.table.name}"

2. Admin后台配置

让用户能在Admin创建表格:

from django.contrib import admin
from .models import DynamicTable, TableRow

class TableRowInline(admin.TabularInline):
    model = TableRow
    extra = 0

@admin.register(DynamicTable)
class DynamicTableAdmin(admin.ModelAdmin):
    inlines = [TableRowInline]
    list_display = ("name", "created_by", "created_at")
    search_fields = ("name",)

3. 前端交互与数据提交

用JS收集表格数据,整理成JSON格式后提交:

// 收集表格数据(假设单元格是输入框)
function getTableData(tableId) {
    const table = document.getElementById(tableId);
    const headers = Array.from(table.querySelectorAll('thead th')).map(th => th.textContent.trim());
    const rows = Array.from(table.querySelectorAll('tbody tr')).map(tr => {
        const rowData = {};
        const cells = tr.querySelectorAll('td input');
        cells.forEach((cell, index) => {
            rowData[headers[index]] = cell.value;
        });
        return rowData;
    });
    return { rows: rows };
}

// 提交到后端
document.getElementById('save-btn').addEventListener('click', async () => {
    const tableData = getTableData('dynamic-table');
    const tableId = {{ table.id }}; // 从Django模板传入表格ID
    const response = await fetch(`/save-table/${tableId}/`, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'X-CSRFToken': '{{ csrf_token }}'
        },
        body: JSON.stringify(tableData)
    });
    const result = await response.json();
    result.success ? alert('保存成功') : alert(`保存失败:${result.error}`);
});

4. 后端视图处理

from django.http import JsonResponse
from django.views.decorators.http import require_POST
from django.views.decorators.csrf import csrf_exempt
from .models import DynamicTable, TableRow

@require_POST
@csrf_exempt
def save_table_data(request, table_id):
    try:
        table = DynamicTable.objects.get(id=table_id)
        data = request.json()
        
        # 清除旧数据后新增(可根据需求改成更新逻辑)
        TableRow.objects.filter(table=table).delete()
        for row_data in data['rows']:
            TableRow.objects.create(table=table, row_data=row_data)
        
        return JsonResponse({'success': True})
    except Exception as e:
        return JsonResponse({'success': False, 'error': str(e)}, status=400)

方案二:EAV模式(实体-属性-值,适合需按列查询/统计的场景)

把每个单元格作为一条独立记录存储,拆分表格的行、列、单元格三个维度,灵活性更高。

1. 模型定义

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

class DynamicTable(models.Model):
    name = models.CharField(max_length=100, verbose_name="表格名称")
    created_by = models.ForeignKey(User, on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.name

class TableColumn(models.Model):
    table = models.ForeignKey(DynamicTable, on_delete=models.CASCADE, related_name="columns")
    column_name = models.CharField(max_length=50, verbose_name="列名")
    order = models.IntegerField(default=0, verbose_name="列顺序")

    class Meta:
        ordering = ["order"]

    def __str__(self):
        return self.column_name

class TableCell(models.Model):
    table = models.ForeignKey(DynamicTable, on_delete=models.CASCADE)
    row_id = models.IntegerField(verbose_name="行标记")  # 用整数关联同一行的所有单元格
    column = models.ForeignKey(TableColumn, on_delete=models.CASCADE)
    cell_value = models.CharField(max_length=255, verbose_name="单元格值")

    class Meta:
        unique_together = ("table", "row_id", "column")

2. Admin后台配置

from django.contrib import admin
from .models import DynamicTable, TableColumn, TableCell

class TableColumnInline(admin.TabularInline):
    model = TableColumn
    extra = 1

class TableCellInline(admin.TabularInline):
    model = TableCell
    extra = 0

@admin.register(DynamicTable)
class DynamicTableAdmin(admin.ModelAdmin):
    inlines = [TableColumnInline, TableCellInline]
    list_display = ("name", "created_by", "created_at")

3. 前端数据提交

function getEAVTableData(tableId) {
    const table = document.getElementById(tableId);
    // 收集列信息
    const columns = Array.from(table.querySelectorAll('thead th')).map((th, index) => ({
        name: th.textContent.trim(),
        order: index
    }));
    // 收集单元格数据
    const cells = Array.from(table.querySelectorAll('tbody tr')).map((tr, rowIndex) => {
        const rowCells = tr.querySelectorAll('td input');
        return rowCells.map((cell, colIndex) => ({
            row_id: rowIndex + 1,
            column_name: columns[colIndex].name,
            value: cell.value
        }));
    }).flat(); // 转成一维数组,每个单元格一条数据
    return { columns: columns, cells: cells };
}

// 提交逻辑同方案一,发送到对应的后端视图

4. 后端视图处理

@require_POST
@csrf_exempt
def save_eav_table(request, table_id):
    try:
        table = DynamicTable.objects.get(id=table_id)
        data = request.json()
        
        // 清除旧数据
        TableColumn.objects.filter(table=table).delete()
        TableCell.objects.filter(table=table).delete()
        
        // 保存列
        column_map = {}
        for col in data['columns']:
            column = TableColumn.objects.create(
                table=table,
                column_name=col['name'],
                order=col['order']
            )
            column_map[col['name']] = column
        
        // 保存单元格
        for cell in data['cells']:
            TableCell.objects.create(
                table=table,
                row_id=cell['row_id'],
                column=column_map[cell['column_name']],
                cell_value=cell['value']
            )
        
        return JsonResponse({'success': True})
    except Exception as e:
        return JsonResponse({'success': False, 'error': str(e)}, status=400)

关键注意事项

  • 优先选择JSON字段方案,开发成本更低;EAV方案适合有复杂查询需求的场景,但会增加数据库查询复杂度。
  • 前端提交必须携带CSRF Token,避免跨站请求伪造。
  • 可优化数据更新逻辑,比如只提交新增/修改的行,而不是全量删除再新增。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 01:20:32