My aim is to have a form where users can enter a book and its authors into the database using an 'Add Author' button if there's more than one author. I use a many-to-many relationship so all of these authors can be added (or in most cases, just 1 author). Currently the book is getting added to the table but not the author. I think it has to do with the multiple forms aspect.
Originally I tried CreateView until I realised that doesn't work with more than one model at a time.
models.py:
class Book(models.Model):
title = models.CharField(max_length=200)
authors = models.ManyToManyField('Author', through='Authored')
def __str__(self):
return self.title
class Author(models.Model):
name = models.CharField(max_length=200)
books = models.ManyToManyField('Book', through='Authored')
def __str__(self):
return self.name
class Authored(models.Model):
book = models.ForeignKey(Book, on_delete=models.CASCADE)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
views.py:
class BookCreate(View):
template_name = 'bookmany/book_form.html'
form_class = BookForm
formset_class = AuthorFormSet
def get(self, request, *args, **kwargs):
form = self.form_class()
formset = self.formset_class()
return render(request, self.template_name, {'form': form, 'formset': formset})
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
formset = self.formset_class(request.POST)
success_url = reverse_lazy('bookmany:book_list')
if form.is_valid():
book = form.save()
if formset.is_valid():
for form in formset:
if form.cleaned_data.get('name'):
author, created = Author.objects.get_or_create(
name=form.cleaned_data.get('name'))
Authored.objects.create(book=book, author=author)
return redirect(success_url)
return render(request, self.template_name, {'form': form, 'formset': formset})
book_form.html:
{% extends "base_menu.html" %}
{% block content %}
<p>
<form action="" method="post">
{% csrf_token %}
{{ form.as_p }}
<div id="author-forms">
{{ formset.management_form }}
{% for author_form in formset %}
{{ author_form.as_p }}
{% endfor %}
</div>
<div id="empty-form" style="display:none;">
<div class="author-form">
{{ formset.empty_form }}
</div>
</div>
<button type="button" class="add-form">Add Author</button>
<input type="submit" value="Submit">
<input type="submit" value="Cancel" onclick="window.location.href=
"{% url 'bookmany:book_list' %}";return false;">
</form>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script type="text/javascript">
$(function() {
$('.add-form').click(function() {
var formIdx = parseInt($('#id_authorformset-TOTAL_FORMS').val());
var newForm = $('#empty-form').html().replace(/__prefix__/g, formIdx);
$('#author-forms').append(newForm);
$('#id_authorformset-TOTAL_FORMS').val(formIdx + 1);
});
});
</script>
</p>
{% endblock %}
You don't have to use
models.ManyToManyFieldin both models. Use it only in Book model. Then create Book model object and Author model object and use them to create Authored model object. But to be honest, you don't have to usethroughin relationship in this case. You can use onlymodels.ManyToManyFieldin one of that models. We use transitional table, when we want to add another parameter to this relationship.