In order to do exercises in Django 4.2 and Python, I am creating a project in Django so that users can consult and evaluate the books they read. Some books are already in the database in the "Book" table in the "library" app. My goal is for users to add these books to their user area, which would be saved in the "UserBook" table of the "readers" app so that they could later add notes, or classification.
To implement this logic, the idea is for the user to search for a book, if it exists in the "Book" database of the "library" app, enter the book page and use the button "Add to my books" it is automatically added (its copied data) to the "Userbook" table in the "readers" app and is displayed on the book page in the user area.
This is the table "Book" of the app "library":
class Book(Base):
class Meta:
verbose_name = 'Book'
verbose_name_plural = 'Books'
db_table = 'books'
cover = models.ImageField(upload_to='pictures/cover', blank=True, default='')
title = models.CharField('Title', max_length=100)
publisher = models.CharField('Publisher', max_length=100)
isbn = models.CharField('ISBN', unique=True, max_length=100)
nr_pages = models.IntegerField('Pages', null=True, blank=True)
synopsis = models.TextField('Synopsis', null=True, blank=True)
author = models.ManyToManyField(Author, verbose_name='Author(s)', related_name='book')
def __str__(self) -> str:
return self.title
This is the code for the url path('book/int:id/', views.single_book, name='single_book'), that have the form and the button "Add to my books":
<main class="main-content">
<div class="main-bg">
<section class="hero">
<img class="hero-content-img" src= {{ book.cover.url }} alt="Book cover of {{ book.title }}" >
<header class="hero-content">
<h2 class="hero-content-title">{{ book.title }}</h2>
<a class="hero-content-subtitle">
{% for author in book.author.all %}
{{ author.name }}{% if not forloop.last %}, {% endif %}
{% endfor %}
</a>
<div class="data-meta">
<span class="data-meta-text">ISBN: {{ book.isbn }}</span>
<span class="data-meta-text">Publisher: {{ book.publisher }}</span>
<span class="data-meta-text">Pages: {{ book.nr_pages }}</span>
</div>
<form action="{% url 'readers:book_review' book.id %}" method="post">
{% csrf_token %}
<button type="submit" class="start-reading-button">Add to My Books</button>
</form>
</header>
</section>
</div>
</main>
This is the table "UserBook" of the app "readers":
class UserBook(Base):
class Meta:
verbose_name = 'My Book'
verbose_name_plural = 'My Books'
db_table = 'user_books'
cover = models.ImageField(upload_to='readers/cover', blank=True, default='')
title = models.CharField('Title', max_length=100)
publisher = models.CharField('Publisher', max_length=100)
isbn = models.CharField('ISBN', unique=True, max_length=100)
nr_pages = models.IntegerField('Pages', null=True, blank=True)
synopsis = models.TextField('Synopsis', null=True, blank=True)
author = models.CharField('Author(s)', max_length=255, null=True, blank=True)
owner = models.ForeignKey(User, related_name='books', on_delete=models.CASCADE,)
def __str__(self) -> str:
return self.title
This is the view for the page that presentes the book in the user area in the path('review/my_books/int:user_book_id/', views.book_review, name='book_review'):
def book_review(request, user_book_id):
user_book = None # Inicialize user_book com None
if request.method == 'POST':
# Verifica se o livro existe
book = get_object_or_404(Book, pk=user_book_id)
# Verifica se o usuário já possui este livro
user_book_exists = UserBook.objects.filter(isbn=book.isbn, owner=request.user).exists()
if user_book_exists:
messages.error(request, "You already have this book in your collection.")
# Se o livro já existe, redirecione para a página de revisão do livro existente
user_book = UserBook.objects.get(isbn=book.isbn, owner=request.user)
return redirect('readers:book_review', user_book_id=user_book.id)
else:
# Cria um novo UserBook com os dados do livro
user_book = UserBook.objects.create(
cover=book.cover,
title=book.title,
publisher=book.publisher,
isbn=book.isbn,
nr_pages=book.nr_pages,
synopsis=book.synopsis,
author=", ".join([author.name for author in book.author.all()]),
owner=request.user
)
# Redireciona para a página de revisão do UserBook recém-criado
return redirect('readers:book_review', user_book_id=user_book.id)
print("User Book:", user_book)
# Redireciona para a página de revisão do UserBook correspondente (se existir)
if user_book is not None:
return redirect('readers:book_review', user_book_id=user_book.id)
else:
# Caso não haja user_book, renderize um template vazio ou redirecione para outra página
# Por exemplo, você pode retornar uma resposta HTTP com um status 404 (página não encontrada)
return HttpResponse("User Book not found", status=404)
This is where I have difficulty, because despite recording in the database, in the "Userbook" table, the url review/my_books/int:user_book_id presents the id from the "Book" table and not the new id that is in the "Userbook" table, thus giving an error when presenting the url correct. Is it possible to change this view to maintain this logic or how could I do this?
Problem
I think the problem comes from line 6
book = get_object_or_404(Book, pk=user_book_id). You search in theBooktable by filtering with a primary key from theUserBooktable.Solution
In the HTML, instead of sending the
user_book_id, you should send thebook_id.