2

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?

2 Answers 2

2

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.

Sign up to request clarification or add additional context in comments.

1 Comment

Perfect! Thanks a lot for your answer and clearing my doubt! It really helped a lot :)
2

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.

1 Comment

Thanks for your answer as well!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.