有没有办法为django查询集添加额外属性?
我想给一个查询结果中的元素添加一些额外的属性,这样我就可以在模板中使用这些额外的信息,而不是多次去数据库查询。举个例子,假设我们有书籍(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 个回答
我猜测你在查询集上调用 .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()
这是一个老问题,但我最近需要这个解决方案,所以我来分享一下。
理想情况下,我们可以为 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
可能会很有用。
这个错误出现的原因是因为 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)
编辑
正如其他回答中提到的,这个解决方案 可能 效率不高。
如果你确定要从查询中获取所有的书籍对象,那么最好只进行一次查询,把结果存入一个列表,然后再反转这个列表。
另一方面,如果你只想获取表的“头部”和“尾部”,那么进行两次查询会更好,因为这样就不会查询到所有“中间”的无用对象。