我正在尝试向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()之类的东西,就可以在QuerySet的元素中添加属性。
我目前的解决方法是使用select_related()再次从数据库中获取我需要的额外信息,即使我已经在内存中。
有更好的方法吗?
答案 0 :(得分: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
可能会很有用
答案 1 :(得分:2)
出现错误是因为qs.reverse()会产生一个新的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
关键字可能要复杂得多,事实上它几乎可以是任何适合[XXX]
SELECT ..., [XXX] as price, ... FROM ... WHERE ... (etc)
修改强>
正如其他回复中所指出的,此解决方案可能效率低下。
如果您确定从查询中获取全部 Book对象,那么您最好进行一次查询,将其存储到列表中并最终反转结果列表。
另一方面,如果您获得表的“头部”和“队列”,则进行两次查询会更好,因为您不会查询所有“中间”无用的对象。
答案 2 :(得分: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()
答案 3 :(得分:0)
如果在.reverse之前迭代查询集,则调用反向并再次迭代生成的查询集,然后执行2个不同的SQL查询,.reverse()方法将不会反转已经获取的结果,它将重新获取(可能已更改)结果与另一个SQL查询。所以你所做的不仅是脆弱而且效率低下。
为了避免第二个SQL查询,您可以在迭代之前反转查询集,或者使用例如python中的模型实例反转列表。内置'逆转'功能(见MattoTodd回答)。
答案 4 :(得分:0)
以下是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