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

Odoo基于父模型字段生成动态子模型的实现方案问询

基于动态Properties生成子模型的实现方案

完全可以实现需求,下面分后端、前端给出具体落地方案,以Python Django + Vue.js为例(其他技术栈思路通用):

一、核心思路

因为properties字段是动态定义的,子模型的列结构无法提前固定,所以采用**「动态结构存储+前端动态渲染」**的组合方案,主要有两种存储模式可选,根据查询需求选择:


二、后端实现

1. 定义基础Data模型

首先存储主模型的基础信息,同时记录动态属性的结构(字段名、数据类型):

from django.db import models

class Data(models.Model):
    name = models.CharField(max_length=255)
    latitude = models.FloatField()
    longitude = models.FloatField()
    # 用JSON存储动态属性的结构,比如 {"temperature": "float", "humidity": "string"}
    properties_schema = models.JSONField(default=dict)

2. 动态子数据存储方案

方案1:JSON字段存储(推荐,适合中小数据量)

用JSON字段直接存储每一行的动态值,结构简单,开发成本低:

class DataRow(models.Model):
    data = models.ForeignKey(Data, on_delete=models.CASCADE, related_name="rows")
    # 存储一行的动态属性值,key对应properties_schema中的字段名
    values = models.JSONField(default=dict)
  • 当用户创建Data对象时,提交的properties结构会被存在properties_schema中
  • 添加数据行时,前端收集的字段值会以JSON格式存在values里

方案2:EAV模式(适合需对动态列做复杂查询的场景)

如果需要基于动态属性做过滤、排序等操作,可采用实体-属性-值模式:

class DataAttribute(models.Model):
    data = models.ForeignKey(Data, on_delete=models.CASCADE, related_name="attributes")
    row_identifier = models.CharField(max_length=255)  # 标记同一行的属性
    attribute_name = models.CharField(max_length=255)  # 动态属性名
    # 按数据类型分字段存储,避免类型混淆
    value_float = models.FloatField(null=True)
    value_string = models.CharField(max_length=255, null=True)
    value_int = models.IntegerField(null=True)

这种方式每个属性单独存一行,查询时可针对性过滤,但代码复杂度更高。

3. 核心接口实现

  • 创建Data接口:接收name、latitude、longitude、properties_schema,保存Data对象
  • 添加数据行接口:接收data_id和values(JSON),创建DataRow关联到对应Data
  • 获取Data详情接口:返回Data的基础信息、properties_schema及所有关联的DataRow

三、前端实现

核心是根据后端返回的properties_schema动态生成表格的表头和输入框:

1. 动态渲染表格(Vue.js示例)

<template>
  <div class="dynamic-table">
    <table>
      <thead>
        <tr>
          <th v-for="(type, field) in data.properties_schema" :key="field">
            {{ field }}
          </th>
          <th>操作</th>
        </tr>
      </thead>
      <tbody>
        <!-- 已有数据行 -->
        <tr v-for="(row, idx) in data.rows" :key="row.id">
          <td v-for="(type, field) in data.properties_schema" :key="field">
            {{ row.values[field] }}
          </td>
          <td><button @click="deleteRow(row.id)">删除</button></td>
        </tr>
        <!-- 新增行输入区 -->
        <tr>
          <td v-for="(type, field) in data.properties_schema" :key="field">
            <input 
              :type="type === 'float' ? 'number' : 'text'" 
              v-model="newRow[field]"
              :placeholder="`输入${field}`"
            >
          </td>
          <td><button @click="submitNewRow">添加</button></td>
        </tr>
      </tbody>
    </table>
  </div>
</template>

<script>
export default {
  data() {
    return {
      data: {},
      newRow: {}
    }
  },
  methods: {
    async fetchData() {
      // 从后端获取Data详情(含schema和rows)
      const res = await this.$axios.get(`/api/data/${this.$route.params.id}/`)
      this.data = res.data
      // 初始化新增行的空值
      this.newRow = Object.fromEntries(Object.keys(this.data.properties_schema).map(k => [k, '']))
    },
    async submitNewRow() {
      // 提交新增行到后端
      await this.$axios.post('/api/data-rows/', {
        data_id: this.data.id,
        values: this.newRow
      })
      // 刷新数据
      this.fetchData()
      this.newRow = Object.fromEntries(Object.keys(this.data.properties_schema).map(k => [k, '']))
    },
    async deleteRow(rowId) {
      await this.$axios.delete(`/api/data-rows/${rowId}/`)
      this.fetchData()
    }
  },
  mounted() {
    this.fetchData()
  }
}
</script>

四、关键注意事项

  • 数据校验:后端要根据properties_schema校验传入的values类型是否匹配,比如float类型不能存字符串
  • 性能优化:如果用JSON存储,PostgreSQL的JSONB类型支持索引和部分查询,比普通JSON更适合生产环境
  • 兼容性处理:如果允许用户修改properties_schema,要考虑已有数据的兼容,比如新增字段给默认值,删除字段时保留历史数据

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 17:32:11