Given the (simplified) model, used within a Django Admin app:
models.py
class Appointment(models.Model):
customer = models.ForeignKey(
Customer, blank=False, null=True, on_delete=models.CASCADE
)
pianos = models.ManyToManyField(Piano, blank=True)
def save(self, *args, **kwargs):
# Is it a new Appointment, not an existing one being re-saved?
newAppointment = self.id is None
try:
super().save(*args, **kwargs) # Call the "real" save() method.
if newAppointment:
# Returns empty queryset
for piano in self.pianos.all():
print(piano)
except:
pass
I want to access 'pianos'. If it's a newly created Appointment,
self.pianos returns
<django.db.models.fields.related_descriptors.create_forward_many_to_many_manager..ManyRelatedManager object at 0x7f6f875209a0>
and self.pianos.all() returns an empty queryset, even though the pianos are displayed in the template form that was submitted to initiate the save.
However, if it's an updating of an existing Appointment, 'pianos' returns the data as expected.
Apparently that ManyToMany field doesn't get saved to the db immediately when save() is called. So how can I access the data as shown below? Note that 'pianos' are not instantiated here, they already exist in the database, and only need Appointment to point to one or more of them in its m2m field, as directed from a horizontal_filter defined in admin.py.
I also tried the alternative method of using a post_save signal, with exactly the same result:
@receiver(signals.post_save, sender=Appointment)
def createServiceHistory(sender, instance, created, **kwargs):
if created:
for piano in instance.pianos.all(): #empty queryset
print(piano)
Update: modified to catch m2m_changed instead of post_save:
@receiver(signals.m2m_changed, sender=Appointment)
def createServiceHistory(sender, instance, action, **kwargs):
print(action)
But this signal isn't received.
The docs say that ManyToMany fields are saved using add() rather than save(), but I don't see how that would be applied in this case.
A
ManyToManyassociation can not be created until both instances are saved, that's why it is not yet created while you are in theAppointment.save() method.The
Form.save()method (or theSavebutton, if you are using the Django Admin Interface) saves theAppointment, then associates it with thePianoinstance(s) using.add(). In this case, you can use them2m_changedsignal as @IainShelvington suggested:Note that the
senderfor this signal won't beAppointmentanymore. You should useAppointment.pianos.throughas mentioned in the docs:A simplified example would be:
Additional comments from the OP: