2011-08-09 55 views
1

我試圖給QuerySet的元素添加一些額外的屬性,所以我可以在模板中使用額外的信息,而不是多次擊中數據庫。讓我通過例子來說明,假設我們有作者的ForeignKeys書籍。有沒有辦法用額外的屬性來擴充django QuerySets?

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

所以添加屬性到查詢集的作品的元素,只要你不使用的東西像反向()。

我目前的解決方法是隻使用select_related()來從數據庫中再次獲取額外的信息,即使我已經在內存中。

有沒有更好的方法來做到這一點?

+0

爲什麼在設置price屬性之前不要調用reverse函數? – MattoTodd

+0

感謝MattoTodd,這就是我最終做的。 – toner

回答

1

錯誤的出現是因爲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關鍵字可以是複雜得多,事實上,它幾乎可以是任何有意義的SQL片段,可以適應[XXX]

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

編輯

爲連接點在其他回覆中排除,此解決方案可能效率低下。

如果您確定從查詢中獲得全部 Book對象,那麼您最好進行一次查詢,將其存儲到一個列表中並最終反轉結果列表。

如果,另一方面,你所得到的「頭」,而該表的「隊列」,使得兩個查詢是更好的,因爲你不會查詢所有的「中間」無用之物。

+0

謝謝,這表明爲什麼我寫的不起作用。 – toner

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() 
+0

謝謝,但我想避免這樣做,因爲我們正在使用基於類的通用視圖,它需要QuerySets而不是列表。 – toner

0

如果在.reverse之前迭代queryset,然後調用reverse並重新生成結果集queryset,那麼將會執行2個不同的SQL查詢,.reverse()方法不會反轉已獲取的結果,它將重新獲取(可能已更改)的結果與另一個SQL查詢。所以你所做的不僅脆弱而且效率低下。

爲了避免第二SQL查詢您可以反轉查詢集迭代之前或反向使用例如在python與模型實例的列表內置「顛倒」功能(請參閱MattoTodd答案)。

3

這是一個老問題,但因爲我需要它最近我將添加我的解決方案。

理想的情況下,我們可以使用某種形式的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可能是有用的,如果你想去更完整的實施

0

這是我的版本alter_items由威爾哈迪提出。

而不是一個值,它允許每個對象的自定義屬性的不同值:您可以通過對象ID傳遞值的映射。

它也自動包裝返回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 wrap(*args, **kwargs): 
      result = method(*args, **kwargs) 
      if isinstance(result, QuerySet): 
       result = self.__class__(result, **self.custom_attrs) 
      return result 
     return wrap 

    def __getattr__(self, name): 
     attr = getattr(self.queryset, name) 
     if isinstance(attr, types.MethodType): 
      return self.__apply(attr) 
     else: 
      return attr 
相關問題