有没有办法用额外的属性来增强 django 查询集?
我正在尝试向 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.
因此,只要不使用诸如reverse()之类的东西,向查询集的元素添加属性就可以了。
我当前的解决方法是仅使用 select_lated() 再次从数据库中获取我需要的额外信息,即使我已经在内存中了。
有更好的方法吗?
I'm trying to add some extra attributes to the elements of a QuerySet so I can use the extra information in templates, instead of hitting the database multiple times. Let me illustrate by example, assuming we have Books with ForeignKeys to 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.
So adding attributes to the elements of a QuerySet works, so long as you don't use things like reverse().
My current workaround is to just use select_related() to fetch the extra information I need from the database again, even though I already have in memory.
Is there a better way to do it?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
这是一个老问题,但我会添加我的解决方案,因为我最近需要它。
理想情况下,我们可以为
QuerySet
使用某种代理对象。然后,我们的代理版本将在迭代过程中进行更改。很难涵盖所有可能的场景,
QuerySet
对象有点复杂,并且有多种不同的使用方式。但对于由于发送到模板(或通用视图)而在最后一刻添加属性的简单情况,以下操作可能有效:然后像这样使用它:
因为它不是真正的代理,您可能需要进一步做根据使用方式进行修改,但这是粗略的方法。如果有一种简单的方法可以用新样式的类在 Python 中创建代理类,那就太好了。如果您想要更完整的实现,外部库
wrapt
可能会很有用This is an old question, but I'll add my solution because I needed it recently.
Ideally we could use some sort of proxy object for the
QuerySet
. Our proxy version would then make the changes during iteration.It will be hard to cover all possible scenarios,
QuerySet
objects are a little complicated and are used in many different ways. But for the simple case of adding an attribute at the last minute because sending to a template (or generic view), the following might work:And then use this like so:
Because it is not a true proxy, you may need to make further modifications depending on how it is used, but this is the rough approach. It would be nice if there were an easy way to make a proxy class in Python with new style classes. The external library
wrapt
might be useful if you want to go with a more complete implementation出现错误是因为 qs.reverse() 产生了一个新的 QuerySet 实例,因此您没有反转旧的实例。
如果您想要一个基本的 QS 来执行操作,您可以执行以下操作:
当然
select
关键字可以复杂得多,事实上它可以是几乎任何可以适合的有意义的 SQL 片段 中的[XXX]
EDIT
正如其他回复中所指出的,此解决方案可能效率低下。
如果您确定从查询中获取所有 Book 对象,那么您最好进行一个查询,将其存储到一个列表中,并最终反转结果列表。
另一方面,如果您要获取表的“头”和“队列”,则进行两个查询会更好,因为您不会查询所有“中间”无用的对象。
The error arises because qs.reverse() give rise to a new QuerySet instance, so you are not reversing the old one.
If you want to have a base QS on which to act you can do the following:
Of course the
select
keyword can be much more complex, in fact it can be almost any any meaningful SQL snippet that could fit the[XXX]
inEDIT
As pointed out in other responses, this solution may be inefficient.
If you are sure to get all the Book objects from the query, then you 'd better make one query, store it into a list and eventually reverse the resulting list.
If, on the other hand, you are getting the "head" and the "queue" of the table, making two queries is better, because you won't query all the "middle" useless objects.
我假设在查询集上调用 .reverse 是什么导致了您的问题。试试这个:
I would assume calling .reverse on a queryset s what is causing your issues. try this:
如果您在 .reverse 之前迭代查询集,然后调用反向并再次迭代结果查询集,那么将执行 2 个不同的 SQL 查询,.reverse() 方法不会反转已获取的结果,它将重新获取(可能已更改) ) 另一个 SQL 查询的结果。所以你所做的不仅脆弱而且效率低下。
为了避免第二个 SQL 查询,您可以在迭代之前反转查询集,或者使用内置的“reversed”函数反转 python 中模型实例的列表(请参阅 MattoTodd 答案)。
If you iterate queryset before .reverse, then call the reverse and iterate the resulting queryset again then there will be 2 different SQL queries executed, .reverse() method won't reverse already fetched results, it will re-fetch the (possibly changed) results with an another SQL query. So what you're doing is not only fragile but also inefficient.
In order to avoid the second SQL query you can either reverse the queryset before iterating it or reverse a list with model instances in python using e.g. builtin 'reversed' function (see MattoTodd answer).
这是 Will Hardy 提出的我的
alter_items
版本。它允许每个对象使用不同的自定义属性值,而不是单个值:您可以按对象 ID 传递值的映射。
它还自动包装所有返回 QuerySet 的方法的结果。
Here is my version of
alter_items
proposed by Will Hardy.Instead of a single value it allows different values of a custom attribute for each object: you can pass a mapping of values by object id.
It also automatically wraps the results of all methods that return
QuerySet
.