Reputation: 8722
I have the following models:
class Pizza(models.Model):
toppings = models.ManyToManyField(Topping)
has_toppings = models.BooleanField(default=False)
def check_if_there_are_toppings(self):
if len(self.toppings.all()) > 0:
self.has_toppings = True
@receiver(m2m_changed, sender=Pizza.toppings.through)
def toppings_changed(sender, instance, **kwargs):
instance.check_if_there_are_toppings()
instance.save()
What I want to do is update the has_toppings
field whenever the toppings
length is more than 0. What would be the correct way to do this? Thanks for any help.
Upvotes: 1
Views: 718
Reputation: 476547
I think it is probably not a good idea to do that. There can be a lot of reasons how a many-to-many relation changes. It will be very hard, or even impossible to cover all of these. For example a Topping
object itself can be removed, and therefore trigger a change in all the many-to-many relations where the topping was used. Furthermore the Django ORM has some functions like .bulk_create(…)
[Django-doc] and .update(…)
[Django-doc] that circumvent Django's signal mechanism, and thus can render the database in an inconsistent state. Therefore it might make more sense to just remove the has_toppings
field:
class Pizza(models.Model):
toppings = models.ManyToManyField(Topping)
# no has_toppings
It might make more sense to just annotate your Pizza
querysets. For example with:
from django.db.models import Exists, OuterRef
Pizza.objects.annotate(
has_toppings=Exists(
Pizza.toppings.through.objects.filter(pizza_id=OuterRef('pk'))
)
)
This will generate a query that looks like:
SELECT pizza.id,
EXISTS(
SELECT U0.id, U0.pizza_id, U0.topping_id
FROM pizza_toppings U0
WHERE U0.pizza_id = pizza.id
) AS has_toppings
FROM pizza
You can use this queryset when you access Pizza.objects
, by setting a manager:
from django.db.models import Exists, OuterRef
class PizzaManager(models.Manager):
def get_queryset(self):
return Pizza.objects.annotate(
has_toppings=Exists(
Pizza.toppings.through.objects.filter(pizza_id=OuterRef('pk'))
)
)
class Topping(models.Model):
pass
class Pizza(models.Model):
toppings = models.ManyToManyField(Topping)
objects = PizzaManager()
So we can now for example retrieve all Pizza
s with toppings with:
Pizza.objects.filter(has_toppings=True)
Upvotes: 4
Reputation: 743
You can to it simply checking count of the toppings, by calling:
is_empty = instance.toppings.all().count() == 0
is_empty
would have False
if there are more than 0 toppings, and True
if there are no toppings.
Hope, it helps.
Upvotes: 1