0

What is the proper way to create M2M relationships during a post-save signal in the admin panel? I have the below code. It successfully creates two Articles and a Blog but does not save the relationship between the two.

## models.py
from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver


class Article(models.Model):
    title = models.CharField(max_length=250)


class Blog(models.Model):
    title = models.CharField(max_length=250)
    articles = models.ManyToManyField(Article, blank=True)

    def set_related_articles(self):
        article_titles = ['a', 'b']
        for title in article_titles:
            _blog = Article(title=title)
            _blog.save()
            self.articles.add(_blog)


@receiver(post_save, sender=Blog)
def blog_post_save(sender, instance, **kwargs):
    instance.set_related_articles()

## admin.py
from django.contrib import admin
from .models import Blog


@admin.register(Blog)
class BlogUploadAdmin(admin.ModelAdmin):
    pass
Ben J.
  • 766
  • 1
  • 6
  • 17
  • I have copy&pasted your code in a brand new project and cannot reproduce the problem. The relationship gets saved. – dirkgroten Sep 09 '19 at 15:56
  • Where are you looking at whether the relationship is saved? – dirkgroten Sep 09 '19 at 15:57
  • You're right. I have been using the admin panel. It works as intended from the console but not the admin panel. – Ben J. Sep 09 '19 at 16:51
  • `from django.contrib import admin from .models import Blog @admin.register(Blog) class BlogUploadAdmin(admin.ModelAdmin): pass` – Ben J. Sep 09 '19 at 16:52
  • what do you mean "not the admin panel". When you create a new blog in the admin panel the article relationship isn't saved? or you don't see the relationship in the admin panel? – dirkgroten Sep 09 '19 at 16:53
  • From the web interface, when i save a `Blog` no entries are created in the `blog_articles` table. I cannot see them in the admin panel or the databse. If i create the `Blog` in console there are 2 entries in the table. http://127.0.0.1:8000/admin/m2m/blog/add/ – Ben J. Sep 09 '19 at 16:57

1 Answers1

1

When you use the default Blog admin page to add a new blog post, you're also including the M2M relationships you want to save. Django is going to create a Blog post with the values you submitted in the form:

enter image description here

The way the admin works, is that it's going to do the things in this order, because it's using a ModelForm:

  • Wrap everything in one transaction
  • Create the blog object by instantiating the form and saving it.
  • Call post_save() which will trigger your receiver and create the 2 articles and the relation to it
  • Call save_m2m() on the form with the relations you specified when creating the blog, which is no relation assuming you didn't select anything. form.cleaned_data['articles'] is the empty queryset.
  • Commit the transaction

The penultimate step is going to cancel your intent to add the relationship.

dirkgroten
  • 20,112
  • 2
  • 29
  • 42
  • Than you! So overwrite the `save_related` method in the admin. Is there any simple way to pass the output from the post_save signal to the form? – Ben J. Sep 09 '19 at 17:41
  • No, the post save signal only “knows” about models. It doesn’t even need to be called during a request let alone inside a form’s save method. – dirkgroten Sep 09 '19 at 18:50