No, you don't need to use Atomic Transaction such as "@transaction.atomic" or "with transaction.atomic():" explicitly for overrided "save()" in Django Model because Atomic Transaction is implicitly used for overrided "save()" by default.
Using PostgreSQL, I experimented if Atomic Transaction is implicitly used for overrided "save()" by default or not with the code below. *"SELECT", "INSERT", "UPDATE" and "DELETE" queries are run by "Animal" class code in the overrided "save()" on "models.py" as shown below:
# "store/models.py"
from django.db import models
class Animal(models.Model):
name = models.CharField(max_length=30)
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
def save(self, *args, **kwargs):
animal = Animal.objects.all() # For "SELECT" query
print(animal) # Needed to run "SELECT" query
Animal(name='Dog').save() # For "INSERT" query
Animal.objects.filter(name="Dog").update(name="Cat") # For "UPDATE" query
Animal.objects.filter(name="Cat").delete() # For "DELETE" query
def __str__(self):
return self.first_name + " " + self.last_name
Then, I add a person to run the overrided "save()" in "Person" class as shown below. *Actually, the person "John Smith" is not added because the overrided "save()" doesn't have "super().save(*args, **kwargs)":

Now, between "BEGIN" and "COMMIT" queries, "SELECT", "INSERT", "UPDATE" and "DELETE" queries are run by "Animal" class code in the overrided "save()". So, Atomic Transaction is implicitly used for overrided save() by default. *These logs below are the query logs of PostgreSQL. You can check On PostgreSQL, how to log SQL queries with transaction queries such as "BEGIN" and "COMMIT":

In addition, at the end of the overrided "save()", I add "super().save(*args, **kwargs)" which runs "INSERT" query as shown below:
# "store/models.py"
from django.db import models
class Animal(models.Model):
name = models.CharField(max_length=30)
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
def save(self, *args, **kwargs):
animal = Animal.objects.all() # For "SELECT" query
print(animal) # Needed to run "SELECT" query
Animal(name='Dog').save() # For "INSERT" query
Animal.objects.filter(name="Dog").update(name="Cat") # For "UPDATE" query
Animal.objects.filter(name="Cat").delete() # For "DELETE" query
super().save(*args, **kwargs) # For "INSERT" query
def __str__(self):
return self.first_name + " " + self.last_name
Then, I add a person to run the overrided "save()" in "Person" class as shown below. *This time, the person "John Smith" is added because the overrided "save()" has "super().save(*args, **kwargs)":

Now, "INSERT" query is run by "super().save(*args, **kwargs)" after "DELETE" query between "BEGIN" and "COMMIT" queries as shown below:

Next, at the beginning of the overrided "save()", I add "super().save(*args, **kwargs)" which runs "INSERT" query as shown below:
# "store/models.py"
from django.db import models
class Animal(models.Model):
name = models.CharField(max_length=30)
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
def save(self, *args, **kwargs):
super().save(*args, **kwargs) # For "INSERT" query
animal = Animal.objects.all() # For "SELECT" query
print(animal) # Needed to run "SELECT" query
Animal(name='Dog').save() # For "INSERT" query
Animal.objects.filter(name="Dog").update(name="Cat") # For "UPDATE" query
Animal.objects.filter(name="Cat").delete() # For "DELETE" query
def __str__(self):
return self.first_name + " " + self.last_name
Then, I add a person to run the overrided "save()" in "Person" class as shown below. *This time, the person "John Smith" is added because the overrided "save()" has "super().save(*args, **kwargs)":

Now, "INSERT" query is run by "super().save(*args, **kwargs)" before "SELECT" query between "BEGIN" and "COMMIT" queries as shown below:

