-1

I want to retry if exception occurs in Django Admin:

  1. When trying to add data:

enter image description here

  1. When trying to change data:

enter image description here

  1. When clicking on Delete on "Change" page:

enter image description here

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

enter image description here

  1. When clicking on Go on "Select to change" page:

enter image description here

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

enter image description here

I have Person model below:

# "store/models.py"

from django.db import models

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

And, Person admin below:

# "store/admin.py"

from django.contrib import admin
from .models import Person

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

So, how can I retry when exception occurs in Django Admin?

Super Kai - Kazuya Ito
  • 22,221
  • 10
  • 124
  • 129

1 Answers1

0

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:

enter image description here

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:

enter image description here

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

enter image description here

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

enter image description here

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:

enter image description here

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

enter image description here

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:

enter image description here

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

enter image description here

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:

enter image description here

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

enter image description here

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:

enter image description here

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

enter image description here

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:

enter image description here

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:

enter image description here

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

# ...
Super Kai - Kazuya Ito
  • 22,221
  • 10
  • 124
  • 129