Django Form ModelMultipleChoiceField issue with saving data to db

68 Views Asked by At

I am creating my first proper Django application. It is a Library management system. I have three models:

class Authors(models.Model):
    firstName = models.CharField(max_length=20)
    lastName = models.CharField(max_length=20)
    
    def __str__(self):
        return f"{self.firstName.title()} {self.lastName.title()}"
    
    def save(self):
        self.firstName = self.firstName.casefold()
        self.lastName = self.lastName.casefold()
        super().save()
    
class Books(models.Model):
    isbn = models.BigIntegerField(primary_key=True, validators=[
        MinValueValidator(9780000000000),
        MaxValueValidator(9799999999999),
    ])
    bookName = models.CharField(max_length=50)
    authors = models.ManyToManyField(Authors, through='BookAuth')
    pubName = models.CharField(max_length=20)
    inventory = models.IntegerField(default=0)
    
    def __str__(self):
        return f"{self.pk} || {self.getName()}"
    
    def getName(self):
        name, bname = self.bookName.split(), ""
        for x in name:
            bname += x[0].upper() + x[1:] + " "
        return bname[:-1]
    
    def getAuthors(self):
        bookAuths = BookAuth.objects.filter(isbn=self.isbn)
        auth = ""
        for bookAuth in bookAuths:
            auth += f"{bookAuth.getAuth()}, "
        return auth[:-2]
    
    def save(self):
        self.bookName = self.bookName.casefold()
        self.pubName = self.pubName.casefold()
        super().save()

class BookAuth(models.Model):
    isbn = models.ForeignKey(Books, on_delete=models.CASCADE)
    auth = models.ForeignKey(Authors, on_delete=models.CASCADE)
    
    def getAuth(self):
        return Authors.objects.get(id=self.auth.id)
    
    def __str__(self):
        return f"{self.auth} - {self.isbn}"

Here I am storing the Books and the Authors separately, and using the BookAuth model to eastablish a ManyToMany relation. I am having problems with adding a new book. For that I have two forms, one to add a new author, and the other to add a new book. The forms.py file looks like this:

class AddAuthors(forms.ModelForm):
    class Meta:
        model = Authors
        fields = ("__all__")

class AddBooks(forms.ModelForm):
    author = forms.ModelMultipleChoiceField(
        queryset=Authors.objects.all(),
        widget=CheckboxSelectMultiple,
        )
    class Meta:
        model = Books
        fields = ('isbn', 'bookName', 'authors', 'pubName', 'inventory')

I want to have a multiple choice field for the author attribute, to add one or more authors to a new book. I am handling this form using the following view:

def books(request):
    if request.method == "GET":
        cont1 = footer_counter() # returns a dictionary with total no of members, books and total inventory
        cont2 = {'bookform': AddBooks(), 'authform': AddAuthors(), 'books': Books.objects.all()}
        return render(request, 'books.html', {**cont1,**cont2})
    if request.method == "POST":
        authData = AddAuthors(request.POST)
        bookData = AddBooks(request.POST)
        if authData.is_valid():
            authData.save()
        elif bookData.is_valid():
            bookData.save()
        return redirect(books)

But my issue here is that after I submit the form, none of the data is saved to the model (Book or the BookAuth). Django gives the following log messages: [27/Apr/2023 14:13:03] "POST /books/ HTTP/1.1" 302 0 [27/Apr/2023 14:13:03] "GET /books/ HTTP/1.1" 200 8009

How can I fix this? Thank you.

Additional: The Django Template Language for the form:

<div class="col-md-6 text-center">
  <form method="post" autocomplete="off">
  {% csrf_token %}
    <table class="table text-center">
      <thead class="table-dark">
        <tr><th colspan="2">Add a new Book</th></tr>
      </thead>
      <tbody>
        <tr>
          <td class="col-4"><label for="{{ bookform.isbn.id_for_label }}" class="form-label text-white">ISBN</label></td>
          <td class="col-8">{{ bookform.isbn }}</td>
        </tr>
        <tr>
          <td><label for="{{ bookform.bookName.id_for_label }}" class="form-label text-white">Book Name</label></td>
          <td>{{ bookform.bookName }}</td>
        </tr>
        <tr>
          <td><label for="{{ bookform.author.id_for_label }}" class="form-label text-white control-label">Authors</label>
          </td>
          <td>{{ bookform.author }}</td>
        </tr>
        <tr>
          <td><label for="{{ bookform.pubName.id_for_label }}" class="form-label text-white">Publisher Name</label></td>
          <td>{{ bookform.pubName }}</td>
        </tr>
        <tr>
          <td><label for="{{ bookform.inventory.id_for_label }}" class="form-label text-white">Inventory</label></td>
          <td>{{ bookform.inventory }}</td>
        </tr>
      </tbody>
    </table>
    <button type="submit" class="btn btn-primary mx-auto">Add Book</button>
  </form>
</div>

After submission the data is not getting saved to the db. It works just the way it's supposed to, but does not save the data to the model. (I checked using a flag variable and print statements, and the bookform.is_valid() is returning false even when I enter proper values)

1

There are 1 best solutions below

0
On BEST ANSWER

Your problem comes from the name of the field in your form.

Your field in the form is called author, but in the fields you get authors (from the model).

Just edit your form like this:

class AddBooks(forms.ModelForm):
authors = forms.ModelMultipleChoiceField(
    queryset=Authors.objects.all(),
    widget=CheckboxSelectMultiple,
    )
class Meta:
    model = Books
    fields = ('isbn', 'bookName', 'authors', 'pubName', 'inventory')

And it should work