如何在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}股"
关键改进点说明
拆分模型职责:
Portfolio专门代表投资组合实体,每个组合对应唯一ID,彻底解决原模型的主键冲突问题PortfolioStock负责记录每笔持仓,一条记录对应「某个组合+某只股票」的持仓细节
遵循Django惯例:
- 模型类名用单数(
Portfolio而非Portfolios) - ForeignKey字段直接用关联模型名(比如
user而非user_id),Django会自动在数据库生成user_id字段 - 用
django.utils.timezone.now替代datetime.now,支持项目时区配置
- 模型类名用单数(
保障数据完整性:
- 给
Stock.stock_symbol加unique=True,避免重复录入同一股票 - 给
PortfolioStock加联合唯一约束,防止同一个组合里重复添加同一只股票
- 给
提升开发可用性:
- 添加
__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=20id=2, portfolio_id=1, stock_id=2, buy_datetime=2020-02-19, number_of_shares=41
既清晰区分了投资组合和持仓记录,又完全满足你的业务需求。
内容的提问来源于stack exchange,提问作者Twenny
相关产品推荐
相关产品推荐

