1

我有三个模型:PlayerLeagueLeaguePlayerLeaguePlayer与 有外键关系,Player与 有外键关系LeaguePlayer有几个自定义查询,例如:

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)

如果没有办法以某种方式“继承”过滤器Playerfor 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 方法。

4

2 回答 2

2

我在您的代码中看到的大部分问题是您的数据库设计,这太复杂和令人困惑,并且有很多清理工作要做。良好的数据库设计意味着简单而合乎逻辑的查询。QuerySet 的子类化对我来说似乎有点矫枉过正,但这部分是有道理的,而且写得很好

这么简短的回答:

使用 LeaguePlayer 作为 Player 和 League 之间的中间表,并将 player_value 属性移动到 Player:

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

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

可能更好地将其命名为 value 因为您已经在引用一个播放器对象

然后查询看起来像其他查询而不改变任何东西:

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

长答案:

  1. 如果 LeaguePlayer 不包含更多信息,我认为没有理由保留它。即使没有“通过”,它仍然可以作为 Player 和 League 之间的中间表,但它有一个不必要的字段和一个计算属性(填充相同的字段)。如果仅此而已,只需将其完全删除即可。

  2. 将所有型号名称从复数更改为单数。那是(明智的)约定

  3. 您不应该将 Player 链接到 Position 两次。将其链接一次并找到某种方法将主要职位识别为一种方法似乎更有意义(使用另一个中间表或使用称为“主要”的布尔字段作为职位。我不知道,无论对你有意义)。

考虑到这些变化,Player 模型应该看起来像这样:

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

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

ps 请记住,计算不应保存在数据库中。这就是方法的用途

于 2013-10-09T21:44:24.667 回答
1

像这样的东西怎么样

catchers = Player.objects.by_position('catcher').values_list('id', flat = True)
leagueCatchers = LeaguePlayer.objects.filter(player__in = catchers).filter('player_value__gt'=100)
于 2013-10-09T17:31:31.063 回答