You can use retry()
below to retry Django Admin 0 or more times with 0 or more interval seconds:
# "store/admin.py"
from django.db import DatabaseError, OperationalError, transaction, router
from django.contrib import messages, admin
from django.shortcuts import redirect
from time import sleep
from .models import Person
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_protect
csrf_protect_m = method_decorator(csrf_protect)
def retry(count, interval=1):
def _retry(func):
def core(*args, **kwargs):
nonlocal count
if callable(count):
count = 2
for i in range(count+1):
try:
return func(*args, **kwargs)
except DatabaseError as e:
print(e)
if i == count:
messages.error(args[1], "Error occurred, try again!")
return redirect("/".join(args[1].path.split('/')[0:4]))
sleep(interval)
return core
if callable(count):
return _retry(count)
return _retry
# ...
Then, using @retry(4, 2)
for the overridden changeform_view() can retry adding and changing data 4 times with 2 interval seconds in addition to a normal try each time exception occurs as shown below. *@retry(4, 2)
must be the 1st decorator from the top to work properly if there are multiple decorators:
# "store/admin.py"
from django.db import DatabaseError, OperationalError, transaction, router
from django.contrib import messages, admin
from django.shortcuts import redirect
from time import sleep
from .models import Person
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_protect
csrf_protect_m = method_decorator(csrf_protect)
def retry(count, interval=1):
# ...
@admin.register(Person)
class PersonAdmin(admin.ModelAdmin):
@retry(4, 2) # Must be the 1st decorator to work properly
@csrf_protect_m
def changeform_view(self, request, object_id=None, form_url="", extra_context=None):
with transaction.atomic(using=router.db_for_write(self.model)):
return self._changeform_view(request, object_id, form_url, extra_context)
So, let's raise OperationalError exception in the overridden response_add() and response_change() as shown below:
# ...
@admin.register(Person)
class PersonAdmin(admin.ModelAdmin):
@retry(4, 2) # Must be the 1st decorator to work properly
@csrf_protect_m
def changeform_view(self, request, object_id=None, form_url="", extra_context=None):
with transaction.atomic(using=router.db_for_write(self.model)):
return super()._changeform_view(request, object_id, form_url, extra_context)
def response_add(self, request, obj, post_url_continue=None):
raise OperationalError("Exception occurs") # Here
return super().response_add(request, obj, post_url_continue)
def response_change(self, request, obj):
raise OperationalError("Exception occurs") # Here
return super().response_change(request, obj)
Now, if trying to add data:

Adding data is retried 4 times in addition to a normal try as shown below:
Exception occurs # Normal try
Exception occurs # 1st retry
Exception occurs # 2st retry
Exception occurs # 3rd retry
Exception occurs # 4th retry
[30/Dec/2022 22:01:41] "POST /admin/store/person/add/ HTTP/1.1" 302 0
[30/Dec/2022 22:01:41] "GET /admin/store/person HTTP/1.1" 301 0
[30/Dec/2022 22:01:41] "GET /admin/store/person/ HTTP/1.1" 200 21364
[30/Dec/2022 22:01:41] "GET /admin/jsi18n/ HTTP/1.1" 200 3195
And, transaction is run 5 times (The light blue one is a normal try and the yellow ones are 4 retries) according to the PostgreSQL query logs below. *You can check how to log PostgreSQL queries:

Then finally, the page is redirected to "Select to change" page without adding data as shown below:

Next, if trying to change data from John
to David
:

Changing data is retried 4 times in addition to a normal try as shown below:
Exception occurs # Normal try
Exception occurs # 1st retry
Exception occurs # 2st retry
Exception occurs # 3rd retry
Exception occurs # 4th retry
[30/Dec/2022 23:02:49] "POST /admin/store/person/678/change/ HTTP/1.1" 302 0
[30/Dec/2022 23:02:49] "GET /admin/store/person HTTP/1.1" 301 0
[30/Dec/2022 23:02:50] "GET /admin/store/person/ HTTP/1.1" 200 22512
[30/Dec/2022 23:02:50] "GET /admin/jsi18n/ HTTP/1.1" 200 3195
And, transaction is run 5 times (The light blue one is a normal try and the yellow ones are 4 retries) according to the PostgreSQL query logs below:

Then finally, the page is redirected to "Select to change" page without changing data as shown below:

