是否有一种方法可以用额外的属性来增强django QuerySets ?



我试图向QuerySet的元素添加一些额外的属性,这样我就可以在模板中使用额外的信息,而不是多次击中数据库。让我举个例子来说明,假设我们有作者的外键的书。

>>> books = Book.objects.filter(author__id=1)
>>> for book in books:
...     book.price = 2  # "price" is not defined in the Book model
>>> # Check I can get back the extra information (this works in templates too):
>>> books[0].price
2
>>> # but it's fragile: if I do the following:
>>> reversed = books.reverse()
>>> reversed[0].price
Traceback (most recent call last):
  ...
AttributeError: 'Book' object has no attribute 'price'
>>> # i.e., the extra field isn't preserved when the QuerySet is reversed.

所以给QuerySet的元素添加属性是有效的,只要你不使用reverse()之类的东西。

我目前的解决方案是使用select_related()来获取额外的信息,我需要从数据库再次,即使我已经在内存中。

有更好的方法吗?

这是一个老问题,但我将添加我的解决方案,因为我最近需要它。

理想情况下,我们可以为QuerySet使用某种代理对象。我们的代理版本将在迭代期间进行更改。

很难涵盖所有可能的场景,QuerySet对象有点复杂,并且以许多不同的方式使用。但是对于由于发送到模板(或泛型视图)而在最后一刻添加属性的简单情况,下面的方法可能有效:

class alter_items(object):
  def __init__(self, queryset, **kwargs):
    self.queryset = queryset
    self.kwargs = kwargs
  # This function is required by generic views, create another proxy
  def _clone(self):
    return alter_items(queryset._clone(), **self.kwargs)
  def __iter__(self):
    for obj in self.queryset:
      for key, val in self.kwargs.items():
        setattr(obj, key, val)
      yield obj

然后像这样使用:

query = alter_items(Book.objects.all(), price=2)

因为它不是一个真正的代理,您可能需要根据它的使用方式做进一步的修改,但这是一个粗略的方法。如果有一种简单的方法可以用新的样式类在Python中创建代理类,那就太好了。外部库wrapt可能是有用的,如果你想去更完整的实现

出现这个错误是因为q .reverse()产生一个新的QuerySet实例,所以您没有反转旧的QuerySet实例。

如果你想有一个基本的QS,你可以这样做:

>>> augmented_books = Book.objects.extra(select={'price': 2))
>>> augmented_books[0].price
2
>>> augmented_books_rev = augmented_books.reverse()
>>> augmented_books_rev[0].price
2
当然,select关键字可以更复杂,事实上,它可以是几乎任何任何有意义的SQL片段,可以适合[XXX]
SELECT ..., [XXX] as price, ... FROM ... WHERE ... (etc)

编辑

正如在其他回复中指出的,此解决方案可能效率低下。

如果您确定从查询中获得所有 Book对象,那么您最好进行一次查询,将其存储到一个列表中,并最终反转结果列表。

另一方面,如果您正在获取表的"头部"one_answers"队列",则进行两次查询更好,因为您不会查询所有"中间"无用的对象。

我认为在查询集上调用。reverse是导致您的问题的原因。试试这个:

books = Book.objects.filter(author__id=1)
books_set = []
for book in books:
    book.price = 2
    books_set.append(book)
reverse = books_set.reverse()

如果你在。reverse之前迭代queryset,然后调用reverse并再次迭代结果queryset,那么将会执行2个不同的SQL查询,.reverse()方法不会反转已经获取的结果,它将使用另一个SQL查询重新获取(可能更改的)结果。所以你所做的不仅脆弱而且效率低下。

为了避免第二个SQL查询,您可以在迭代之前反转查询集,或者在python中使用例如内置的'reversed'函数反转带有模型实例的列表(参见MattoTodd回答)。

这是Will Hardy提出的我的alter_items版本。

它允许每个对象的自定义属性的不同值,而不是单个值:您可以通过对象id传递值的映射。

它还自动包装返回QuerySet的所有方法的结果。

import types
from itertools import islice
from django.db.models import Model, QuerySet

class QuerySetWithCustomAttributes(object):
    def __init__(self, queryset, **custom_attrs):
        self.queryset = queryset
        self.custom_attrs = custom_attrs
    def __set_custom_attrs(self, obj):
        if isinstance(obj, Model):
            for key, val in self.custom_attrs.items():
                setattr(obj, key, val[obj.id])
        return obj
    def __iter__(self):
        for obj in self.queryset:
            yield self.__set_custom_attrs(obj)
    def __getitem__(self, ndx):
        if type(ndx) is slice:
            return self.__class__(self.queryset.__getitem__(ndx), **self.custom_attrs)
        else:
            return self.__set_custom_attrs(next(islice(self.queryset, ndx, ndx + 1)))
    def __len__(self):
        return len(self.queryset)
    def __apply(self, method):
        def apply(*args, **kwargs):
            result = method(*args, **kwargs)
            if isinstance(result, QuerySet):
                result = self.__class__(result, **self.custom_attrs)
            elif isinstance(result, Model):
                self.__set_custom_attrs(result)
            return result
        return apply
    def __getattr__(self, name):
        attr = getattr(self.queryset, name)
        if isinstance(attr, types.MethodType):
            return self.__apply(attr)
        else:
            return attr

相关内容

  • 没有找到相关文章

最新更新