在Django中针对另一个相关模型的M2M关系过滤相关字段

Oli*_*Oli 10 django django-models django-queryset

所以我有一个预订系统.代理商(提交预订的人员和组织)仅允许在我们分配的类别中进行预订.许多代理可以分配到相同的类别.这是一个简单的多对多.以下是模型的概念:

class Category(models.Model):
    pass

class Agent(models.Model):
    categories = models.ManyToManyField('Category')

class Booking(models.Model):
    agent = models.ForeignKey('Agent')
    category = models.ForeignKey('Category')
Run Code Online (Sandbox Code Playgroud)

因此,当预订进入时,我们会根据代理商可用的类别动态分配类别.代理通常不指定.

我可以选择Booking.agent.categories中不包含Booking.category的预订吗?

我们刚刚注意到 - 由于一个愚蠢的管理员错误的优雅 - 一些代理人被允许提交任何类别的预订.它让我们在错误的地方有成千上万的预订.

我可以解决这个问题,但我只能通过嵌套查找来实现它:

for agent in Agent.objects.all():
    for booking in Booking.objects.filter(agent=agent):
        if booking.category not in agent.categories.all():
            # go through the automated allocation logic again
Run Code Online (Sandbox Code Playgroud)

这可行,但它超级慢.这是数据库和Django之间的大量数据.这也不是一次性的.我想定期审核新的预订,以确保它们在正确的位置.在检查代理数据库之后,似乎不会发生另一个管理问题,我想查询不在其代理的类别中的预订.

同样,嵌套查询不会起作用,但随着我们的数据集增长到数百万(甚至更多)我想更有效地做到这一点.

我觉得应该可以通过F()查找来执行此操作,如下所示:

from django.db.models import F
bad = Booking.objects.exclude(category__in=F('agent__categories'))
Run Code Online (Sandbox Code Playgroud)

但这不起作用: TypeError: 'Col' object is not iterable

我也尝试过.exclude(category=F('agent__categories')),虽然它的语法更快乐,但它并没有排除"正确"的预订.

F()在M2M上进行此类查询的秘密公式是什么?


为了帮助确定我在使用这些模型(以及一些数据)设置Github仓库后的确切内容.请用它们来编写查询.当前唯一的答案点击和问题我也看到了我的"真实"数据.

git clone https://github.com/oliwarner/djangorelquerytest.git
cd djangorelquerytest
python3 -m venv venv
. ./venv/bin/activate
pip install ipython Django==1.9a1

./manage.py migrate
./manage.py shell
Run Code Online (Sandbox Code Playgroud)

在壳中,火:

from django.db.models import F
from querytest.models import Category, Agent, Booking
Booking.objects.exclude(agent__categories=F('category'))
Run Code Online (Sandbox Code Playgroud)

那是一个错误吗?有没有正确的方法来实现这一目标?

leh*_*ins 6

我有可能错了,但我认为反过来应该这样做:

bad = Booking.objects.exclude(agent__categories=F('category'))

编辑

如果上面不起作用,这是另一个想法.我在设置上尝试了类似的逻辑,似乎有效.尝试添加中间模型ManyToManyField:

class Category(models.Model):
    pass

class Agent(models.Model):
    categories = models.ManyToManyField('Category', through='AgentCategory')

class AgentCategory(models.Model):
    agent = models.ForeignKey(Agent, related_name='agent_category_set')
    category = models.ForeignKey(Category, related_name='agent_category_set')

class Booking(models.Model):
    agent = models.ForeignKey('Agent')
    category = models.ForeignKey('Category')
Run Code Online (Sandbox Code Playgroud)

然后你可以做一个查询:

bad = Booking.objects.exclude(agent_category_set__category=F('category'))
Run Code Online (Sandbox Code Playgroud)

当然,指定一个中间模型有它自己的含义,但我相信你可以处理它们.