Got "No Books matches the given query" error even though the object exist on Django

I got this problem where Django return “No Books matches the given query”, even though the object exist in database.

For context, I want to create an update page without relying the use of URL. First, here is my model :

User = get_user_model()
class Books(models.Model):
    owner = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True)
    title = models.CharField(max_length=50)
    author = models.CharField(
        max_length=40,
    )
    author_nationality = models.CharField(max_length=20, default='Japanese')
    author_medsos = models.CharField(max_length=30, default='None')
    book_type = models.CharField(
        max_length=10,
        null=True,
        choices=BOOKS_TYPES
    )
    tl_type = models.CharField(
        max_length=20,
        null=True, 
        choices=TRANSLATION_BY
    )
    series_status = models.CharField(
        max_length=20,
        choices=SERIES_STATUS,
        default=ONG
    )
    source = models.CharField(max_length=100) # source can refer to downloage page, example = https://www.justlightnovels.com/2023/03/hollow-regalia/
    reading_status = models.CharField(
        max_length=20,
        choices=READING_STATUS,
        default=TOR
    )
    current_progress = models.CharField( # For tracking how many chapter have been read
        max_length=30,
        null=True
    )
    cover = models.FileField(upload_to='cover/', default='cover/default.png')
    genre = models.CharField(max_length=40, null=True)

class Review(models.Model):
    books = models.ForeignKey('Books', on_delete=models.CASCADE, null=True)
    volume = models.IntegerField()
    review = models.TextField()

Before the user get to the update page, they first have to visit /library page :

{% for book in user_books %}
    <div class='lib-item'>
        <form method='POST' action="{% url 'core:update_library' %}">
            {% csrf_token %}
            <input type='hidden' name='book_id' value='{{ book.id }}'>
            <button type='submit'>
                <img src={{ book.cover.url }} width='185'>
                
                    <span>
                        {{ book.title }}
                    </span>
                
            </button>
        </form>
    </div>
{% endfor %}

Notice the hidden input? The idea is to pass the book.id to the next page, which is /library/update. Here is the page for /library/update :

{% block content %}   
<div class='content'>
    <h3>{{ book.title }} by {{ book.author }}</h3>
    <small> _id : {{ book_id }} </small>
    <small> id : {{ book }} </small>

    <form method='POST'>
        {% csrf_token %}
        {{ form.as_p }}
        <button type='submit'>submit</button>
    </form>
{% endblock %}

And here is the views.py for both page

@login_required
def library(request):
    user = request.user
    user_books = user.books_set.all()
    context = {
        'user' : user,
        'user_books' : user_books
    }
    return render(request, 'user_library.html', context)

@login_required
def update_library(request):
    user = request.user

    book_id = request.POST.get('book_id')
    # book = user.books_set.get(pk=book_id)
    book = get_object_or_404(user.books_set, pk=book_id)

    if request.method == 'POST':
        form = UpdateBooks(request.POST, instance=book)
        if form.is_valid():
            form.save()

        context = {
            'book_id' : book_id,
            'book' : book,
            'form' : form
        }
        # messages.success(request, 'Library have been updated')
        # return HttpResponseRedirect('/library/update/')
        return render(request, 'user_update.html', context)
    else :
        context = {
            'book_id' : book_id,
            'book' : book
        }
        return render(request, 'user_update.html', context)

And the form using ModelForm :

class AddBooks(forms.ModelForm):
    class Meta:
        model = Books
        exclude = ["owner"]

class UpdateBooks(forms.ModelForm):
    class Meta:
        model = Review
        exclude = ["books"]

Now, when the user load the /library page, the page works fine. When user choose a book, they are redirected to /library/update with the book’s id being passed with the use of hidden input. And the /library/update able to display the information about the chosen book correctly, including the id, object, title, and author. So the book’s id should be present right?

But when the user submit the form, the page return an error : “Page not found (404) No Books matches the given query”.

I am confused as to why the error says there is “No Books matches the given query”. I have check Django admin page and the object exist. Even the /library/update is correctly displaying the information about the books, so the book object should exist. But when submitting the form, the books does not exist.

Does anyone have any ideas about this error happens?

Thank you

I don’t see anything materially wrong here. My only suggestion would be to either run this in the debugger or add some print statements in your view to verify whether you’re getting what you think you’re getting at each step.

For example, you have:

You could add print(f'book_id: {book_id}') after this line to show you what is present in that field.

You could also use the network tab in your browser’s developer tools to see what is being submitted in the post request.

