查询集的高效逆向
Efficient reverse of queryset
我正在尝试寻找最有效的方法来遍历我的模型以获取我需要的数据。我有三个 "related" 模型。 Item
、ProtectionList
和 Player
保护列表
class ProtectionList(models.Model):
player = models.ForeignKey(Player)
main_hand = models.ForeignKey(Item, null=True, blank=True, related_name='main_hand')
off_hand = models.ForeignKey(Item, null=True, blank=True, related_name='off_hand')
head = models.ForeignKey(Item, null=True, blank=True, related_name='head')
neck = models.ForeignKey(Item, null=True, blank=True, related_name='neck')
shoulder = models.ForeignKey(Item, null=True, blank=True, related_name='shoulder')
back = models.ForeignKey(Item, null=True, blank=True, related_name='back')
chest = models.ForeignKey(Item, null=True, blank=True, related_name='chest')
wrist = models.ForeignKey(Item, null=True, blank=True, related_name='wrist')
hands = models.ForeignKey(Item, null=True, blank=True, related_name='hands')
waist = models.ForeignKey(Item, null=True, blank=True, related_name='waist')
legs = models.ForeignKey(Item, null=True, blank=True, related_name='legs')
feet = models.ForeignKey(Item, null=True, blank=True, related_name='feet')
ring1 = models.ForeignKey(Item, null=True, blank=True, related_name='ring1')
ring2 = models.ForeignKey(Item, null=True, blank=True, related_name='ring2')
trinket1 = models.ForeignKey(Item, null=True, blank=True, related_name='trinket1')
trinket2 = models.ForeignKey(Item, null=True, blank=True, related_name='trinket2')
locked = models.BooleanField(default=False)
def __unicode__(self):
return self.player.main_character.name
玩家
class Player(models.Model):
accountID = models.CharField(max_length=255, null=True, blank=True)
battletag = models.CharField(max_length=255, null=True, blank=True)
user = models.OneToOneField(User, null=True, blank=True)
main_character = models.ForeignKey('Character', null=True, blank=True, related_name='main_character')
signature = models.TextField(null=True, blank=True)
def __unicode__(self):
return self.battletag
项目
class Item(models.Model):
'''Details individual data for each item on a loot table'''
name = models.CharField(max_length=255, null=True)
item_id = models.IntegerField(null=True)
dropped_by = models.ForeignKey(Boss)
warforgeable = models.BooleanField(default=True)
bonus_string = models.CharField(max_length=255, blank=True, null=True)
def __unicode__(self):
return self.name
我需要映射回 Player
以根据谁在其 ProtectedList
的任何字段中拥有 Item
来制作 Player
对象的列表.
我知道 Item
与 ProtectionList
相关,ProtectionList
与 Player
相关,但是我想找到一种有效的方法来检查 [=13] 的所有字段=] 是 ProtectionList
对象中的外键,因为被过滤的 Item
可能在任何字段中(减去 player
和 locked
字段)。
我考虑过使用 Q 来对每个字段进行或运算,但我不确定这在较大的查询集上的最佳效果。
我也在查看中间表并将模型更改为多对多。
我可能是错的,但我觉得你的模型结构没有规范化。您通过 Slots
在 Player
和 Item
之间建立了 M2M 关系。考虑以下模型结构:
from django.db import models
from django.contrib.auth.models import User
class Slot(models.Model):
#e.g. main_hand, off_hand, head, ... , feet
name = models.CharField(max_length=255)
def __unicode__(self):
return self.name
class Item(models.Model):
'''Details individual data for each item on a loot table'''
name = models.CharField(max_length=255, null=True)
item_id = models.IntegerField(null=True)
"""removed for the example"""
#dropped_by = models.ForeignKey(Boss)
warforgeable = models.BooleanField(default=True)
bonus_string = models.CharField(max_length=255, blank=True, null=False, default='')
def __unicode__(self):
return self.name
class PlayerManager(models.Manager):
def create_player_with_empty_slots(self, **kwargs):
player = Player.objects.create(**kwargs)
PlayerSlot.objects.bulk_create([
PlayerSlot(player=player, slot=slot) for slot in Slot.objects.all()
])
return player
class Player(models.Model):
user = models.OneToOneField(User, null=True, blank=True)
"""removed for the example"""
#accountID = models.CharField(max_length=255, null=True, blank=True)
#battletag = models.CharField(max_length=255, null=True, blank=True)
#main_character = models.ForeignKey('Character', null=True, blank=True, related_name='main_character')
#signature = models.TextField(null=True, blank=True)
slots = models.ManyToManyField(Slot, through='PlayerSlot')
items = models.ManyToManyField(Item, through='PlayerSlot')
objects = PlayerManager()
def __unicode__(self):
"""changed for the example"""
return self.user.username
def get_slot(self, slot_or_slotname):
if isinstance(slot_or_slotname, Slot):
return self.playerslot_set.get(slot=slot_or_slotname)
else:
return self.playerslot_set.get(slot__name=slot_or_slotname)
def set_item(self, item, at_slot):
slot = self.get_slot(at_slot)
slot.item = item
slot.save()
class PlayerSlot(models.Model):
player = models.ForeignKey(Player)
slot = models.ForeignKey(Slot)
#item is optional, to allow empty slots
item = models.ForeignKey(Item, null=True, blank=True)
locked = models.BooleanField(default=False)
class Meta:
unique_together = ('player', 'slot')
def __unicode__(self):
return "{player} carry {item} on {slot}".format(player=self.player, item=self.item or 'nothing', slot=self.slot)
和API的
一起玩
>>> from django.contrib.auth.models import User
>>> from game.models import Slot, Item, Player, PlayerSlot
让我们创建一些初始数据:
>>> Slot.objects.bulk_create([
... Slot(name='main_hand'),
... Slot(name='off_hand'),
... Slot(name='head'),
... Slot(name='neck'),
... Slot(name='shoulder'),
... Slot(name='back'),
... Slot(name='chest'),
... Slot(name='wrist'),
... Slot(name='hands'),
... Slot(name='waist'),
... Slot(name='legs'),
... Slot(name='feet'),
... Slot(name='ring1'),
... Slot(name='ring2'),
... Slot(name='trinket1'),
... Slot(name='trinket2'),
... ])
[<Slot: main_hand>, <Slot: off_hand>, <Slot: head>, <Slot: neck>, <Slot: shoulder>, <Slot: back>, <Slot: chest>, <Slot: wrist>, <Slot: hands>, <Slot: waist>, <Slot: legs>, <Slot: feet>, <Slot: ring1>, <Slot: ring2>, <Slot: trinket1>, <Slot: trinket2>]
>>> Item.objects.bulk_create([
... Item(name='Short Sword'),
... Item(name='Sabre'),
... Item(name='Broadsword'),
... Item(name='Apprentice Broadsword'),
... Item(name='Monster Hunter'),
... ])
[<Item: Short Sword>, <Item: Sabre>, <Item: Broadsword>, <Item: Apprentice Broadsword>, <Item: Monster Hunter>]
>>> user1 = User.objects.create_user('john', 'lennon@thebeatles.com', 'johnpassword')
>>> user2 = User.objects.create_user('paul', 'mccartney@thebeatles.com', 'mccartnypassword')
>>> player1 = Player.objects.create_player_with_empty_slots(user=user1)
>>> player2 = Player.objects.create_player_with_empty_slots(user=user2)
获取所有玩家位置:
>>> player1.slots.all()
[<Slot: main_hand>, <Slot: off_hand>, <Slot: head>, <Slot: neck>, <Slot: shoulder>, <Slot: back>, <Slot: chest>, <Slot: wrist>, <Slot: hands>, <Slot: waist>, <Slot: legs>, <Slot: feet>, <Slot: ring1>, <Slot: ring2>, <Slot: trinket1>, <Slot: trinket2>]
获取所有玩家物品:
>>> player1.items.all()
[]
添加新项目?
>>> broadsword = Item.objects.get(name='Broadsword')
>>> player1.set_item(broadsword, at_slot='main_hand')
>>> player1.items.all()
[<Item: Broadsword>]
检查某个槽里面有什么?
>>> player1.get_slot('main_hand')
<PlayerSlot: john carry Broadsword on main_hand>
查找拥有物品的玩家:
>>> Player.objects.filter(items=broadsword)
[<Player: john>]
两位炼金术士的更多示例:
以下是此模型结构的一些示例查询:
你有一个 Player 对象 player
并且需要知道 his/her 左手是什么:
player.items.filter(slot__name='off_hand')[0].item # Could be None
您有一个物品对象 item
并且想知道它装备在哪个插槽 player
中:
PlayerSlot.objects.filter(player=player).filter(item=item)[0].slot.name
您有一个物品对象 item
并且想知道到底有哪些玩家拥有该物品:
PlayerSlot.objects.filter(item=item).all()
或
Player.objects.filter(slots__item=item).all()
取决于您更愿意使用哪个对象。我没有测试上面的任何查询,但这应该可以帮助您入门。另请注意,如果关系不符合您的想法(例如,player
实际上没有 item
),以这种方式链接(查询 1 和 2)可能会抛出 IndexError
,所以准备好抓住它。
迁移旧数据
我相信这样的事情可以完成这项工作:
plist_slots = [
'main_hand', 'off_hand', 'head', 'neck', 'shoulder',
'back', 'chest', 'wrist', 'hands', 'waist', 'legs',
'feet', 'ring1', 'ring2', 'trinket1', 'trinket2'
]
Slot.objects.bulk_create([
Slot(name=slot) for slot in plist_slots
])
plist = ProtectionList.objects.all().select_related(
*list(['player'] + plist_slots)
)
for p in plist:
PlayerSlot.objects.bulk_create([
PlayerSlot(
player=p.player,
item=getattr(p, slot),
slot=Slot.objects.get(name=slot)
) for slot in plist_slots
])
我正在尝试寻找最有效的方法来遍历我的模型以获取我需要的数据。我有三个 "related" 模型。 Item
、ProtectionList
和 Player
保护列表
class ProtectionList(models.Model):
player = models.ForeignKey(Player)
main_hand = models.ForeignKey(Item, null=True, blank=True, related_name='main_hand')
off_hand = models.ForeignKey(Item, null=True, blank=True, related_name='off_hand')
head = models.ForeignKey(Item, null=True, blank=True, related_name='head')
neck = models.ForeignKey(Item, null=True, blank=True, related_name='neck')
shoulder = models.ForeignKey(Item, null=True, blank=True, related_name='shoulder')
back = models.ForeignKey(Item, null=True, blank=True, related_name='back')
chest = models.ForeignKey(Item, null=True, blank=True, related_name='chest')
wrist = models.ForeignKey(Item, null=True, blank=True, related_name='wrist')
hands = models.ForeignKey(Item, null=True, blank=True, related_name='hands')
waist = models.ForeignKey(Item, null=True, blank=True, related_name='waist')
legs = models.ForeignKey(Item, null=True, blank=True, related_name='legs')
feet = models.ForeignKey(Item, null=True, blank=True, related_name='feet')
ring1 = models.ForeignKey(Item, null=True, blank=True, related_name='ring1')
ring2 = models.ForeignKey(Item, null=True, blank=True, related_name='ring2')
trinket1 = models.ForeignKey(Item, null=True, blank=True, related_name='trinket1')
trinket2 = models.ForeignKey(Item, null=True, blank=True, related_name='trinket2')
locked = models.BooleanField(default=False)
def __unicode__(self):
return self.player.main_character.name
玩家
class Player(models.Model):
accountID = models.CharField(max_length=255, null=True, blank=True)
battletag = models.CharField(max_length=255, null=True, blank=True)
user = models.OneToOneField(User, null=True, blank=True)
main_character = models.ForeignKey('Character', null=True, blank=True, related_name='main_character')
signature = models.TextField(null=True, blank=True)
def __unicode__(self):
return self.battletag
项目
class Item(models.Model):
'''Details individual data for each item on a loot table'''
name = models.CharField(max_length=255, null=True)
item_id = models.IntegerField(null=True)
dropped_by = models.ForeignKey(Boss)
warforgeable = models.BooleanField(default=True)
bonus_string = models.CharField(max_length=255, blank=True, null=True)
def __unicode__(self):
return self.name
我需要映射回 Player
以根据谁在其 ProtectedList
的任何字段中拥有 Item
来制作 Player
对象的列表.
我知道 Item
与 ProtectionList
相关,ProtectionList
与 Player
相关,但是我想找到一种有效的方法来检查 [=13] 的所有字段=] 是 ProtectionList
对象中的外键,因为被过滤的 Item
可能在任何字段中(减去 player
和 locked
字段)。
我考虑过使用 Q 来对每个字段进行或运算,但我不确定这在较大的查询集上的最佳效果。
我也在查看中间表并将模型更改为多对多。
我可能是错的,但我觉得你的模型结构没有规范化。您通过 Slots
在 Player
和 Item
之间建立了 M2M 关系。考虑以下模型结构:
from django.db import models
from django.contrib.auth.models import User
class Slot(models.Model):
#e.g. main_hand, off_hand, head, ... , feet
name = models.CharField(max_length=255)
def __unicode__(self):
return self.name
class Item(models.Model):
'''Details individual data for each item on a loot table'''
name = models.CharField(max_length=255, null=True)
item_id = models.IntegerField(null=True)
"""removed for the example"""
#dropped_by = models.ForeignKey(Boss)
warforgeable = models.BooleanField(default=True)
bonus_string = models.CharField(max_length=255, blank=True, null=False, default='')
def __unicode__(self):
return self.name
class PlayerManager(models.Manager):
def create_player_with_empty_slots(self, **kwargs):
player = Player.objects.create(**kwargs)
PlayerSlot.objects.bulk_create([
PlayerSlot(player=player, slot=slot) for slot in Slot.objects.all()
])
return player
class Player(models.Model):
user = models.OneToOneField(User, null=True, blank=True)
"""removed for the example"""
#accountID = models.CharField(max_length=255, null=True, blank=True)
#battletag = models.CharField(max_length=255, null=True, blank=True)
#main_character = models.ForeignKey('Character', null=True, blank=True, related_name='main_character')
#signature = models.TextField(null=True, blank=True)
slots = models.ManyToManyField(Slot, through='PlayerSlot')
items = models.ManyToManyField(Item, through='PlayerSlot')
objects = PlayerManager()
def __unicode__(self):
"""changed for the example"""
return self.user.username
def get_slot(self, slot_or_slotname):
if isinstance(slot_or_slotname, Slot):
return self.playerslot_set.get(slot=slot_or_slotname)
else:
return self.playerslot_set.get(slot__name=slot_or_slotname)
def set_item(self, item, at_slot):
slot = self.get_slot(at_slot)
slot.item = item
slot.save()
class PlayerSlot(models.Model):
player = models.ForeignKey(Player)
slot = models.ForeignKey(Slot)
#item is optional, to allow empty slots
item = models.ForeignKey(Item, null=True, blank=True)
locked = models.BooleanField(default=False)
class Meta:
unique_together = ('player', 'slot')
def __unicode__(self):
return "{player} carry {item} on {slot}".format(player=self.player, item=self.item or 'nothing', slot=self.slot)
和API的
一起玩>>> from django.contrib.auth.models import User
>>> from game.models import Slot, Item, Player, PlayerSlot
让我们创建一些初始数据:
>>> Slot.objects.bulk_create([
... Slot(name='main_hand'),
... Slot(name='off_hand'),
... Slot(name='head'),
... Slot(name='neck'),
... Slot(name='shoulder'),
... Slot(name='back'),
... Slot(name='chest'),
... Slot(name='wrist'),
... Slot(name='hands'),
... Slot(name='waist'),
... Slot(name='legs'),
... Slot(name='feet'),
... Slot(name='ring1'),
... Slot(name='ring2'),
... Slot(name='trinket1'),
... Slot(name='trinket2'),
... ])
[<Slot: main_hand>, <Slot: off_hand>, <Slot: head>, <Slot: neck>, <Slot: shoulder>, <Slot: back>, <Slot: chest>, <Slot: wrist>, <Slot: hands>, <Slot: waist>, <Slot: legs>, <Slot: feet>, <Slot: ring1>, <Slot: ring2>, <Slot: trinket1>, <Slot: trinket2>]
>>> Item.objects.bulk_create([
... Item(name='Short Sword'),
... Item(name='Sabre'),
... Item(name='Broadsword'),
... Item(name='Apprentice Broadsword'),
... Item(name='Monster Hunter'),
... ])
[<Item: Short Sword>, <Item: Sabre>, <Item: Broadsword>, <Item: Apprentice Broadsword>, <Item: Monster Hunter>]
>>> user1 = User.objects.create_user('john', 'lennon@thebeatles.com', 'johnpassword')
>>> user2 = User.objects.create_user('paul', 'mccartney@thebeatles.com', 'mccartnypassword')
>>> player1 = Player.objects.create_player_with_empty_slots(user=user1)
>>> player2 = Player.objects.create_player_with_empty_slots(user=user2)
获取所有玩家位置:
>>> player1.slots.all()
[<Slot: main_hand>, <Slot: off_hand>, <Slot: head>, <Slot: neck>, <Slot: shoulder>, <Slot: back>, <Slot: chest>, <Slot: wrist>, <Slot: hands>, <Slot: waist>, <Slot: legs>, <Slot: feet>, <Slot: ring1>, <Slot: ring2>, <Slot: trinket1>, <Slot: trinket2>]
获取所有玩家物品:
>>> player1.items.all()
[]
添加新项目?
>>> broadsword = Item.objects.get(name='Broadsword')
>>> player1.set_item(broadsword, at_slot='main_hand')
>>> player1.items.all()
[<Item: Broadsword>]
检查某个槽里面有什么?
>>> player1.get_slot('main_hand')
<PlayerSlot: john carry Broadsword on main_hand>
查找拥有物品的玩家:
>>> Player.objects.filter(items=broadsword)
[<Player: john>]
两位炼金术士的更多示例:
以下是此模型结构的一些示例查询:
你有一个 Player 对象
player
并且需要知道 his/her 左手是什么:player.items.filter(slot__name='off_hand')[0].item # Could be None
您有一个物品对象
item
并且想知道它装备在哪个插槽player
中:PlayerSlot.objects.filter(player=player).filter(item=item)[0].slot.name
您有一个物品对象
item
并且想知道到底有哪些玩家拥有该物品:PlayerSlot.objects.filter(item=item).all()
或
Player.objects.filter(slots__item=item).all()
取决于您更愿意使用哪个对象。我没有测试上面的任何查询,但这应该可以帮助您入门。另请注意,如果关系不符合您的想法(例如,player
实际上没有 item
),以这种方式链接(查询 1 和 2)可能会抛出 IndexError
,所以准备好抓住它。
迁移旧数据
我相信这样的事情可以完成这项工作:
plist_slots = [
'main_hand', 'off_hand', 'head', 'neck', 'shoulder',
'back', 'chest', 'wrist', 'hands', 'waist', 'legs',
'feet', 'ring1', 'ring2', 'trinket1', 'trinket2'
]
Slot.objects.bulk_create([
Slot(name=slot) for slot in plist_slots
])
plist = ProtectionList.objects.all().select_related(
*list(['player'] + plist_slots)
)
for p in plist:
PlayerSlot.objects.bulk_create([
PlayerSlot(
player=p.player,
item=getattr(p, slot),
slot=Slot.objects.get(name=slot)
) for slot in plist_slots
])