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 查找的主要内容,如果未能解决你的问题,请参考以下文章