使用 Django ORM 从相关表中获取数据

use*_*021 4 python django django-models

使用 Django ORM,如何在不有效地对每条记录进行单独调用的情况下访问相关表中的数据(或者冗余地对数据进行非规范化以使其更易于访问)?

假设我有 3 个模型:

class Tournament(models.Model):
    name = models.CharField(max_length=250) 
    active = models.BooleanField(null=True,default=1)
    
class Team(models.Model):
    name = models.CharField(max_length=250)
    coach_name = models.CharField(max_length=250)
    active = models.BooleanField(null=True,default=1)
    
class Player(models.Model):
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.DO_NOTHING
    )
    number = models.PositiveIntegerField()
    age = models.PositiveIntegerField()
    active = models.BooleanField(null=True,default=1)
Run Code Online (Sandbox Code Playgroud)

请注意,此玩家模型在应用程序中很重要,因为它是与大多数模型的主要连接 - 从注册到团队到统计数据到结果到奖品。但此 Player 模型实际上并不包含人名,因为该模型包含一个用户字段,该字段是AUTH_USER_MODEL包含名字/姓氏信息的自定义(“用户”)模型的外键。这允许玩家登录到应用程序并执行某些操作。

除了这些基本模型之外,假设由于玩家可以在不同的锦标赛中为不同的球队效力,我还有一个连接的 ManyToMany 模型:

class PlayerToTeam(models.Model):
    player = models.ForeignKey(
        Player,
        on_delete=models.DO_NOTHING
    )
    team = models.ForeignKey(
        Team,
        on_delete=models.DO_NOTHING
    )
    tournament = models.ForeignKey(
        Tournament,
        on_delete=models.DO_NOTHING
    )       
Run Code Online (Sandbox Code Playgroud)

作为我遇到的挑战之一的例子,假设我正在尝试创建一种允许教练选择首发阵容的表格。因此,我需要我的表格来列出特定锦标赛中特定团队的球员姓名。

给定锦标赛和团队 ID,我可以轻松提取必要的 QuerySet 来描述我感兴趣的初始记录。

playersOnTeam = PlayerToTeam.objects.filter(tournament=[Tournament_id]).filter(team=[Team_id])
Run Code Online (Sandbox Code Playgroud)

这将返回球队、锦标赛和球员的 ID(但仅是 ID)的 QuerySet。然而,名称数据与两个模型相距甚远:

PlayerToTeam->[player_id]->玩家->[user_id]->用户->[first_name] [last_name]

现在,如果我只拉回一条记录,我可以简单地做

onlyPlayerOnTeam = PlayerToTeam.objects.filter(tournament=[Tournament_id]).filter(team=[Team_id]).filter(player=[Player_id]).get()

onlyPlayerOnTeam.player.user.first_name
Run Code Online (Sandbox Code Playgroud)

因此,如果我只需要显示名称,我相信我可以在视图返回中传递 QuerySet,并在模板中循环它并显示我需要的内容。但我不知道当我需要将名称显示为表单的一部分时,您是否可以执行类似的操作。

为了填充表单,我相信我可以循环初始查询集并构建一个新的数据结构:

playersOnTeam = PlayerToTeam.objects.filter(tournament=[Tournament_id]).filter(team=[Team_id])

allPlayersData= []
for nextPlayer in playersOnTeam:
    playerDetails= {
        "player_id": nextPlayer.player.id,
        "first_name": nextPlayer.player.user.first_name, 
        "last_name": nextPlayer.player.user.last_name,
    }
    allPlayersData.append(playerDetails)

form = StartingLineupForm(allPlayersData)
Run Code Online (Sandbox Code Playgroud)

但是,我担心这会导致每个玩家/用户都有一个单独的数据库调用!

虽然这对于 6-10 名玩家来说可能是可以忍受的,但对于更大的数据集来说,这似乎不太理想。循环对每个用户执行查询似乎是完全错误的。

此外,令人沮丧的是,使用直接的 SQL 查询就足够简单了:

SELECT User.first_name, User.last_name 
FROM PlayerToTeam 
INNER JOIN Player ON PlayerToTeam.player_id = Player.id 
INNER JOIN User ON Player.user_id = User.id 
WHERE PlayerToTeam.tournament_id=[tourney_id] AND PlayerToTeam.team_id=[team_id]
Run Code Online (Sandbox Code Playgroud)

但我试图尽可能地坚持 Django ORM 最佳实践,并避免在无法立即弄清楚某些问题时直接使用 SQL 查询,并且我几乎可以肯定这并不那么复杂在这种情况下,如果不借助直接 SQL 查询,我就无法完成此任务。

我开始关注select_relatedprefetch_related,但我很难理解它们如何适用于超过单个表连接的关系。好像我可以Player.age使用预取来访问数据,但我不知道如何User.first_name从中获取数据。

任何帮助,将不胜感激。

tar*_*inf 5

  1. 我建议两种方法:

A)选择相关(一个数据库查询):

objects = PlayerToTeam.objects.filter(
    ...
).select_related(
    'player__user',
).only('player__user__name')

name = objects.first().user.name
Run Code Online (Sandbox Code Playgroud)

B) 注释(一个数据库查询):

objects = PlayerToTeam.objects.filter(
    ...
).annotate(
    player_name=F('player__user__name'),
)

name = objects.first().player_name
Run Code Online (Sandbox Code Playgroud)
  1. 为了确保您只有一个特定的对象playerteam并且tournament,我建议添加unique_together
class PlayerToTeam(models.Model):
    ...

    class Meta:
        unique_together = ('player', 'team', 'tournament', )
Run Code Online (Sandbox Code Playgroud)