python - 查询集的高效逆向

标签 python django django-queryset models

我正在尝试寻找最有效的方法来遍历我的模型以获取我想要的数据。我有三个“相关”模型。 ItemProtectionListPlayer

保护名单

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,以根据谁在任何字段中拥有 Item 来创建 Player 对象列表在他们的ProtectedList上。

我知道ItemProtectionList相关,ProtectionListPlayer相关,但我想找到一种有效的方法检查 ItemProtectionList 对象中的外键的所有字段,因为被过滤的 Item 可能位于任何字段中(减去playerlocked 字段)。

我已经考虑过使用 Q 来或每个字段,但我不确定这在较大的查询集上有多优化。

我还在查看中间表并将模型更改为多对多。

最佳答案

我可能是错的,但我觉得你的模型结构没有标准化。您通过 SlotsPlayerItem 之间建立 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', '<a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="6a060f040405042a1e020f080f0b1e060f1944090507" rel="noreferrer noopener nofollow">[email protected]</a>', 'johnpassword')
>>> user2 = User.objects.create_user('paul', '<a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="f9949a9a988b8d979c80b98d919c9b9c988d959c8ad79a9694" rel="noreferrer noopener nofollow">[email protected]</a>', '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>]

两位炼金术士提供的更多示例:

以下是此模型结构的一些示例查询:

  1. 您有一个 Player 对象 player 并且需要知道他/她的左手拿的是什么:

    player.items.filter(slot__name='off_hand')[0].item    # Could be None
    
  2. 您有一个 Item 对象 item,并希望知道 player 将其装备在哪个插槽中:

    PlayerSlot.objects.filter(player=player).filter(item=item)[0].slot.name
    
  3. 您有一个 Item 对象 item 并且希望知道哪些玩家拥有该元素:

    PlayerSlot.objects.filter(item=item).all()
    

    Player.objects.filter(slots__item=item).all()

取决于您想使用哪个对象。我尚未测试上述任何查询,但这应该可以帮助您入门。另请注意,如果关系未按照您的想法排列(例如,player 实际上没有item),因此请做好可能捕获这一点的准备。

迁移旧数据

我相信这样的东西可以完成这项工作:

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
    ])

关于python - 查询集的高效逆向,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28510022/

相关文章:

django - 在 Django 中使用一个查询获取多行?

python - 在运行时指定 Django 查询过滤器

python - Python 何时在读取文件时解码字节字符串?

mysql - django + 自带数据库切换MySQL报错

ruby-on-rails - 实现 Web API 请求 throttle/速率限制的常用方法有哪些?

Django 查询集更新方法从查询集中删除对象

Python - 正则表达式 - 查找所有重复项

python - 属性错误 : 'str' object has no attribute 'loads' , json.loads()

python - 从 "mysite.app"中的 INSTALLED_APPS 中删除 mysite 前缀可以防止双重导入,为什么?

django - 还有另一种方法可以在 Django 中生成 api key 吗?