【发布时间】: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
(这里列出标签,因为我没有足够的声誉。)
【问题讨论】:
-
既然这是我的第一个问题,有什么改进的提示吗?
标签: django django-views django-queryset django-q