Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Many to Many relationship query returns empty queryset on post_save signal but not in django shell

I have a function in my model that send an email on successful save. But i am struggling to figure out why print statement on signal function in the model returns empty queryset but not in the shell. Below is my code. Model code

class Booking(models.Model):
    """Database table for users booking details"""
   //more code here
    items = models.ManyToManyField(BookingItems)
    //more code here
def send_confirmation_email(sender, instance, **kwargs):
    """Function to send email upon successfull booking creation"""
    name = instance.guest_name
    total = instance.final_total,
    email = instance.email
    order_number = instance.reservation_id
    bookingId = instance.id
    itemsData = instance.items.all()
    booking = Booking.objects.get(id=bookingId)
    bookingItems = booking.items.all()
    print(bookingItems)-----------------------------------> this returns empty queryset <QuerySet []>
    context = {"name": name, "items": itemsData, 'total': total,
               'order_number': order_number}
    message = render_to_string(
        "reservations/room_reservation_success.html", context)
    plain_message = strip_tags(message)
    subject = f"Your booking details for {instance.hotel.name}"
    mail.send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [
        email], html_message=message)


post_save.connect(send_confirmation_email, sender=Booking)

Code from shell

>>> book = Booking.objects.filter(id=61)
>>> print(book[0].items.all())
<QuerySet [<BookingItems: One bedroom>, <BookingItems: Two bedroom>]>
>>> 

What could be the issue here. Why is it returning empty set when there's clearly related child items? Kindly assist

like image 580
Muteshi Avatar asked Sep 18 '25 22:09

Muteshi


1 Answers

What could be the issue here. Why is it returning empty set when there's clearly related child items?

There are no related (child) items at the time the post_save trigger runs. Since if you save a many-to-many relation, you first save the object, and then you salve the relation with other objects. You can not do this before, since then it has no primary key yet. The post_save is thus triggered before populating the many-to-many field.

Instead of getting a trigger when the Booking is saved, you can for example perform a query after you add elements to the many-to-many relation with the m2m_changed signal [Django-doc]:

def send_confirmation_email(sender, instance, action, **kwargs):
    if action == 'post_add':
        # …
        pass


m2m_changed.connect(send_confirmation_email, sender=Booking.items.through)

That being said, signals are often considered an anti-pattern [Django-doc]. Especially since there are a lot of ORM calls that can circumvent signals. It might be easier to implement it in the view and thus trigger a function once the entire object is saved.

like image 192
Willem Van Onsem Avatar answered Sep 21 '25 16:09

Willem Van Onsem