有没有办法为django查询集添加额外属性?

4 投票
5 回答
5493 浏览
提问于 2025-04-16 23:13

我想给一个查询结果中的元素添加一些额外的属性,这样我就可以在模板中使用这些额外的信息,而不是多次去数据库查询。举个例子,假设我们有书籍(Books),每本书都有一个指向作者(Authors)的外键。

>>> 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.

所以,只要不使用像 reverse() 这样的东西,给查询结果中的元素添加属性是可行的。

我现在的解决办法是使用 select_related() 再次从数据库中获取我需要的额外信息,尽管这些信息我已经在内存中了。

有没有更好的方法呢?

5 个回答

0

我猜测你在查询集上调用 .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()
4

这是一个老问题,但我最近需要这个解决方案,所以我来分享一下。

理想情况下,我们可以为 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 可能会很有用。

2

这个错误出现的原因是因为 qs.reverse() 会生成一个新的查询集实例,所以你并没有反转原来的那个。

如果你想要一个基础的查询集来进行操作,可以这样做:

>>> 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 关键字可以更复杂,实际上它可以是任何适合 [XXX] 的有意义的 SQL 片段。

SELECT ..., [XXX] as price, ... FROM ... WHERE ... (etc)

编辑

正如其他回答中提到的,这个解决方案 可能 效率不高。

如果你确定要从查询中获取所有的书籍对象,那么最好只进行一次查询,把结果存入一个列表,然后再反转这个列表。

另一方面,如果你只想获取表的“头部”和“尾部”,那么进行两次查询会更好,因为这样就不会查询到所有“中间”的无用对象。

撰写回答