Next, for the overrided "save()", I put "@transaction.atomic" decorator as shown below:
# "store/models.py"
from django.db import models
class Animal(models.Model):
name = models.CharField(max_length=30)
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
@transaction.atomic # Here
def save(self, *args, **kwargs):
animal = Animal.objects.all() # For "SELECT" query
print(animal) # Needed to run "SELECT" query
Animal(name='Dog').save() # For "INSERT" query
Animal.objects.filter(name="Dog").update(name="Cat") # For "UPDATE" query
Animal.objects.filter(name="Cat").delete() # For "DELETE" query
def __str__(self):
return self.first_name + " " + self.last_name
Or, for the overrided "save()", I put "with transaction.atomic():" as shown below:
# "store/models.py"
from django.db import models
class Animal(models.Model):
name = models.CharField(max_length=30)
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
def save(self, *args, **kwargs):
with transaction.atomic(): # Here
animal = Animal.objects.all() # For "SELECT" query
print(animal) # Needed to run "SELECT" query
Animal(name='Dog').save() # For "INSERT" query
Animal.objects.filter(name="Dog").update(name="Cat") # For "UPDATE" query
Animal.objects.filter(name="Cat").delete() # For "DELETE" query
def __str__(self):
return self.first_name + " " + self.last_name
Then, I add a person to run the overrided "save()" in "Person" class as shown below:

Now, "SAVEPOINT" and "RELEASE SAVEPOINT" queries are run by "@transaction.atomic" decorator or "with transaction.atomic():" after "BEGIN" query and after "DELETE" query respectively between "BEGIN" and "COMMIT" queries as shown below:

Next, for the overrided "save()" as shown below:
# "store/models.py"
from django.db import models
class Animal(models.Model):
name = models.CharField(max_length=30)
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
def save(self, *args, **kwargs):
animal = Animal.objects.all() # For "SELECT" query
print(animal) # Needed to run "SELECT" query
Animal(name='Dog').save() # For "INSERT" query
Animal.objects.filter(name="Dog").update(name="Cat") # For "UPDATE" query
Animal.objects.filter(name="Cat").delete() # For "DELETE" query
def __str__(self):
return self.first_name + " " + self.last_name
I put 'ATOMIC_REQUESTS': True to my PostgreSQL settings in "settings.py":
# "core/settings.py"
DATABASES = {
'default':{
'ENGINE':'django.db.backends.postgresql',
'NAME':'postgres',
'USER':'postgres',
'PASSWORD':'admin',
'HOST':'localhost',
'PORT':'5432',
'ATOMIC_REQUESTS': True, # Here
}
}
Then, I add a person to run the overrided "save()" in "Person" class as shown below:

Now, "SAVEPOINT" and "RELEASE SAVEPOINT" queries are run by 'ATOMIC_REQUESTS': True before "SELECT" query run by "Animal" class code and before "COMMIT" query respectively and 2 "SELECT" queries in blue are added between "BEGIN" and "COMMIT" queries as shown below:

Next, for the overrided "save()", I put both "@transaction.atomic" decorator and "with transaction.atomic():" as shown below:
# "store/models.py"
from django.db import models
class Animal(models.Model):
name = models.CharField(max_length=30)
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
@transaction.atomic # Here
def save(self, *args, **kwargs):
with transaction.atomic(): # Here
animal = Animal.objects.all() # For "SELECT" query
print(animal) # Needed to run "SELECT" query
Animal(name='Dog').save() # For "INSERT" query
Animal.objects.filter(name="Dog").update(name="Cat") # For "UPDATE" query
Animal.objects.filter(name="Cat").delete() # For "DELETE" query
def __str__(self):
return self.first_name + " " + self.last_name
And, I also put 'ATOMIC_REQUESTS': True to my PostgreSQL settings in "settings.py" as shown below:
# "core/settings.py"
DATABASES = {
'default':{
'ENGINE':'django.db.backends.postgresql',
'NAME':'postgres',
'USER':'postgres',
'PASSWORD':'admin',
'HOST':'localhost',
'PORT':'5432',
'ATOMIC_REQUESTS': True, # Here
}
}
Then, I add a person to run the overrided "save()" in "Person" class as shown below:

Now, between "BEGIN" and "COMMIT" queries, 3 "SAVEPOINT" and 3 "RELEASE SAVEPOINT" queries are run by "@transaction.atomic" decorator, "with transaction.atomic():" and 'ATOMIC_REQUESTS': True and 2 "SELECT" queries in blue are added as shown below:

