views:

489

answers:

3

I have a simple django's query set like:

qs = AModel.objects.exclude(state="F").order_by("order")

I'd like to use it as follows:

qs[0:3].update(state='F')
expected = qs[3] # throws error here

But last statement throws: "Cannot update a query once a slice has been taken."

How can I duplicate the query set?

+2  A: 

It's the first line throwing the error: you can't do qs[0:3].update(). qs[0:3] is taking a slice; update() is updating the query.

update() is meant for bulk updates, resulting in SQL queries like

UPDATE app_model SET state = 'F' WHERE state <> 'F';

You're trying to update the first three items according to "order", but that can't be done with this type of UPDATE--you can't order or limit an SQL UPDATE. It needs to be written differently, eg.

UPDATE app_model SET state = 'F' WHERE id IN (
    SELECT id FROM app_model WHERE state <> 'F' ORDER BY order LIMIT 3
) AS sub;

but Django can't do that for you.

Glenn Maynard
A: 

There have been some talks about making it possible to do slices of a queryset and then be able to use update on them before, but AFAIK there have never been made anything. I don't think you can copy a slice of a queryset, but in this case you wont need to. If your order is an unique integer, you would be able to do this:

qs = AModel.objects.exclude(state="F").order_by("order")
if len(qs) > 3:
    slice = qs.exclude(order__gt=qs[3])
else:
    slice = qs
slice.update(state='F')

I use exclude to remove the unwanted elements, but this will only work if the order is unique, else you wont be able to know how many you update. In case order is not unique it will still be possible using a second and unique arg in order:

qs = AModel.objects.exclude(state="F").order_by("order", "pk")
if len(qs) > 3:
    slice = qs.exclude(order__gt=qs[3]).exclude(order=qs[3], pk__gt=qs[3])
...
googletorp
Note that this is nonatomic, even in a transaction (at least in Postgres), unless you take out a table lock first.
Glenn Maynard
You can copy a queryset with qs.all(), but that won't help you here.
Glenn Maynard
I meant copying a part of a QuerySet to make a new one that you can run update on. Clarified answer.
googletorp
A: 

You can do this:

qs = AModel.objects.filter(id__in= AModel.objects.exclude(state="F").order_by("order")[10]).update()

stach