Search code examples
pythondjangodjango-signals

Create M2M Relationship in post_save Signal from Admin


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

Solution

  • 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.