How can I autofill a field in my django form in class based view?

I am building website for library and I want to add comment for each book. I built a form for it, but I don’t know how to pass the title for each book. Furthermore, I did it for user, but I don’t know how to do it for title.

models.py

import uuid

from django.db import models
from django.db.models.enums import TextChoices
from django.urls import reverse
from django.utils import timezone
from django.utils.translation import gettext_lazy as _

from django.contrib.auth.models import User


class Genre(models.Model):
    name = models.CharField(max_length=100)

    def __str__(self):
        return self.name


class Book(models.Model):
    title = models.CharField(max_length=200)
    image = models.ImageField(
        upload_to="photos", default=None, blank=True, null=True)
    author = models.ForeignKey("Author", on_delete=models.SET_NULL, null=True)
    summary = models.TextField()
    isbn = models.CharField(max_length=10, unique=True, blank=True, null=True)
    genre = models.ManyToManyField("Genre", related_name="book_genre")
    publisher = models.ManyToManyField("Publisher")
    languages = models.ManyToManyField("Language")
    publication_date = models.DateTimeField(default=timezone.now)
    created_date = models.DateTimeField(default=timezone.now)

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        return reverse("book_detail", kwargs={"pk": self.pk})

    class Meta:
        ordering = ["-created_date"]
        constraints = [
            models.UniqueConstraint(
                fields=["title"], name="%(app_label)s_unique_booking"
            )
        ]


class Publisher(models.Model):
    name = models.CharField(max_length=250)

    def __str__(self):
        return self.name


class Language(models.Model):

    class LanguageBook(models.TextChoices):
        English = "Eng", _('English')
        Spanish = "Es", _('Spanish')
        Russian = "Rus", _('Russian')
        German = "German", _('German')
        Chinese = "Chinese", _('Chinese')

    language_book = models.CharField(
        max_length=10, choices=LanguageBook.choices, default=LanguageBook.English)

    def __str__(self):
        return self.language_book

    # Author


class Author(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)

    class Meta:
        ordering = ["first_name", "last_name"]
        constraints = [
            models.UniqueConstraint(
                fields=["first_name", "last_name"], name="uniquename"
            )
        ]

    def get_absolute_url(self):
        return reverse("author_detail", kwargs={"pk": self.pk})

    def __str__(self):
        """String for representing the Model object."""
        return f"{self.first_name} {self.last_name}"


class Review(models.Model):
    book = models.ForeignKey(
        Book, on_delete=models.CASCADE, related_name="reviews")
    title = models.CharField(max_length=255)
    body = models.TextField()
    author = models.OneToOneField(User, on_delete=models.CASCADE)
    created = models.DateTimeField(default=timezone.now)

    def __str__(self):
        """String for representing the Model object."""
        return self.body

forms.py

from django import forms

from .models import Author, Book, Review

class NewReviewForm(forms.ModelForm):
    class Meta:
        model = Review
        fields = ("title", "body")

views.py

from django.db.models import Q
from django.contrib import messages
from django.contrib.messages.views import SuccessMessageMixin
from django.http import HttpResponseForbidden
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse_lazy
from django.urls import reverse
from django.views.generic.edit import FormMixin
from django.views.generic import (CreateView, DeleteView, DetailView, ListView,
                                  TemplateView, UpdateView)


from .filters import BookFilter
from .forms import AuthorForm, BookForm, NewReviewForm
from .models import Author, Book, Genre, Review



class BookDetailView(FormMixin, DetailView):
    model = Book
    template_name = "book-detail.html"
    context_object_name = "book"
    form_class = NewReviewForm

    def get_success_url(self):
        return reverse('book_detail', kwargs={'pk': self.object.id})

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context["author"] = Author.objects.all()
        context['form'] = NewReviewForm(initial={'id': self.object})
        return context

    def post(self, request, *args, **kwargs):
        if not request.user.is_authenticated:
            return HttpResponseForbidden()
        self.object = self.get_object()
        form = self.get_form()
        if form.is_valid():
            return self.form_valid(form)
        else:
            return self.form_invalid(form)

    def form_valid(self, form):
        form.instance.author = self.request.user
        form.save()
        return super(BookDetailView, self).form_valid(form)

 

book-detail.html

{% extends 'base.html' %}



{% block content %}

