2

I'm overriding default save() method in my model and I'm creating a related model instance there:

def save(self, *args, **kwargs):
    super().save(*args, **kwargs)
    parent_folder = None
    if self.parent:
        parent_folder = self.parent.task_folders.all().first()

    folder, created = Folder.objects.get_or_create(
        project=self.project,
        task=self,
        parent=parent_folder,
        defaults={'name': self.name,
                  'project': self.project,
                  'task': self,
                  'creator': self.creator,
                  'parent': parent_folder,
                  'is_root': True
                  })

The question is - should I use transaction.atomic here? How does it work with save() method?

Abdul Aziz Barkat
  • 19,475
  • 3
  • 20
  • 33
Chiefir
  • 2,561
  • 1
  • 27
  • 46

1 Answers1

5

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)":

enter image description here

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":

enter image description here

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)":

enter image description here

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

enter image description here

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)":

enter image description here

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

enter image description here

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:

enter image description here

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:

enter image description here

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:

enter image description here

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:

enter image description here

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:

enter image description here

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:

enter image description here

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:

enter image description here

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:

enter image description here

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:

enter image description here

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:

enter image description here

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:

enter image description here

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

enter image description here

Henry Ecker
  • 34,399
  • 18
  • 41
  • 57
Super Kai - Kazuya Ito
  • 22,221
  • 10
  • 124
  • 129