user3064538
user3064538

Reputation:

Pass a queryset as the argument to __in in django?

I have a list of object ID's that I am getting from a query in an model's method, then I'm using that list to delete objects from a different model:

class SomeObject(models.Model):
    # [...]
    def do_stuff(self, some_param):
        # [...]
        ids_to_delete = {item.id for item in self.items.all()}
        other_object = OtherObject.objects.get_or_create(some_param=some_param)
        other_object.items.filter(item_id__in=ids_to_delete).delete()

What I don't like is that this takes 2 queries (well, technically 3 for the get_or_create() but in the real code it's actually .filter(some_param=some_param).first() instead of the .get(), so I don't think there's any easy way around that).

How do I pass in an unevaluated queryset as the argument to an __in lookup?

I would like to do something like:

ids_to_delete = self.items.all().values("id")
other_object.items.filter(item_id__in=ids_to_delete).delete()

Upvotes: 1

Views: 2380

Answers (2)

user3064538
user3064538

Reputation:

I should've tried the code sample I posted, you can in fact do this. It's given as an example in the documentation, but it says "be cautious about using nested queries and understand your database server’s performance characteristics" and recommends against doing this, casting the subquery into a list:

values = Blog.objects.filter(
        name__contains='Cheddar').values_list('pk', flat=True)
entries = Entry.objects.filter(blog__in=list(values))

Upvotes: 0

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 476557

You can, pass a QuerySet to the query:

other_object.items.filter(id__in=self.items.all()).delete()

this will transform it in a subquery. But not all databases, especially MySQL ones, are good with such subqueries. Furthermore Django handles .delete() manually. It will thus make a query to fetch the primary keys of the items, and then trigger the delete logic (and also remove items that have a CASCADE dependency). So .delete() is not done as one query, but at least two queries, and often a larger amount due to ForeignKeys with an on_delete trigger.

Note however that you here remove Item objects, not "unlink" this from the other_object. For this .remove(…) [Django-doc] can be used.

Upvotes: 3

Related Questions