I have models Book and I want to save new record this book after every update information about this book (duplicate information). Wherein, I want that main book don’t save in database, only duplicate information in database. All fields are stored normally except “many-to-many”
I have tried all methods that I know, but It doesn’t work. In the code below I left my attempts maybe it will be useful
models.py
class Book(models.Model):
slug = models.SlugField(max_length=255)
title = models.CharField(max_length=255)
author = models.ForeignKey(
"Author",
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="author_book",
)
abstract = models.TextField()
coauthor_book = models.ManyToManyField("Author", blank=True, related_name="coauthor_book")
subject = ManyToManyField("Subject")
class Author(models.Model):
slug = models.SlugField(max_length=255)
name = models.CharField(max_length=255)
class Subject(models.Model):
slug = models.SlugField(max_length=255)
name = models.CharField(max_length=255)
forms.py
class BookForm(forms.ModelForm):
class Meta:
model = Book
fields = "__all__"
views.py
class BookUpdateView(UpdateView):
model = Book
form_class = BookForm
template_name = "book/book_update.html"
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form = self.get_form()
if form.is_valid():
obj = form.save(commit=False)
obj.editor = self.request.user
coauthors = obj.coauthor_book
subjects = obj.subject
draft = Book.objects.create(
title=obj.title,
author=obj.author,
abstract=obj.abstract,
slug=obj.slug,
)
draft.save()
# My triying. But It doesn't work
for coauthor in coauthors.all():
draft.coauthor_book.add(coauthor)
for subject in subjects.all():
draft.subject.add(subject)
return redirect("book", obj.slug)
return self.render_to_response({"form": form})
I’d verify that this code is doing what you’re expecting it to do by adding a couple of print statements to the flow.
I would want to print each instance of coauthor and subject within their related loops, along with printing coauthors.count() and subjects.count() before the loops - all in the interest of validating that you’re getting the right data.
I am newbie to Django, and can relate to frustration of trying to find a solution. Two things I see that may need changing is your using a HTML function “POST” as a class name “post” not recommended. 2nd is you need to use the .add() method on the associative tables to make a new record. not the save() method. You are going to have a book_coauthors and a book_subjects associative tables. Use a viewer on your SQL database to examine the tables created and the column names. This helped me better understand how the many-to-many relationship works in Django.
i had to add both a save() and an add() to my own view to get both tables updated in own my app.
Sorry, that is not correct. He’s not creating a class name “post”. That function is the appropriately named function within a ClassBasedView.
This is defining a class named BookUpdateView with a method named post.
He is doing that. See the section of code that I quoted in my previous reply.
What makes his pattern look odd is that he’s creating a new copy of the base object here, not modifying an existing copy. But assuming the data being referenced is correct, this all appears “right”.
From here, then I’d do more digging into the objects being used.
First, I’d probably examine the data in the tables themselves, then add some print statements earlier on to verify you’re working with the objects you think you’re working with and that the data you’re expecting to find is present in the table.
This line concerns me - I’m not convinced that obj is going to be fully populated in this case.
Ken, I dug into my objects which I used. It’s right objects. But it works If I only use (code is below):
class BookUpdateView(UpdateView):
model = Book
form_class = BookForm
template_name = "book/book_update.html"
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form = self.get_form()
if form.is_valid():
obj = form.save() #New
obj.editor = self.request.user
coauthors = obj.coauthor_book
subjects = obj.subject
draft = Book.objects.create(
title=obj.title,
author=obj.author,
abstract=obj.abstract,
slug=obj.slug,
)
draft.save()
for coauthor in coauthors.all():
draft.coauthor_book.add(coauthor)
for subject in subjects.all():
draft.subject.add(subject)
return redirect("book", obj.slug)
return self.render_to_response({"form": form})
This creates a new object and update my currently book. Although, I don’t want to update my currently book.
Ken, could you please give me some advice why this is so?
My best guess is that the commit=False doesn’t populate the pk field of the skeleton object being built - so you’re not getting the id of the Book being submitted at that point. As a result, the obj.coauthor_book reference is going to return null.
Since you’re creating a new instance of the model and not updating the existing model, you’ve got a couple different choices.
You could call self.get_object() to retrieve the object being edited rather than saving the form - but this just “feels wrong”. My gut reaction to this process is a “square peg / round hole” situation.
You’re creating a new instance of a model rather than updating an existing instance - so I’d approach this a different way.
Rather than inheriting from UpdateView, I’d inherit from CreateView - but still have the url parameter of the primary key for the existing object. When building the view, use that existing object as the default data for the form.
Then, when the form is submitted, you have all the original data in the fields, and still have that url parameter available to retrieve the m2m references to populate your new instance accordingly.
Another option would be to not use a model form at all. Use a FormView