Django - 过滤相关的模型自定义查询

时间:2013-10-09 17:10:39

标签: django django-models

我有三个模型:PlayerLeagueLeaguePlayerLeaguePlayerPlayer具有外键关系,与League具有外键关系。 Player有几个自定义查询,例如:

Player.objects.by_position('catcher')

Player.objects.by_position(position='batter', exclude='catcher')

Player.objects.by_best_position('shortstop')

我希望能够在对Player进行过滤时使用LeaguePlayer自定义查询。示例可能是:LeaguePlayer.objects.by_position('catcher'),它将使用Player自定义过滤器by_position。因此,如果LeaguePlayer有一个字段player_value,我想做类似的事情:

LeaguePlayer.objects.by_position('catcher').filter('player_value__gt'=100)

如果没有办法以某种方式“继承”来自Player LeaguePlayer的过滤器?或者如果没有,是否有不同的方式来组织我的模型,因此这种类型的过滤可能很容易创建?

@yuvi,我的问题背后的一些代码:

from django.db import models
from model_utils.managers import PassThroughManager
from player.models import Positions

class League(models.Model):
    name = models.CharField(verbose_name="League name", max_length=50, unique=True)
    number_of_teams = models.IntegerField(
        verbose_name="Number of teams in your league",
        choices=[(i, i) for i in range(6, 19)],
        blank=False, default=10
    )
    ...

class PlayerQuerySet(models.query.QuerySet):
    def exclude_position(self, exclude=None):
        non_excluded_positions = [...]
        return self.filter(all_positions__in=non_excluded_positions).distinct()

    def by_position(self, positions=None, exclude=None):
        player_set = self
        if exclude is not None:
            player_set = self.exclude_position(exclude)

        if positions is None:
            return player_set.distinct()

        return player_set.filter(all_positions__in=positions).distinct()

class PlayersManager(PassThroughManager):
    def get_query_set(self):
        return PlayerQuerySet(self.model, using=self._db)

class Players(models.Model):
    name = models.CharField(max_length=30)
    primary_position = models.ForeignKey(Positions, related_name='primary')
    all_positions = models.ManyToManyField(Positions, related_name='positions')

    objects = PlayersManager()

class LeaguePlayerQuerySet(models.query.QuerySet):
    def custom_query(self):
        return some filtered version of self

class LeaguePlayerManager(PassThroughManager):
    def get_query_set(self):
        return LeaguePlayerQuerySet(self.model, using=self._db)

class LeaguePlayer(models.Model):
    league = models.ForeignKey(League)
    player = models.ForeignKey(Players)
    player_value_property = models.FloatField(null=True)
    ...

    objects = LeaguePlayerManager()

    @property
    def player_value(self):
        if not self.player_value_property:
            self.player_value_property = calculate value based on self.league and self.player
            self.save()
        return self.player_value_property

有关PassThroughManager的详细信息,请访问:https://bitbucket.org/carljm/django-model-utils/overview。简而言之,它允许链接自定义QuerySet方法。

2 个答案:

答案 0 :(得分:2)

我看到你的代码遇到的大多数问题都是你的数据库设计,这太复杂而且令人困惑,并且需要做很多清理工作。 良好的数据库设计意味着简单而合理的查询。 QuerySet的子类化对我来说看起来有些过分,但这部分很有意义并且写得很好

如此简短的回答:

使用LeaguePlayer作为玩家和联盟之间的中间表,并将player_value属性移动到玩家:

class Player(models.Model):
    league = models.ManyToManyField(League, through='LeaguePlayer')

    @property
    def player_value(self): 
        return ( 'calculation based on self.league and self' )

可能更好地命名它,因为你已经引用了一个玩家对象

然后查询看起来像其他查询而不改变事物:

Player.objects.by_position('catcher').filter('player_value__gt'=100)

答案很长:

  1. 如果LeaguePlayer不包含任何其他信息,我认为没有理由保留它。即使没有“通过”,它仍然可以作为玩家和联盟之间的中间表,但是具有不必要的字段,以及作为计算的属性(填充相同的字段)。如果这就是全部,那就完全删除它。

  2. 将所有模型名称从复数更改为单个。这是(明智的)惯例

  3. 您不应该两次将玩家链接到位置。似乎更有意义的是链接它一次并找到一些方法来识别主要位置作为一种方法(使用另一个中间表或使用一个名为'primary'的布尔字段作为位置。我不知道,对你来说什么都有意义)。

  4. 考虑到这些变化,播放器模型应该看起来像这样:

    class Player(models.Model):
        name = models.CharField(max_length=30)
        positions = models.ManyToManyField(Position)
        league = models.ManyToManyField(League)
    
        def primary_position(self):
            return self.positions.all().filter(primary=True)
    
        @property
        def player_value(self): 
            return ( 'calculation based on self.league and self' )
    

    希望这有帮助,祝你好运!

    P.S。请记住,计算不应保存在数据库中。这就是

    的方法

答案 1 :(得分:1)

这样的东西
catchers = Player.objects.by_position('catcher').values_list('id', flat = True)
leagueCatchers = LeaguePlayer.objects.filter(player__in = catchers).filter('player_value__gt'=100)