[英]Django - Add object in front of existing QuerySet
我正在構建一個library
Django 應用程序,其中我有一個BooksView
來顯示某個Author
編寫的所有Book
。 在某些情況下,我想一個單一的Book
之前在查詢集中的所有其他書來顯示。 當圖書的 id 出現在 URL 中時,就會發生這種情況,否則,所有圖書都應按時間順序列出。
書籍/urls.py :
urlpatterns = [
# Books displayed in chronological order
path("<slug:author>/books", views.BooksView.as_view()),
# Books displayed in chronological order
# but first book is the one with id `book`
path("<slug:author>/books/<int:book>", views.BooksView.as_view()),
]
書籍/views.py :
class BooksView(APIView):
def get(self, request, author, book=None):
author = get_object_or_404(Author, slug=author)
books = author.author_books
books = books.order_by("-pub_date")
if book:
book = get_object_or_404(Book, id=book)
# TODO: add `book` in front of books QuerySet (and remove duplicated if any)
serializer = BooksSerializer(books, many=True)
return Response(serializer.data)
我怎樣才能完成我的TODO ? 如何將對象推送到現有查詢集的前面?
這並不能准確回答您的問題,但我認為它可以讓您獲得所需的結果:不要擔心保留查詢集,而是將其轉換為列表。
這不是您想要為整個代碼庫中的每個查詢集做的事情,尤其是如果它是您以后可能想要構建並與其他過濾器、排除等組合的查詢集。 但是當您即將呈現頁面時(就像你在這里一樣)沒關系,因為無論如何你都會導致查詢集被評估。 你導致它發生的時間可能會提前幾微秒。
class BooksView(APIView):
def get(self, request, author, book=None):
author = get_object_or_404(Author, slug=author)
books = list(author.author_books)
if book:
book = Book.objects.get(id=book)
# TODO: add `book` in front of books QuerySet (and remove duplicated if any)
books = [x for x in books if not x == book] # first remove if dup
books.insert(0, book) # now insert at front
serializer = BooksSerializer(books, many=True)
return Response(serializer.data)
BooksSerializer(我懷疑它是 BaseSerializer 的子類)無論如何都會在您調用它時列出一個列表:
def to_representation(self, data):
"""
List of object instances -> List of dicts of primitive datatypes.
"""
# Dealing with nested relationships, data can be a Manager,
# so, first get a queryset from the Manager if needed
iterable = data.all() if isinstance(data, models.Manager) else data
return [
self.child.to_representation(item) for item in iterable
]
https://github.com/encode/django-rest-framework/blob/master/rest_framework/serializers.py#L663
試試這個怎么樣? 通過在查詢集被評估到列表之前添加exclude
,您可以防止 O(n) 掃描列表以查找和刪除應該位於頂部的“主要”書籍。
class BooksView(APIView):
def get(self, request, author, book=None):
author = get_object_or_404(Author, slug=author)
books = author.author_books
if book:
book = Book.objects.get(id=book)
# TODO: add `book` in front of books QuerySet (and remove duplicated if any)
# ensure the queryset doesn't include the "main" book
books = books.exclude(book_id=book.id)
# evaluate it into a list just like the BookSerializer will anyhow
books = list(books)
# now insert the "main" book at the front of the list
books.insert(0, book)
serializer = BooksSerializer(books, many=True)
return Response(serializer.data)
您可以使用按位|
運營商作為
from django.http.response import Http404
class BooksView(APIView):
def get(self, request, author, book=None):
author = get_object_or_404(Author, slug=author)
book_qs = author.author_books
if book:
single_book_qs = Book.objects.filter(id=book) if not single_book_qs.exists(): raise Http404 book_qs = single_book_qs | book_qs
serializer = BooksSerializer(book_qs, many=True)
return Response(serializer.data)
請注意,此解決方案的一個警告是,如果您使用order_by(...)
方法,位置將根據 ORDER By 表達式進行更改。
由於您使用的是order_by(...)
表達式,因此您必須執行以下操作,
class BooksView(APIView):
def get(self, request, author, book=None):
author = get_object_or_404(Author, slug=author)
book_qs = author.author_books.order_by("-pub_date")
serialized_single_book = []
if book:
single_book = get_object_or_404(Book, id=book)
book_qs.exclude(id=book) # to remove dups serialized_single_book = [BooksSerializer(single_book).data]
serializer = BooksSerializer(book_qs, many=True)
serialized_book_qs = serializer.data
return Response([*serialized_single_book, *serialized_book_qs])
應該這樣做,只需使用QuerySet
對象中的 union 方法,並從books
集中排除我們嘗試訪問的books
。
我對代碼做了一些小的改動,但你不需要做更多的事情來完成你需要的。
class BooksView(APIView):
def get(self, request, author, book=None):
author = get_object_or_404(Author, slug=author)
books = author.author_books.all().order_by('-pub_date')
if book:
# I am assuming this line if for validating that the request is valid.
# I've changed the query to also include author's slug,
# so the request doesn't get books not related to the author.
book_obj = get_object_or_404(Book, id=book, author__slug=author)
# Just Query the same book, and union it with the books queryset,
# excluding the current book in the book_obj
books = Book.objects.filter(id=book_obj.id).union(books.exclude(id=book_obj.id))
serializer = BooksSerializer(books, many=True)
return Response(serializer.data)
可能有點開箱即用,但如果您想將其保留為查詢集,那么這就是解決方案。 請注意,這解決了X 問題,而不是 Y 問題。 目標是將一本書放在列表的最前面,這可以通過插入來實現,也可以通過基於注釋重新排序來實現。
from django.db.models import Case, When, Value, IntegerField
from rest_framework import generics
from . import models, serializers
class BooksPerAuthor(generics.ListAPIView):
serializer_class = serializers.BookWithoutAuthor
def get_queryset(self):
book_pk = self.kwargs.get("book", 0)
queryset = (
models.Book.objects.filter(author__slug=self.kwargs["author"])
.annotate(
promoted=Case(
When(pk=book_pk, then=Value(1)),
default=Value(0),
output_field=IntegerField(),
)
)
.order_by("-promoted", "-pub_date")
)
return queryset
這不會捕獲不存在的引用 - 不喜歡為列表視圖返回 404 並且如果升級的書不在集合中,那么就沒有真正的傷害。 上面的大量示例以額外查詢的(較小)成本使用 404 執行相同的操作。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.