django-elasticsearch-dsl
django-elasticsearch-dsl copied to clipboard
Deleting many to many with through table does not work
Hi, I think I found a bug when working with many to many fields with a through table.
Here is the thing, I'm currently working on a project that has an Organization
model, this model has a many to many field related to an Agent
model through a Membership
model.
Membership
is listed as a related model on the corresponding Organization
document and in the get_instances_from_related
is checked if the related instance is a Membership
object.
To add a new member to the organization a call to Membership.objects.create(...)
is made and in this case the indexing process work fine, the new agent is added as a member on the corresponding document. The problem arises when I try to remove a member from the organization using the .delete()
method of the Membership
instance, the agent is not removed from elasticsearch.
I checked and when I remove an agent from the organization, the Organization
object that I get in the get_instances_from_related
method still lists the removed agent as a member of the organization. My guess is that this problem is caused by using a pre_delete
signal for related instances.
Membership.objects.create(...)
and .delete()
are used since django disables the .add()
and .remove()
methods on many to many managers for fields with a through table, also the m2m_changed
signal is not dispatched for this fields.
Thanks in advance for your help and for developing this library, it has been very useful for us.
If someone else is having this problem, it can be solved defining a simple post_delete
signal, this can serve as an example:
from django.db.models.signals import post_delete
from .documents import OrganizationDocument
from .models import Membership, Organization
def update_elasticsearch_after_removing_member(sender, instance, **kwargs):
organization = instance.organization
doc = OrganizationDocument()
doc.update(organization)
post_delete.connect(
update_elasticsearch_after_removing_member, sender=Membership
)
I've also encountered this issue. As a foolproof strategy, while less efficient, I find that inheriting a signal processor from RealTimeSignalProcessor and defining handle_delete as such, addresses the above issue -- note that it is less efficient as delete_related() is called twice (once in handle_pre_delete, and once below), this ensures that any get_instances_from_related calls that query the DB continue to work properly pre-delete AND all objects that need to remove the relevant "through" objects post-delete do so properly:
def handle_delete(self, sender, instance, **kwargs):
"""Handle delete.
Given an individual model instance, delete the object from index.
"""
registry.delete(instance, raise_on_error=False)
# we must call delete related again here (after the delete) to ensure that related instances don't persist the deleted
# object, e.g. when an m2m object is deleted
registry.delete_related(instance)
If someone else has a more modular/efficient solution here, feel free to share! :)