How to set default value to ManyToManyField

Dear All,

I am having issues defining logic for default values of a ManyToManyField.

I have two models associated with a many-to-many relationship with a custom through model. For simplicity I will use the Sandwich and Sauce example defined in this post: The right way to use a ManyToManyField in Django :

from django.db import models

class Sauce(models.Model):
    name = models.CharField(max_length=100)

    def __str__(self):
        return self.name

class Sandwich(models.Model):
    name = models.CharField(max_length=100)
    sauces = models.ManyToManyField(Sauce, through='SauceQuantity')

    def __str__(self):
        return self.name

class SauceQuantity(models.Model):
    sauce = models.ForeignKey(Sauce, on_delete=models.CASCADE)
    sandwich = models.ForeignKey(Sandwich, on_delete=models.CASCADE)
    extra_sauce = models.BooleanField(default=False)

    def __str__(self):
        return "{}_{}".format(self.sandwich.__str__(), self.sauce.__str__())

I would like to implement the following behaviors:

  • when a new Sandwich is created, it should be associated with a default Sauce with name “Unknown” (if the Sauce “Unknown” does not exist yet in the database, it must be created, like in get_or_create).
  • when a new Sauce is added to a Sandwich, the “Unknown” Sauce must no longer be associated with the Sandwich.
  • if all Sauces are removed from a Sandwich, the default “Unknown” Sauce must be associated again to the Sandwich.

At first I tried to implement this behavior by defining a callable and using it as the default argument of ManyToManyField. Then I found that default for ManyToManyField is ignored.

Then I tried to use a post_save signal associated to Sandwich. The issue is that, if I try to add a Sauce to a Sandwich, the post_save receiver function of the Sandwich is executed with a Sandwich instance that only has the previous Sauces, not the new Sauce I am adding. So I don’t know how I can reliably implement the logic “if a Sauce other than Unknown is going to be present on this Sandwich, remove the Unknown Sauce”.

I have also tried using the m2m_changed signal. But then I found out it is not triggered by an inline in the Admin interface, and I need to use the Admin interface.

Am I missing something? I would be grateful for any suggestion: should I do this with signals? By defining a custom save method? Should I do something else entirely? I want to do this in the most efficient and idiomatic way in the context of the Django framework.

I am using Django 5.0.1 on Python 3.12.1.

Yes.

Managing these types of relationships fall outside the general guidelines of what you should be using the admin for.

To quote the Django admin docs

If you need to provide a more process-centric interface that abstracts away the implementation details of database tables and fields, then it’s probably time to write your own views.

This sort of “business-rule” management belongs in its own views.

Yes, you probably can work around the structure of the admin to do this, but I think it’s a bad idea - an “anti-pattern” of Django usage.

Thanks, I now understand that the Admin interface is not meant to handle business logic. That’s probably why the fact that m2m_changed is not triggered by the Admin interface is not a bug.

Ok, I need my own views to interact with Sandwiches and Sauces in the above example.
But I don’t think this kind of logic should be tied to the views directly: this is more “business logic” than “presentation logic”. May I ask you where would you put this kind of behaviour? I am reading conflicting advice about signals and custom save methods. I would be interested in your opinion.

If you are familiar with, or have used the term “Model-View-Controller” as an architecture design principle and are simply looking at the name “views.py”, then you’re drawing (a reasonable but) an incorrect conclusion. The “views.py” is (one of) the place(s) where it’s appropriate to put business logic. Don’t get hung up on file name use relative to other systems or documentation that is not Django-related.

See the FAQ: Django appears to be a MVC framework, …

And briefly:

I’ve said many times around here: Use _only_when absolutely necessary. In Django, they should not be used routinely or as an architectural design default.

Wonderful - if you have sufficient control over the application to ensure they’re used. (There are API calls to save data that can bypass those methods, bulk_create and bulk_update being two of them. As a result, you do need to be careful relying upon them.)

Thanks for the thorough answer!

For the reference, I consider myself a relatively experienced Python developer, but relatively new to Django, and still learning about Django patterns and best practices.

Do you have additional reading materials suggesting what kind of business logic is appropriate for the views? Because the FAQ you sent me suggests using views to select which data the templates will then present.

Also thanks for the warning about using signals too routinely! I found a couple of blog posts that highly recommends them, but the official documentation warns that “Signals give the appearance of loose coupling, but they can quickly lead to code that is hard to understand, adjust and debug”.

Also thanks for the warning that the custom save method is can be bypassed by other “bulk save” methods. In my particular situation, since I should have sufficient control over this particular application, I think they can be appropriate!

What kind of business logic is appropriate for views? All.

Now, some people choose to embed as much model-related code in the models as possible - what you’ll see described in blog posts as “fat models”, and I agree with that too.

As a web framework, Django is built on a request/response cycle. An http request for a URL is received by a server and handed off to Django. Django examines the url definitions to find the view that will handle the requested URL. Django calls that view. The view’s responsibility is to create a response - typically, an HTTP response.

What happens between the view being called by Django and the response being returned is entirely up to you.

My position is actually even stronger than that. I say, don’t use signals at all, unless it’s absolutely required. (Typically, this occurs when you’re integrating some functionality with a third party package and signals are your only way to interact with that package.)

Yes, they can be. But it’s something that the “2-years from now” you will need to remember when you’re looking to add functionality then, that wasn’t needed today.

1 Like

Thank you so much for all your valuable insights!