Django get_next_by_FIELD使用复杂的Q查找

时间:2016-02-24 12:53:22

标签: django django-views django-queryset django-q

在为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)也应该有效,但事实并非如此。

实际问题

使用lookup 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 无法将关键字'size_ne'解析为字段。

然后我尝试使用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()获得参数'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,complex-lookup,query,get_next_by_FIELD,get_previous_by_FIELD

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

2 个答案:

答案 0 :(得分:1)

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

.get_next_by_created(size__ne=4)

答案 1 :(得分:0)

我怀疑你先尝试过的方法只对你所基于get_next的字段进行查询arg。这意味着您将无法从get_next_by_created()方法访问size字段,例如。

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

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

show_admin_bar(false);

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

希望这有帮助!

此致