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

如何在Django中用3张表正确建模用户-投资组合-股票多对多关系?

Django 数据库建模优化:用户-投资组合-股票关系

你的核心问题是混淆了「投资组合实体」和「投资组合持仓记录」的概念,原代码里的Portfolios表既想代表投资组合,又想记录每只股票的持仓,导致同一个投资组合ID对应多条记录,主键冲突。下面是符合Django最佳实践的改进方案:

模型设计思路

正确的关系逻辑:

  • 一个用户(User)可拥有多个独立的投资组合(Portfolio)
  • 一个投资组合(Portfolio)包含多只股票(Stock),每只股票的持仓需记录买入时间、股数等细节
  • 这是典型的「多对多关联带额外字段」场景,需要拆分出独立的持仓记录表

优化后的代码实现

from django.db import models
from django.utils.timezone import now

class User(models.Model):
    # Django默认自动生成id主键,无需手动定义AutoField,除非有特殊命名需求
    username = models.CharField(max_length=25, unique=True)
    in_cash = models.DecimalField(max_digits=15, decimal_places=2, default=0.00)

    def __str__(self):
        return self.username

class Portfolio(models.Model):
    # 代表独立的投资组合实体,比如"蓝筹价值组合"、"科技成长组合"
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="portfolios")
    name = models.CharField(max_length=50, default="默认组合")  # 可选:给组合添加名称便于区分
    created_at = models.DateTimeField(default=now)

    def __str__(self):
        return f"{self.user.username} - {self.name}"

class Stock(models.Model):
    stock_symbol = models.CharField(max_length=12, unique=True)  # 股票代码需唯一
    name = models.CharField(max_length=100, blank=True)  # 可选:补充股票全称

    def __str__(self):
        return f"{self.stock_symbol} ({self.name})"

class PortfolioStock(models.Model):
    # 记录投资组合中单只股票的持仓详情
    portfolio = models.ForeignKey(Portfolio, on_delete=models.CASCADE, related_name="holdings")
    stock = models.ForeignKey(Stock, on_delete=models.CASCADE)
    buy_datetime = models.DateTimeField(default=now)
    number_of_shares = models.IntegerField()

    class Meta:
        # 约束:同一个投资组合里同一只股票只能有一条持仓记录
        constraints = [
            models.UniqueConstraint(fields=['portfolio', 'stock'], name='unique_portfolio_stock')
        ]

    def __str__(self):
        return f"{self.portfolio.name} - {self.stock.stock_symbol}: {self.number_of_shares}股"

关键改进点说明

  1. 拆分模型职责:

    • Portfolio专门代表投资组合实体,每个组合对应唯一ID,彻底解决原模型的主键冲突问题
    • PortfolioStock负责记录每笔持仓,一条记录对应「某个组合+某只股票」的持仓细节
  2. 遵循Django惯例:

    • 模型类名用单数(Portfolio而非Portfolios)
    • ForeignKey字段直接用关联模型名(比如user而非user_id),Django会自动在数据库生成user_id字段
    • 用django.utils.timezone.now替代datetime.now,支持项目时区配置
  3. 保障数据完整性:

    • 给Stock.stock_symbol加unique=True,避免重复录入同一股票
    • 给PortfolioStock加联合唯一约束,防止同一个组合里重复添加同一只股票
  4. 提升开发可用性:

    • 添加__str__方法,在Django admin或调试时能直观看到对象信息
    • 给User.username加unique=True,确保用户名唯一
    • 配置related_name,方便通过用户对象快速获取所有投资组合(比如user.portfolios.all())

对应你的业务场景示例

按照这个模型,你需要的记录结构会变成:

  • Portfolio表:id=1, user_id=1, name="我的第一组合"
  • PortfolioStock表:
    • id=1, portfolio_id=1, stock_id=1, buy_datetime=2019-01-12, number_of_shares=20
    • id=2, portfolio_id=1, stock_id=2, buy_datetime=2020-02-19, number_of_shares=41

既清晰区分了投资组合和持仓记录,又完全满足你的业务需求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 02:35:19