5

It seems Django's queryset.update method execute under transaction.atomic context manager. When would I need to do that explicitly in my code during update? Or what will be the benefits doing it, or problems not doing it?

Code

try: queryset = Model.objects.filter(a=1) if queryset.count(): with transaction.atomic(): queryset.update(a=2) # queryset will [] after this. for item in queryset: item.emit_event('Updated') except: logger.info('Exception') 

My question is do I really need to have transaction.atomic(): here?

Secondly, after .update my queryset gets empty because its a filtered queryset. how to retain the values in my case as I want to emit an event on the individual objects.

1 Answer 1

5

Update 30.08.2023

There is a new queryset method select_for_update in most cases you probably want to use it instead.

First

As docs state

Atomicity is the defining property of database transactions. atomic allows us to create a block of code within which the atomicity on the database is guaranteed. If the block of code is successfully completed, the changes are committed to the database. If there is an exception, the changes are rolled back.

In your example you would need atomic if emit_event is doing something and you want this update to be done only if all emit_event function calls and queryset.update are successfull. But if states of emit_event does not affect your business logic of update, atomic here would be redundant because as you said yourself update has internal atomic.

Second

Querysets are lazy. Which means evaluation of queryset will be done when you are iterating over it. So you need to do something like this. Answering latest comment

try: queryset = Model.objects.filter(a=1) item_ids = list(queryset.values_list('id', flat=True)) # store ids for later if item_ids: # optimzing here instead of queryset.count() so there won't be hit to DB with transaction.atomic(): queryset.update(a=2) # queryset will [] after this. for item in Model.objects.filter(id__in=item_ids): # <- new queryset which gets only updated objects item.emit_event('Updated') except: logger.info('Exception') 

See there we make new queryset when iterating over it to get updated items

Sign up to request clarification or add additional context in comments.

7 Comments

emit_event doesn't have to change the db in order for atomic to be useful. For example, it could make a request to an external api. If that raises an exception then the atomic block will roll back the update. However if it's not in an atomic block then the update will already have been committed.
@Alasdair yes. Thanks. Forgot about other external sources. Like cache, apis, etc, or just function calls.
The problem it will have that items = list(queryset) will save queryset with old values not updated values. Like attribute a would have value 1 rather than 2 after the update.
@Clayton Yes. I thought this is what you wanted. If not. Go with first suggested method.
Thanks, I would like the answer to be precise for the others. Model.objects.filter(a=2) would not be the actual queryset that was updated. Lets say we have 3 rows in db, 2 with a=1 and one with a=2. After updating all 3 rows would have a=2 and refetching Model.objects.filter(a=2) would get me all three rows. Not looking good right? let me check how we can overcome that, Almost there yeah :)
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.