Thinker
Thinker

Reputation: 5366

Django url with query params and single view to retrieve different set of objects

I have a model Book:

class Book(models.Model):
    book_id = models.AutoField(primary_key=True)
    title = models.CharField(max_length=30)
    # A book can have multiple authors and an author can have multiple books
    author = models.ManyToManyField('Author')
    genre = models.CharField(max_length=20)
    isbn = models.CharField(max_length=20)
    summery = models.CharField(max_length=100, null=True)
    language = models.CharField(max_length=20, null=True)
    status = models.CharField(max_length=15, null=True)
    number_of_pages = models.IntegerField(blank=True)
    borrowed = models.DateField(auto_now=False, auto_now_add=False, null=True, blank=True)
    loan_period = models.IntegerField(default=14)
    due_date = models.DateField(auto_now=False, auto_now_add=False, null=True, blank=True)
    # A library has many books
    which_library = models.ForeignKey('Library', related_name='books', on_delete=models.CASCADE)

    #This helps to print in admin interface
    def __str__(self):
        return u"%s" % (self.title)

I have one url that gives me all the books in a specific library:

urlpatterns = [
    url(r'^books/(?P<library_id>[0-9]+)$', book_list),
]

My view that returns those book objects:

@api_view(['GET', 'POST'])
def book_list(request, library_id):
    """
    List all books in a specific library, or create a new book for a specific library
    """
    if request.method == 'GET':
        books = Book.objects.filter(which_library=library_id)
        print(books)
        serializer = BookSerializer(books, many=True)
        return Response(serializer.data)

My question is: can I and if yes how can I modify my url and view so that this complete list of books can be filtered for different query params to return those specific books i.e. if I hit url such as api/books/1?genre=Fiction&status=Available should further filter the books satisfying these criteria? Is it a good practice to do so or writing separate urls and views for this purpose is advised?

Upvotes: 2

Views: 1408

Answers (2)

oxalorg
oxalorg

Reputation: 2808

You can get the query urls from the GET query and then apply filters if they exist.

The values passed in the url query parameters are accessible in Django using the GET attribute of the request object.

Your code should look something like this:

@api_view(['GET', 'POST'])
def book_list(request, library_id):
    """
    List all books in a specific library, or create a new book for a specific library
    """
    if request.method == 'GET':
        genre_query = request.GET.get('genre', None)
        status_query = request.GET.get('status', None)

        books = Book.objects.filter(which_library=library_id)
        if genre_query:
            books = books.filter(genre=genre_query)
        if status_query:
            books = books.filter(status=status_query)

        print(books)
        serializer = BookSerializer(books, many=True)
        return Response(serializer.data)

This works because filter just builds the query without actually firing it to the database. Thus you can chain filters before an actual fetching command is called (like print or .all()).

Is it a good practice to do so or writing separate urls and views for this purpose is advised?

Depends on the use case, but filters using query params is a very popular way to add more customization while filtering.

Upvotes: 2

Jason
Jason

Reputation: 11363

If you want to pass in optional attributes to a view handler, you can use query params, which will be accessible by 'request.GETwhich is aQueryDict` that can be accessed just like a dictionary.

Rewriting your example:

if request.method == 'GET':
    books = Book.objects.filter(which_library = library_id)
    genre = request.GET.get('genre', None)
    status = request.GET.get('status', None)

    if genre:
       books = books.filter(genre = genre)
    if status:
       books = books.filter(status = status)

    ....    

Note reassigning of book on each filter. This is because a filter or any other queryset operation returns a new queryset and doesn't change the existing one.

Upvotes: 2

Related Questions