Next, using @retry
for the overridden delete_view() can retry deleting data 2 times with 1 interval second by default in addition to a normal try each time exception occurs as shown below. *@retry
must be the 1st decorator from the top if there are multiple decorators:
# "store/admin.py"
from django.db import DatabaseError, OperationalError, transaction, router
from django.contrib import messages, admin
from django.shortcuts import redirect
from time import sleep
from .models import Person
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_protect
csrf_protect_m = method_decorator(csrf_protect)
def retry(count, interval=1):
# ...
@admin.register(Person)
class PersonAdmin(admin.ModelAdmin):
@retry # Must be the 1st decorator to work properly
@csrf_protect_m
def delete_view(self, request, object_id, extra_context=None):
with transaction.atomic(using=router.db_for_write(self.model)):
return self._delete_view(request, object_id, extra_context)
So, let's raise OperationalError
exception in the overridden response_delete() as shown below:
# ...
@admin.register(Person)
class PersonAdmin(admin.ModelAdmin):
@retry # Must be the 1st decorator to work properly
@csrf_protect_m
def delete_view(self, request, object_id, extra_context=None):
with transaction.atomic(using=router.db_for_write(self.model)):
return super()._delete_view(request, object_id, extra_context)
def response_delete(self, request, obj_display, obj_id):
raise OperationalError("Exception occurs") # Here
return super().response_delete(request, obj_display, obj_id)
Now, if clicking on Delete on "Change" page:

Then clicking on Yes, I'm sure to try deleting data:

Deleting data is retried 2 times in addition to a normal try as shown below:
Exception occurs # Normal try
Exception occurs # 1st retry
Exception occurs # 2st retry
[30/Dec/2022 23:30:30] "POST /admin/store/person/680/delete/ HTTP/1.1" 302 0
[30/Dec/2022 23:30:30] "GET /admin/store/person HTTP/1.1" 301 0
[30/Dec/2022 23:30:30] "GET /admin/store/person/ HTTP/1.1" 200 22513
[30/Dec/2022 23:30:31] "GET /admin/jsi18n/ HTTP/1.1" 200 3195
And, transaction is run 3 times (The light blue one is a normal try and the yellow ones are 2 retries) according to the PostgreSQL query logs below:

Then finally, the page is redirected to "Select to change" page without deleting data as shown below:

Finally, using @retry
for the overridden delete_queryset() can retry deleting data 2 times with 1 interval second by default in addition to a normal try each time exception occurs as shown below. *By default, transaction is not used for Django Admin Actions and @retry
must be the 1st decorator from the top to work properly if there are multiple decorators:
# "store/admin.py"
from django.db import DatabaseError, OperationalError, transaction, router
from django.contrib import messages, admin
from django.shortcuts import redirect
from time import sleep
from .models import Person
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_protect
csrf_protect_m = method_decorator(csrf_protect)
def retry(count, interval=1):
# ...
@admin.register(Person)
class PersonAdmin(admin.ModelAdmin):
@retry # Must be the 1st decorator to work properly
@transaction.atomic
def delete_queryset(self, request, queryset):
queryset.delete()
So, let's raise OperationalError
exception in the overridden delete_queryset()
as shown below:
# ...
@admin.register(Person)
class PersonAdmin(admin.ModelAdmin):
@retry # Must be the 1st decorator to work properly
@transaction.atomic
def delete_queryset(self, request, queryset):
queryset.delete()
raise OperationalError("Exception occurs") # Here
Now, if clicking on Go on "Change" page on "Select to change" page:

Then clicking on Yes, I'm sure to try deleting data:

Deleting data is retried 2 times in addition to a normal try as shown below:
Exception occurs # Normal try
Exception occurs # 1st retry
Exception occurs # 2st retry
[31/Dec/2022 00:18:54] "POST /admin/store/person/ HTTP/1.1" 302 0
[31/Dec/2022 00:18:55] "GET /admin/store/person/ HTTP/1.1" 200 23013
[31/Dec/2022 00:18:55] "GET /admin/jsi18n/ HTTP/1.1" 200 3195
And, transaction is run 3 times (The light blue one is a normal try and the yellow ones are 2 retries) according to the PostgreSQL query logs below:

Then finally, the page is redirected to "Select to change" page without deleting data but I don't know how to remove the default message "Successfully deleted 2 persons." as shown below:

In addition, if you want to retry 3 times with 0.5 interval seconds by default with @retry
, you need to change count = 2
to count = 3
and interval=1
to interval=0.5
respectively as shown below:
# "store/admin.py"
# ...
# ↓ Here
def retry(count, interval=0.5):
def _retry(func):
def core(*args, **kwargs):
nonlocal count
if callable(count):
count = 3 # <= Here
for i in range(count+1):
try:
return func(*args, **kwargs)
except DatabaseError as e:
print(e)
if i == count:
messages.error(args[1], "Error occurred, try again!")
return redirect("/".join(args[1].path.split('/')[0:4]))
sleep(interval)
return core
if callable(count):
return _retry(count)
return _retry
# ...