<div class="container">
  <div class="row justify-content-md-center">

    {% comment %} Image and update, delete {% endcomment %}
    <div class="col-2 mt-5 ms-5 d-flex flex-column align-items-center">
      {% if book.image %}
      <img src="{{ book.image.url }}" alt="{{book.title}}" style="width:200px; height:200px">
      {% else %}
      <p>No Image</p>
      {% endif %}
      <a href="{% url 'book_update' book.id %}" class="d-flex align-items-center gap-1 mt-2"><svg
          xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-pencil-square"
          viewBox="0 0 16 16">
          <path
            d="M15.502 1.94a.5.5 0 0 1 0 .706L14.459 3.69l-2-2L13.502.646a.5.5 0 0 1 .707 0l1.293 1.293zm-1.75 2.456-2-2L4.939 9.21a.5.5 0 0 0-.121.196l-.805 2.414a.25.25 0 0 0 .316.316l2.414-.805a.5.5 0 0 0 .196-.12l6.813-6.814z" />
          <path fill-rule="evenodd"
            d="M1 13.5A1.5 1.5 0 0 0 2.5 15h11a1.5 1.5 0 0 0 1.5-1.5v-6a.5.5 0 0 0-1 0v6a.5.5 0 0 1-.5.5h-11a.5.5 0 0 1-.5-.5v-11a.5.5 0 0 1 .5-.5H9a.5.5 0 0 0 0-1H2.5A1.5 1.5 0 0 0 1 2.5v11z" />
        </svg>Update information</a>
      <a href="{% url 'book_delete' object.id %}"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"
          fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16">
          <path
            d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z" />
          <path fill-rule="evenodd"
            d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z" />
        </svg>
        Delete Book</a>
    </div>
    {% comment %} End Image {% endcomment %}

    {% comment %} Content {% endcomment %}
    <div class="col-6 mt-5 ms-5">
      <h1>{{book.title}}</h1>
      <a href="{% url 'author_detail' book.author.id %}" class="color-primary">{{book.author}}</a>
      {% comment %} start Google Translate {% endcomment %}
      <div id="google_translate_element" class="text-end"></div>
      {% comment %} End Google Translate {% endcomment %}
      <p class="mt-5"><strong>Summary:</strong></p>
      <p class="bold">{{book.summary}}</p>
      <br>
      <ul class="list-group mt-4">
        <li class="list-group-item ">Book author: {{book.author}}</li>
        <li class="list-group-item">Genre: {{ genre.book_genre.all }}</li>
        <li class="list-group-item">A third item</li>
        <li class="list-group-item">A fourth item</li>
        <li class="list-group-item">And a fifth one</li>
      </ul>
      <div>
        {% comment %} Start review {% endcomment %}
        <h1>Review</h1>
        {% for review in book.reviews.all %}
        <p>{{review.author}}</p>
        <div class="notification">
          <p>{{ review.created|date:"d-m-Y" }}</p>
        </div>
        <p>{{review.title}}</p>
        <p>{{ review.body|linebreaks }}</p>
        {% empty %}
        <div class="notification">
          No review yet ...
        </div>
        {% endfor %}
      </div>
      <div>
        {% if request.user.is_authenticated %}
          <form method="post">
            {% csrf_token %}
            {{ form.as_p }}
            <input type="submit" value="Add comment">
          </form>

        {% else %}
          <p>Please sign in to add review</p>
        {% endif %}

      </div>
      {% comment %} End review {% endcomment %}
    </div>
    {% comment %} End comment {% endcomment %}

    {% comment %} Loan {% endcomment %}
    <div class="col-4 mt-5" style="width: 300px; height:100px">
      <div class="card rounded-3 shadow-sm">
        <div class="card-header py-3">
          <h4 class="my-0 fw-normal tex-center">Loan</h4>
        </div>
        <div class="card-body">
          <ul class="list-unstyled mt-3 mb-4">
            <li>...</li>
          </ul>
          <button type="button" class="w-100 btn btn-lg btn-outline-primary">Take</button>
        </div>
      </div>
      {% comment %} End Loan {% endcomment %}
    </div>

  </div>

  {% endblock content %}

Wrong class. You want to use UpdateView. That’ll let you remove about 90% of what you’ve got in the view.

Also, I’d make the suggestion that you get rid of all the extra stuff in your template until you get the basic form working. It’s going to make it a lot easier to see what’s working and what isn’t.

Ken, hello!
Thank you for your response and advice.
But I can’t image how I can use UpdateView within DetailView, especially when I built information about each books with DetailView.
Maybe will it be something like this?

class BookDetailView(DetailView, UpdateView): **new**
    model = Book
    template_name = "book-detail.html"
    context_object_name = "book"
    form_class = ReviewForm

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context["author"] = Author.objects.all()
        context['form'] = ReviewForm(initial={'id': self.object})
        return context

    def post(self, request, *args, **kwargs):
        if not request.user.is_authenticated:
            return HttpResponseForbidden()
        self.object = self.get_object()
        form = self.get_form()
        if form.is_valid():
            return self.form_valid(form)
        else:
            return self.form_invalid(form)

    def form_valid(self, form):
        form.instance.author = self.request.user
        form.instance.title = self.title  **new**
        form.save()
        return super(BookDetailView, self).form_valid(form)

