Django在现有QuerySet前面添加对象

2024-10-01 11:27:18 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在构建一个libraryDjango应用程序,其中我有一个BooksView来显示由某个Author编写的所有Book。在某些条件下,我希望在查询集中的所有其他书籍之前显示一个Book。这应该在图书id出现在URL中时发生,否则,所有图书都应该按时间顺序列出

书籍/url.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()),
]

书籍/视图.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)

我如何完成我的待办事项?如何将对象推送到现有查询集的前面


Tags: inpyidgetorderbooksauthor书籍
3条回答

您可以使用按位|运算符作为

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(...)方法,位置将根据表达式的顺序进行更改

更新1

因为您使用的是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方法,并排除 我们试图从booksqueryset访问的书

我对代码做了一些小的修改,但是您不需要做更多的事情,就可以完成您所需要的

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)

这并不能准确地回答您的问题,但我认为这将允许您获得期望的结果:不要担心保留queryset,而是将其转换为列表

对于整个代码库中的每个查询集,这不是您想要做的事情,特别是如果您以后可能希望在查询集上构建并使用其他过滤器、排除项等组合查询集,那么更是如此。但是,当您准备呈现页面时(就像您在这里),这是可以的,因为您将以任何方式对查询集进行评估。你让它发生的时间可能会提前几微秒

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)

编辑1

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

编辑2

试试这个怎么样?通过在查询集被计算到列表中之前添加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)

相关问题 更多 >