it correctly print the book’s id in the terminal.
Also for more context, this is how /library/update page looks like.


As you can see above the form, the page display correct id and object of the chosen book from previous page.

I’m assuming this page you’re showing here is generated by the (unlabelled) template shown above? Is that template the user_update.html template?

If so, you have a mis-match here.

You have:

But, you’ve defined UpdateBooks as:

This is the wrong model (or the wrong form). UpdateBooks is not designed to accept an instance of Books, because it’s defined as being a form for Review.

1 Like

Yes, that page is for /library/update that uses user_update.html template.

I guess the then the problem is my database design?
User have one-to-many relationship with Books
Books have one-to-many relationship with Review
My goal here is for user can submit their review for each volume of books they’ve read.
Is that possible with my database relationship?

EDIT :
I find new clue for this problem.
So when /library/update is loaded, my terminal able to print the book’s id from print in my views.py.
Then when I fill in the form and submit it, instead of the book’s id, my terminal print out None instead.

book id : 14 is printed out when /update/library is loaded, then book id : None is printed out when I submit the form.

The issue here is not your models. It’s more an issue of how you have your views structured.

Restating this to ensure I understand what you’re trying to do. Please confirm or correct as appropriate.

You start out looking at a list of books. What is the name of the view that populates this list? Is it library?

You can then click on a book. You want this book selection to take you to another page containing a form. The view generating this page is update_library?

What model is it that you’re looking to update? Is it Books or Review?

Review has a many-to-one relationship with Books This means that this individual can create multiple reviews for their books.

So, if the model being updated is Review, are they always updating the same Review? Creating a new Review? Either? (If either, how do they specify that it’s a new review vs an update of an existing review?)

Side note: Yes, that situation you describe in your latest post makes sense because you’ve got this mixup between the model and the form.

Side note two: The model structure you have designed does not permit User A from seeing reviews written by User B. If 10 people all own copies of a given book, you’re going to have 10 instances of that Book in Books. Is that your intent here?

1 Like

You start out looking at a list of books. What is the name of the view that populates this list? Is it library?

Yes, the view is /library

You can then click on a book. You want this book selection to take you to another page containing a form. The view generating this page is update_library?

Correct, the view for this form is /update_library

What model is it that you’re looking to update? Is it Books or Review?

The model I plan to update is Review model, and for your next question, this form is to create a new review. So my intent here, the user can review each volume or chapter of each books, hence new review.

Side note two

Yes my intent is for these review to be private for each user.
And for you second question in this side note, I didn’t think that far before. But aside from Books and Reviews, I have Library model that I am going to use to build some kind of archive for Books. So if User delete a book in their library, my app will still have information about that book archived. Thank you for pointing this out, I’ll think about my database design again.

Also Thank you for pointing out the views issue here. I also realize maybe the way my view is structured makes it lost book_id upon page reload (when submitting a form). So I guess I have to modify my views. And I’m not in position to code right now, so maybe I’ll update this forum tomorrow. Thank you.

This isn’t quite the issue.

The issue is which form you’re using, and how you’re creating the instance of it.

You are creating an instance of UpdateBooks, which is a model form for creating or updating the model Review. However, when you create the instance of UpdateBooks, you’re passing an instance of type Books as the instance parameter.
This is a problem here. You must correct this to make this do what you’re trying to do. (Make sure you’re familiar with, and understand the difference between the instance parameter and the initial parameter when creating instances of a form.)

1 Like

I do that because block that I use for my other form :

@login_required
def books(request):
    user = request.user
    
    if request.method == 'POST':
        form = AddBooks(request.POST, request.FILES)
        
        if form.is_valid():
            owner = user
            form.instance.owner = owner
            form.save()
            
            context = { 'user' : user, 'form' : form }
            messages.success(request, 'Library have been updated')
            return HttpResponseRedirect('/books/')
        else:
            context = { 'user' : user, 'form' : form }
            return render(request, 'user_books.html', context)
    
    else:
        form = AddBooks()
        context = { 'user' : user, 'form' : form }
        return render(request, 'user_books.html', context)   

In this context, form is form for Books model, so I uses form.instance.owner = owner where owner act as reference to user. So I thought this could be done with my update form?

If you were updating a Books model, yes. But your UpdateBooks form is not a form for Books, it’s a form for Review, which is a different model. If you’re going to update an instance of Review, then the instance attribute needs to be an instance of Review, not Books.

1 Like