I’m not understanding what you’re trying to do here.

Let’s take a step back and help me understand what’s going on.

You have a model - Book. You’re looking to update it. I don’t understand where the DetailView comes into play.

The Django generic CBVs are not designed to be mixed in the way you’re trying to use them. What you really have is an UpdateView with additional content to be rendered in the template.

I want to make it possible for the user to write a comment for each book (for example, as is done on amazon). I take the information about the book itself using the detail view.

I created a form for comments, and displayed it on the page, I want the user to write only the title and content, but do not indicate himself or choose the title of the book. I think it will be something like the user fields and the book title will be automatically filled.

I managed to do this for user indication. But I can’t do it for the title of the book

If it’s not an editable field, then it’s not a field - it’s just text to be rendered on the page. Your “CreateView” is for the comment. It’s a form to be filled out. Everything else is just text. From your description, they’re not editing a Book.

Yes, but I use the DetailView and not the CreateView for this.

class BookDetailView(FormMixin, DetailView):
    model = Book
    template_name = "book-detail.html"
    context_object_name = "book"
    form_class = ReviewForm

    def get_success_url(self):
        return reverse('book_detail', kwargs={'pk': self.object.id})

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context["author"] = Author.objects.all()
        context['form'] = ReviewForm(initial={'id': self.object})
        context['hits'] = UrlHit.objects.all()
        return context

    def post(self, request, *args, **kwargs):
        if not request.user.is_authenticated:
            return HttpResponseForbidden()
        self.object = self.get_object()
        form = self.get_form()
        if form.is_valid():
            return self.form_valid(form)
        else:
            return self.form_invalid(form)

    def form_valid(self, form):
        form.instance.author = self.request.user
        form.save()
        return super(BookDetailView, self).form_valid(form)

Yes, you are right, I want to use the data from the database to auto-complete the forms. For example, the user enters the Harry Potter book page and writes his comment, where there are only two fields - the title of the comment and the content.
Now, in order to write a comment, user also need to indicate that this is a book about Harry Potter, otherwise an error appears -
null value in column “book_id” of relation “library_review” violates not-null constraint
DETAIL: Failing row contains (34, assa, sasa, 1, null, 2021-12-12 20:46:01.34461+00).

So, use the correct views.

It’s a CreateView with additional detail, not a DetailView with a FormMixin. That’s the wrong combination of views to use in this situation.

2 Likes

Thank you so much, Ken!
I understood, and I did this. I’m sorry for misunderstanding

views.py

class BookDetailView(DetailView):
    model = Book
    template_name = "book-detail.html"
    context_object_name = "book"

    def get_success_url(self):
        return reverse('book_detail', kwargs={'pk': self.object.id})

    def get_context_data(self, **kwargs):
        context = super(BookDetailView, self).get_context_data(**kwargs)
        context["author"] = Author.objects.all()
        context['reviewform'] = ReviewForm()
        return context

    def post(self, request, pk):
        book = get_object_or_404(Book, pk=pk)
        form = ReviewForm(request.POST)

        if form.is_valid():
            obj = form.save(commit=False)
            obj.book = book
            obj.author = self.request.user
            obj.save()
            return redirect('book_detail', book.pk)


class ReviewCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
    model = Review
    form_class = ReviewForm
    success_message = "Review was created succesfully"

    def get_context_data(self, **kwargs):
        context = super(ReviewCreateView, self).get_context_data(**kwargs)
        context['book'] = get_object_or_404(Book, pk=self.kwargs['pk'])
        return context

    def form_valid(self, form):
        form.instance.author = self.request.user
        form.instance.book = get_object_or_404(Book, pk=self.kwargs['pk'])
        return super(ReviewCreateView, self).form_valid(form)

    def get_success_url(self):
        return reverse('book_detail', kwargs={'pk': self.kwargs['pk'], })

book-detail.html

        <form method="post">
          {% csrf_token %}
          {{ reviewform.as_p }}
          <input type="submit" value="Add comment">
        </form>

I’m sorry. I tried this on another book, but it doesn’t work very well. When I create a new comment a second time on another book, I get the error - duplicate key value violates unique constraint “library_review_author_id_key”
DETAIL: Key (author_id)=(1) already exists.

You have author defined as a OneToOneField to User - that means that each User can only have one Review associated with them.

2 Likes

Thank you! It works.