How to run "SELECT FOR UPDATE" for the default "Delete selected" in Django Admin Actions?

283 views Asked by At

I have Person model as shown below:

# "store/models.py"

from django.db import models

class Person(models.Model):
    name = models.CharField(max_length=30)

And, this is Person admin below:

# "store/admin.py"

from django.contrib import admin
from .models import Person

@admin.register(Person)
class PersonAdmin(admin.ModelAdmin):
    pass

Then, when clicking Go to go to delete the selected persons as shown below:

enter image description here

Then, clicking Yes I'm sure to delete the selected persons:

enter image description here

Only DELETE query is run in transaction as shown below:

enter image description here

Now, how can I run SELECT FOR UPDATE for the default "Delete selected" in Django Admin Actions?

1

There are 1 answers

0
Super Kai - Kazuya Ito On

You need to override delete_queryset() with @transaction.atomic as shown below to run SELECT FOR UPDATE for the default "Delete selected" in Django Admin Actions. *print(qs) is important to run SELECT FOR UPDATE:

# "store/admin.py"

from django.contrib import admin
from .models import Person
from django.db import transaction
from django.db import connection

@admin.register(Person)
class PersonAdmin(admin.ModelAdmin):

    @transaction.atomic # Here
    def delete_queryset(self, request, queryset):
        ids = []
        
        for obj in queryset:
            ids.append(obj.id)
        
        qs = queryset.select_for_update().filter(id__in=ids)
        print(qs) # <- Important to run "SELECT FOR UPDATE"
        qs.delete() 

Then, when clicking Go to go to delete the selected persons as shown below:

enter image description here

Then, clicking Yes I'm sure to delete the selected persons:

enter image description here

SELECT FOR UPDATE query and DELETE query are run in transaction as shown below:

enter image description here

In addition, you can use raw queries as shown below if you want to run shorter queries:

# "store/admin.py"

from django.contrib import admin
from .models import Person
from django.db import transaction
from django.db import connection

@admin.register(Person)
class PersonAdmin(admin.ModelAdmin):

    @transaction.atomic # Here
    def delete_queryset(self, request, queryset):
        ids = ()
        qs_length = len(queryset)
        
        if qs_length > 1:
            for obj in queryset:
                ids += (obj.id,)
            ids = str(ids)
        elif qs_length == 1:
            ids = "(" + str(queryset[0].id) + ")"
        else:
            return

        with connection.cursor() as cursor: # Here
            query = "SELECT * FROM store_person WHERE id IN " + ids + " FOR UPDATE"
            cursor.execute(query)
            query = "DELETE FROM store_person WHERE id IN " + ids
            cursor.execute(query)

Then, when clicking Go to go to delete the selected persons as shown below:

enter image description here

Then, clicking Yes I'm sure to delete the selected persons:

enter image description here

shorter SELECT FOR UPDATE query and shorter DELETE query are run in transaction as shown below:

enter image description here

In addition, you can check the default code of delete_queryset() which is not overridden as shown below:

class ModelAdmin(BaseModelAdmin):
    
    # ...
    
    def delete_queryset(self, request, queryset):
        """Given a queryset, delete it from the database."""
        queryset.delete()