Django get_next_by_FIELD 使用复杂的 Q 查找

Posted

技术标签:

【中文标题】Django get_next_by_FIELD 使用复杂的 Q 查找【英文标题】:Django get_next_by_FIELD using complex Q lookups 【发布时间】:2016-06-06 18:43:06 【问题描述】:

在为 Django 模块创建前端时,我在 Django 核心中遇到了以下问题:

为了从模型查询中显示到下一个/上一个对象的链接,我们可以使用模型实例的extra-instance-methods:get_next_by_FIELD()get_previous_by_FIELD()。其中 FIELD 是 DateField 或 DateTimeField 类型的模型字段。

让我们用一个例子来解释一下

from django.db import models

class Shoe(models.Model):
    created = models.DateTimeField(auto_now_add=True, null=False)
    size = models.IntegerField()

显示鞋子列表的视图,不包括尺寸等于 4 的鞋子:

def list_shoes(request):
    shoes = Shoe.objects.exclude(size=4)

    return render_to_response(request, 
        'shoes': shoes
    )

让下面的视图显示一只鞋和对应的 链接到上一只鞋和下一只鞋。

def show_shoe(request, shoe_id):
    shoe = Shoe.objects.get(pk=shoe_id)

    prev_shoe = shoe.get_previous_by_created()
    next_shoe = shoe.get_next_by_created()

    return render_to_response('show_shoe.html', 
        'shoe': shoe,
        'prev_shoe': prev_shoe,
        'next_shoe': next_shoe
    )

现在我遇到的情况是,无论鞋子大小如何,show_shoe 视图都会显示指向上一个/下一个的链接。但我实际上只想要尺寸不是 4 的鞋子。 因此,如文档所述,我尝试使用 get_(previous|next)_by_created() 方法的 **kwargs 参数来过滤掉不需要的鞋子:

这两种方法都将使用模型的默认管理器执行它们的查询。如果您需要模拟自定义管理器使用的过滤,或者想要执行一次性自定义过滤,这两种方法也都接受 可选关键字参数,应采用字段查找中描述的格式。

编辑:注意“应该”这个词,因为这样 (size_ne=4) 也应该起作用,但它不起作用。

实际问题

使用查找 size__ne 进行过滤 ...

def show_shoe(request, shoe_id):
    ...
    prev_shoe = shoe.get_previous_by_created(size__ne=4)
    next_shoe = shoe.get_next_by_created(size__ne=4)
    ...

... 不起作用,它抛出 FieldError: Cannot resolve keyword 'size_ne' into field.

然后我尝试使用 Q 对象使用否定的complex lookup:

from django.db.models import Q

def show_shoe(request, shoe_id):
    ...
    prev_shoe = shoe.get_previous_by_created(~Q(size=4))
    next_shoe = shoe.get_next_by_created(~Q(size=4))
    ...

... 也不起作用,抛出 TypeError: _get_next_or_previous_by_FIELD() got multiple values for argument 'field'

因为 get_(previous|next)_by_created 方法只接受 **kwargs。

实际解决方案

由于这些实例方法使用 _get_next_or_previous_by_FIELD(self, field, is_next, **kwargs),我将其更改为使用 *args 接受位置参数并将它们传递给过滤器,例如 **kwargs。

def my_get_next_or_previous_by_FIELD(self, field, is_next, *args, **kwargs):
    """
    Workaround to call get_next_or_previous_by_FIELD by using complext lookup queries using
    Djangos Q Class. The only difference between this version and original version is that
    positional arguments are also passed to the filter function.
    """
    if not self.pk:
        raise ValueError("get_next/get_previous cannot be used on unsaved objects.")
    op = 'gt' if is_next else 'lt'
    order = '' if is_next else '-'
    param = force_text(getattr(self, field.attname))
    q = Q(**'%s__%s' % (field.name, op): param)
    q = q | Q(**field.name: param, 'pk__%s' % op: self.pk)
    qs = self.__class__._default_manager.using(self._state.db).filter(*args, **kwargs).filter(q).order_by('%s%s' % (order, field.name), '%spk' % order)
    try:
        return qs[0]
    except IndexError:
        raise self.DoesNotExist("%s matching query does not exist." % self.__class__._meta.object_name)

然后这样称呼它:

...
prev_shoe = shoe.my_get_next_or_previous_by_FIELD(Shoe._meta.get_field('created'), False, ~Q(state=4))
next_shoe = shoe.my_get_next_or_previous_by_FIELD(Shoe._meta.get_field('created'), True, ~Q(state=4))
...

终于做到了。

现在是给你的问题

有没有更简单的方法来处理这个问题? shoe.get_previous_by_created(size__ne=4) 是否应该按预期工作,或者我应该将此问题报告给 Django 人员,希望他们会接受我的 _get_next_or_previous_by_FIELD() 修复?

环境:Django 1.7,尚未在 1.9 上对其进行测试,但 _get_next_or_previous_by_FIELD() 的代码保持不变。

编辑:确实,使用 Q 对象的复杂查找不是“字段查找”的一部分,它更多的是 filter() 和 exclude() 函数的一部分。当我认为 get_next_by_FIELD 也应该接受 Q 对象时,我可能错了。但由于涉及的更改很少,使用 Q 对象的优势很高,我认为这些更改应该在上游进行。

标签:django、复杂查找、查询、get_next_by_FIELD、get_previous_by_FIELD

(这里列出标签,因为我没有足够的声誉。)

【问题讨论】:

既然这是我的第一个问题,有什么改进的提示吗? 【参考方案1】:

我怀疑您首先尝试的方法只需要查找 arg 您作为 get_next 基础的字段。例如,这意味着您将无法通过 get_next_by_created() 方法访问 size 字段。

编辑 :您的方法效率更高,但要回答您关于 Django 问题的问题,我认为一切都按预期的方式工作。您可以提供其他方法,例如您的方法,但现有的 get_next_by_FIELD 正在按照文档中的说明工作。

你已经设法用一种工作方法解决了这个问题,我猜这没问题,但如果你想减少开销,你可以尝试一个简单的循环:

def get_next_by_field_filtered(obj, field=None, **kwargs):

    next_obj = getattr(obj, 'get_next_by_'.format(field))()

    for key in kwargs:
        if not getattr(next_obj, str(key)) == kwargs[str(key)]:
            return get_next_by_field_filtered(next_obj, field=field, **kwargs)

    return next_obj

这不是很有效,但它是做你想做的事情的一种方式。

希望这会有所帮助!

问候,

【讨论】:

是的,循环也可以,但是我不太喜欢它,但你的解决方案看起来更好。 而且我不确定循环的计算/数据库开销是否低于使用修改后的 my_get_next_or_previous_by_FIELD,因为每次调用 get_next_by_FIELD 都需要访问D B。这就是为什么我想使用 size_ne 或 Q 对象来解决这个问题,这样只需一个数据库访问即可提供所需的结果。 是的,你完全正确,你的方法非常有效:)【参考方案2】:

您可以创建custom lookup ne 并使用它:

.get_next_by_created(size__ne=4)

【讨论】:

创建自己的查找作品!但是为什么不能开箱即用地使用 size__ne 呢?因为它“应该”表现得像其他字段查找一样。 因为默认没有ne查找。

以上是关于Django get_next_by_FIELD 使用复杂的 Q 查找的主要内容,如果未能解决你的问题,请参考以下文章

Django初识

Django之路

Django系列

django 错误

mac电脑安装django ,运行django报错解决

Django 大神带你飞系列~走进Django