0

I checked Django repository on GitHub. Then, transaction.atomic(using=using, savepoint=False) and transaction.mark_for_rollback_on_error(using=using) are called in save_base() which is called in save() in class Model(metaclass=ModelBase): as shown below:

# "django/django/db/models/base.py"

class Model(metaclass=ModelBase):
    # ...
    def save(
        self, force_insert=False, force_update=False, using=None, update_fields=None
    ):
        # ...
        self.save_base(
            using=using,
            force_insert=force_insert,
            force_update=force_update,
            update_fields=update_fields,
        )
    # ...    
    def save_base(
        self,
        raw=False,
        force_insert=False,
        force_update=False,
        using=None,
        update_fields=None,
    ):
        # ...  
        # A transaction isn't needed if one query is issued.
        if meta.parents:
            context_manager = transaction.atomic(using=using, savepoint=False) # Here
        else:
            context_manager = transaction.mark_for_rollback_on_error(using=using) # Here
        with context_manager:
            # ...

So, by default, transaction is already used for Django Admin, right?

So, in class Person(models.Model):, if we override save() which calls super().save(*args, **kwargs)` in it as shown below:

# "models.py"

from django.db import models

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

    def save(self, *args, **kwargs):
        super().save(*args, **kwargs) # Here

We don't need to put @transaction.atomic on save() as shown below, right?:

# "models.py"

from django.db import models
from django.db import transaction

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

    @transaction.atomic # Don't need
    def save(self, *args, **kwargs):
        super().save(*args, **kwargs)

Or, we don't need to use with transaction.atomic(): in save() as shown below, right?:

# "models.py"

from django.db import models
from django.db import transaction

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

    def save(self, *args, **kwargs):
        with transaction.atomic(): # Don't need
            super().save(*args, **kwargs)

Or, we don't need to set 'ATOMIC_REQUESTS': True to database settings in settings.py as shown below, right?:

# "settings.py"

DATABASES = {
    'default':{
        'ENGINE':'django.db.backends.postgresql',
        'NAME':'postgres',
        'USER':'postgres',
        'PASSWORD':'admin',
        'HOST':'localhost',
        'PORT':'5432',
        'ATOMIC_REQUESTS': True, # Here
    }
}
Super Kai - Kazuya Ito
  • 22,221
  • 10
  • 124
  • 129

1 Answers1

0

Except Djang Admin Actions, by default transaction is always used for Django Admin whether or not save() is overridden, overridden save() has super().save() and overridden save() has @transaction.non_atomic_requests or not. So, we don't need @transaction.atomic or with transaction.atomic(): for overridden save() and don't need to set 'ATOMIC_REQUESTS': True to database settings in settings.py. *My post explains how to disable transaction for Django Admin and my post explains about Django Admin Actions with non-transaction by default.

I experimented if by default, transaction is always used for Django Admin. I used PostgreSQL. *You can also see my answer which experiments if Atomic Transaction is used for Django View by default or not:

For unoverridden save() as shown below:

# "store/models.py"

from django.db import models

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

    # def save(self, *args, **kwargs): # Here
    #     super().save(*args, **kwargs) # Here

Or for overridden save() with super().save() and @transaction.non_atomic_requests as shown below:

# "store/models.py"

from django.db import models
from django.db import transaction

class Person(models.Model):
    name = models.CharField(max_length=30)
    
    @transaction.non_atomic_requests # Here
    def save(self, *args, **kwargs): # Here
        super().save(*args, **kwargs) # Here

I add John:

enter image description here

Then, two INSERT queries are run between BEGIN and COMMIT queries as shown below. *These logs below are the query logs of PostgreSQL and you can check my answer explaining how to enable and disable query logs on PostgreSQL:

enter image description here

Next, update John to Tom:

enter image description here

Then, one SELECT, UPDATE, and INSERT queries are run between BEGIN and COMMIT queries as shown below:

enter image description here

Next, delete Tom:

enter image description here

Then, one SELECT, INSERT, and DELETE queries are run between BEGIN and COMMIT queries as shown below:

enter image description here

Next, for overridden save() without super().save() but with @transaction.non_atomic_requests:

# "store/models.py"

from django.db import models
from django.db import transaction

class Person(models.Model):
    name = models.CharField(max_length=30)
    
    @transaction.non_atomic_requests # Here
    def save(self, *args, **kwargs): # Here
        pass
        # super().save(*args, **kwargs)

I add John. *Actually, John is not added because overridden save() doesn't have super().save():

enter image description here

Then, one INSERT query is run between BEGIN and COMMIT queries as shown below:

enter image description here

So as the experiments above, we can see that by default, transaction is always used for Django Admin whether or not save() is overridden, overridden save() has super().save() and overridden save() has @transaction.non_atomic_requests. So again, we don't need to use @transaction.atomic or with transaction.atomic(): for overridden save().

In addition, for overridden save() with super().save() and @transaction.atomic as shown below:

# "store/models.py"

from django.db import models
from django.db import transaction

class Person(models.Model):
    name = models.CharField(max_length=30)
    
    @transaction.atomic # Here
    def save(self, *args, **kwargs): # Here
        super().save(*args, **kwargs) # Here

Or, for overridden save() with super().save() and with transaction.atomic(): as shown below:

# "store/models.py"

from django.db import models
from django.db import transaction

class Person(models.Model):
    name = models.CharField(max_length=30)
    
    def save(self, *args, **kwargs): # Here
        
        with transaction.atomic(): # Here
            super().save(*args, **kwargs) # Here

I add John:

enter image description here

Then, one SAVEPOINT and RELEASE SAVEPOINT and two INSERT queries are run between BEGIN and COMMIT queries as shown below:

enter image description here

Next, for overridden save() with super().save() as shown below:

# "store/models.py"

from django.db import models
from django.db import transaction

class Person(models.Model):
    name = models.CharField(max_length=30)
    
    def save(self, *args, **kwargs): # Here
        super().save(*args, **kwargs) # Here

I set 'ATOMIC_REQUESTS': True to 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 John as shown below:

enter image description here

Then, two SELECT and INSERT and one SAVEPOINT and RELEASE SAVEPOINT are run between BEGIN and COMMIT queries as shown below:

enter image description here

Next, for overridden save(), I put both @transaction.atomic and with transaction.atomic(): as shown below:

# "store/models.py"

from django.db import models
from django.db import transaction

class Person(models.Model):
    name = models.CharField(max_length=30)
    
    @transaction.atomic # Here
    def save(self, *args, **kwargs): # Here
        
        with transaction.atomic(): # Here
            super().save(*args, **kwargs) # Here

And, I also set 'ATOMIC_REQUESTS': True to 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 John as shown below:

enter image description here

Then, two SELECT and INSERT and three SAVEPOINT and RELEASE SAVEPOINT are run between BEGIN and COMMIT queries as shown below:

enter image description here

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