【问题标题】:Perform a logical exclusive OR on a Django Q object对 Django Q 对象执行逻辑异或
【发布时间】:2013-01-20 14:19:03
【问题描述】:

我想对django.db.models.Q 对象执行逻辑异或(XOR),使用operator 模块将模型字段的选择限制为外键的子集。我在 Django 1.4.3 和 Python 2.7.2 中这样做。我有这样的事情:

import operator

from django.conf import settings
from django.db import models
from django.db.models import Q
from django.contrib.auth.models import User, Group

def query_group_lkup(group_name):
    return Q(user__user__groups__name__exact=group_name)

class Book(models.Model):
    author = models.ForeignKey(
                 User,
                 verbose_name=_("Author"),
                 null=False,
                 default='',
                 related_name="%(app_label)s_%(class)s_author",
                 # This would have provide an exclusive OR on the selected group name for User
                 limit_choices_to=reduce(
                     operator.xor,
                     map(query_group_lkup, getattr(settings, 'AUTHORIZED_AUTHORS', ''))
                 )

AUTHORIZED_AUTHORS 是现有组名的列表。

但这不起作用,因为Q 对象不支持^ 运算符(仅来自docs|& 运算符)。来自堆栈跟踪的消息(部分)如下:

File "/home/moi/.virtualenvs/venv/lib/python2.7/site-packages/django/db/models/loading.py", line 64, in _populate
    self.load_app(app_name, True)
  File "/home/moi/.virtualenvs/venv/lib/python2.7/site-packages/django/db/models/loading.py", line 88, in load_app
    models = import_module('.models', app_name)
  File "/home/moi/.virtualenvs/venv/lib/python2.7/site-packages/django/utils/importlib.py", line 35, in import_module
    __import__(name)
  File "/opt/dvpt/toto/apps/book/models.py", line 42, in <module>
    class Book(models.Model):
  File "/opt/dvpt/toto/apps/book/models.py", line 100, in Book
    map(query_group_lkup, getattr(settings, 'AUTHORIZED_AUTHORS', ''))
TypeError: unsupported operand type(s) for ^: 'Q' and 'Q'

因此,受answer 的启发,我尝试为我的特定查找实现 XOR。它不是很灵活,因为查找是硬编码的(例如,我需要在query_xor 的参数中使用kwargs...)。我最终做了这样的事情:

from django.conf import settings
from django.db import models
from django.db.models import Q
from django.db.models.query import EmptyQuerySet
from django.contrib.auth.models import User, Group

def query_xor_group(names_group):
    """Get a XOR of the queries that match the group names in names_group."""

    if not len(names_group):
        return EmptyQuerySet()
    elif len(names_group) == 1:
        return Q(user__user__groups__name__exact=names_group[0])

    q_chain_or = Q(user__user__groups__name__exact=names_group[0])
    q_chain_and = Q(user__user__groups__name__exact=names_group[0])

    for name in names_group[1:]:
        query = Q(user__user__groups__name__exact=name)
        q_chain_or |= query
        q_chain_and &= query

    return q_chain_or & ~q_chain_and

class Book(models.Model):
    author = models.ForeignKey(
                 User,
                 verbose_name=_("author"),
                 null=False,
                 default='',
                 related_name="%(app_label)s_%(class)s_author",
                 # This provides an exclusive OR on the SELECT group name for User
                 limit_choices_to=query_xor_group(getattr(settings, 'AUTHORIZED_AUTHORS', ''))
                 )

它可以按我的意愿工作,但在我看来,我似乎不是 pythonic(尤其是 query_xor_group 方法)。 会有更好(更直接)的方法吗?

基本上,我的问题可以去掉limit_choices_to部分,总结为:

如何以 Djangonic 方式对一组 django.db.models.Q 对象进行按位异或?

【问题讨论】:

    标签: python django django-queryset django-q


    【解决方案1】:

    您可以向 Q 添加一个 __xor__() 方法,该方法使用和/或/不执行 XOR 逻辑。

    from django.db.models import Q
    
    class QQ:
        def __xor__(self, other):    
            not_self = self.clone()
            not_other = other.clone()
            not_self.negate()
            not_other.negate()
    
            x = self & not_other
            y = not_self & other
    
            return x | y
    
    Q.__bases__ += (QQ, )
    

    完成此操作后,我可以在 filter() 通话中使用 Q(...) ^ Q(...)

    Foobar.objects.filter(Q(blah=1) ^ Q(bar=2)) 
    

    这意味着原始尝试不再引发不受支持的操作数异常。

    limit_choices_to=reduce(
                         operator.xor,
                         map(query_group_lkup, getattr(settings, 'AUTHORIZED_AUTHORS', ''))
                     )
    

    Django 1.6.1 Python 2.7.5 上测试

    【讨论】:

    • 真是太好了!谢谢
    • @Dan R:您能否通过完整示例详细说明您的解决方案?
    • 添加该类只会使提问者第一次尝试工作,但我还包括了您在任何过滤器调用中如何使用它。
    • 虽然这确实有效,但我认为最好避免在 python 中进行这样的猴子补丁,因为现在很难跟踪何时应用了这个补丁(虽然,我想你可以看看 @987654329 @ 在 Q.mro())。 IMO,最好将 Q 子类化为 QQ 并改用它;如果这使事情更加一致,您可以全局使用子类。
    • 另外,如果你还没有,你绝对应该向 django 发起一个拉取请求。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-03-25
    • 2015-04-13
    • 2013-05-30
    • 2016-02-02
    • 1970-01-01
    • 2017-02-11
    • 2015-03-14
    相关资源
    最近更新 更多