Search code examples
django-modelsdjango-viewsforeign-keysdjango-class-based-views

Django: Querying Foreign Keys


I have two models Book and Review,

models.py

class Book(models.Model):
    title = models.CharField()

class Review(models.Model):
    rating = models.IntegerField()
    content = models.TextField()
    book = models.OneToOneField(Book, on_delete=models.CASCADE)

I'm using ListView to display the book title with it's rating and review. However, I'm having issues with querying the rating and content attributes of the Review model that match the book.

views.py

class BookList(ListView):
  model = Book
  context_object_name = 'books'
  template_name = 'book_list.html'

  def get_context_data(self, **kwargs):
    context = super().get_context_data(**kwargs)
    for book in context['books']:
      try: 
       context['review'] = Review.objects.get(book=book)
      except Review.DoesNotExist:
       context['review'] = None
    return context

book_list.html

<table style=width:100%>
  <tr>
    <th>Book</th>
    <th>Rating</th>
    <th>Review</th>
  </tr>
  {% for book in books %}
  <tr>
    <th>{{ book.title }}</th>
    <th>{{ review.rating }}</th>
    <th>{{ review.content }}</th>
  </tr>
  {% endfor %}
</table>

The book titles render fine, but there is nothing for the rating and an empty queryset for the review.

I know the issue has to do something with the get_context_data method and how it retrieves the review object. I tried using the _set.all() method, but that didn't work either.

def get_context_data(self, **kwargs):
  context = super().get_context_data(**kwargs)
  for book in context['books']:
    context['review'] = book.review_set.all()
  return context

How can I query the review that is connected to the specific book and access all of that review's attributes?


Solution

  • I read in the comments that you recently changed the relationship from ForeignKey (OneToMany) to OneToOne relationship. So you have to make sure that you did your migrations properly and there are no Items left in your database with the "old" relationship. So much for the disclaimer.

    You should be able to do this quite simple.

    class BookList(ListView):
        model = Book
        context_object_name = 'books'
        template_name = 'book_list.html'
    
        # delete get_context_data entirely - the method of parent ListView does just fine
    
    
    <table style=width:100%>
      <tr>
        <th>Book</th>
        <th>Rating</th>
        <th>Review</th>
      </tr>
      {% for book in books %}
      <tr>
        <th>{{ book.title }}</th>
        <th>{{ book.review.rating }}</th>
        <th>{{ book.review.content }}</th>
      </tr>
      {% endfor %}
    </table>
    

    Read more about reverse lookup of OneToOne relationships here.

    Let me know how it goes!