In addition again, using PostgreSQL, I also experimented if Atomic Transaction is implicitly used for Views by default or not with the code below. *"SELECT", "INSERT", "UPDATE" and "DELETE" queries are run by "Animal" class code in "test()" view on "views.py" as shown below:
# "store/views.py"
from django.http import HttpResponse
from .models import Animal
def test(request):
animal = Animal.objects.all() # For "SELECT" query
print(animal) # Needed to run "SELECT" query
Animal(name='Dog').save() # For "INSERT" query
Animal.objects.filter(name="Dog").update(name="Cat") # For "UPDATE" query
Animal.objects.filter(name="Cat").delete() # For "DELETE"
return HttpResponse("Test")
And, this is "urls.py" below:
# "store/urls.py"
from django.urls import path
from . import views
app_name = "store"
urlpatterns = [
path('test/', views.test, name="test"),
]
Then, I open the url "http://localhost:8000/store/test/" to run "test" view as shown below:

Now, between "BEGIN" and "COMMIT" queries, only "DELETE" query is run by "Animal" class code in "test()" view., So, Atomic Transaction is not implicitly used for Views by default. *These logs below are the query logs of PostgreSQL:

Next, for "test()" view, I put "@transaction.atomic" decorator as shown below:
# "store/views.py"
from django.http import HttpResponse
from .models import Animal
@transaction.atomic # Here
def test(request):
animal = Animal.objects.all() # For "SELECT" query
print(animal) # Needed to run "SELECT" query
Animal(name='Dog').save() # For "INSERT" query
Animal.objects.filter(name="Dog").update(name="Cat") # For "UPDATE" query
Animal.objects.filter(name="Cat").delete() # For "DELETE"
return HttpResponse("Test")
Or, for "test()" view, I put "with transaction.atomic():" as shown below:
# "store/views.py"
from django.http import HttpResponse
from .models import Animal
def test(request):
with transaction.atomic(): # Here
animal = Animal.objects.all() # For "SELECT" query
print(animal) # Needed to run "SELECT" query
Animal(name='Dog').save() # For "INSERT" query
Animal.objects.filter(name="Dog").update(name="Cat") # For "UPDATE" query
Animal.objects.filter(name="Cat").delete() # For "DELETE"
return HttpResponse("Test")
Or, for "test()" view, I put 'ATOMIC_REQUESTS': True to my PostgreSQL settings in "settings.py" as shown below:
# "core/settings.py"
DATABASES = {
'default':{
'ENGINE':'django.db.backends.postgresql',
'NAME':'postgres',
'USER':'postgres',
'PASSWORD':'admin',
'HOST':'localhost',
'PORT':'5432',
'ATOMIC_REQUESTS': True, # Here
}
}
Then, I open the url "http://localhost:8000/store/test/" to run "test" view as shown below:

Now, between "BEGIN" and "COMMIT" queries, "SELECT", "INSERT", "UPDATE" and "DELETE" queries are run by "Animal" class code in "test()" view. So now, Atomic Transaction works:

Next, for "test()" view, I put both "@transaction.atomic" decorator and "with transaction.atomic():" as shown below:
# "store/views.py"
from django.http import HttpResponse
from .models import Animal
@transaction.atomic # Here
def test(request):
with transaction.atomic(): # Here
animal = Animal.objects.all() # For "SELECT" query
print(animal) # Needed to run "SELECT" query
Animal(name='Dog').save() # For "INSERT" query
Animal.objects.filter(name="Dog").update(name="Cat") # For "UPDATE" query
Animal.objects.filter(name="Cat").delete() # For "DELETE"
return HttpResponse("Test")
And, I also put 'ATOMIC_REQUESTS': True to my PostgreSQL settings in "settings.py" as shown below:
# "core/settings.py"
DATABASES = {
'default':{
'ENGINE':'django.db.backends.postgresql',
'NAME':'postgres',
'USER':'postgres',
'PASSWORD':'admin',
'HOST':'localhost',
'PORT':'5432',
'ATOMIC_REQUESTS': True, # Here
}
}
Then, I open the url "http://localhost:8000/store/test/" to run "test" view as shown below:

Now, between "BEGIN" and "COMMIT" queries, 2 "SAVEPOINT" and 2 "RELEASE SAVEPOINT" queries are run as shown below:
