django.db.transaction.commit_on_successデコレータの挙動はいまいち

ちょっとハマったのでメモしておく。

commit_on_successデコレータは、その名の通り関数が成功したらコミットする。その関数の開始時にトランザクションを開始したりはしない。既存のコネクションがあればそれを利用する。したがって、commit_on_successデコレータをネストさせても、意図したようには動かない。ネストした関数が失敗すると、ネストする前に行ったDB操作も一緒にロールバックされる。きちんとソース読んで、それでもわからないなら実際にコード書かないと駄目だな・・・
次のコードだと、test_rollback_nestedだけテストにパスしない。

# -*- encoding: utf-8 -*-
from django.db import models
from django.db import transaction


class Person(models.Model):
    name = models.CharField(u'名前', max_length=100, unique=True)

    def __unicode__(self):
        return self.name


def test_without_tran():
    """
    >>> Person.objects.all().delete()
    >>> test_without_tran()
    >>> Person.objects.all().order_by('name')
    [<Person: Test1>]
    """
    Person.objects.create(name="Test1")



def test_transaction():
    """
    >>> Person.objects.all().delete()
    >>> test_transaction()
    >>> Person.objects.all().order_by('name')
    [<Person: Test1>, <Person: Test2>]
    """
    @transaction.commit_on_success
    def _expect_commit():
        Person.objects.create(name="Test1")
        Person.objects.create(name="Test2")

    try:
        _expect_commit()
    except: pass


def test_rollback():
    """
    >>> Person.objects.all().delete()
    >>> test_rollback()
    >>> Person.objects.all().order_by('name')
    []
    """
    @transaction.commit_on_success
    def _expect_rollback():
        Person.objects.create(name="Test1")
        Person.objects.create(name="Test1")

    try:
        _expect_rollback()
    except: pass


def test_rollback_nested():
    """
    >>> Person.objects.all().delete()
    >>> test_rollback_nested()
    >>> Person.objects.all().order_by('name')
    [<Person: Test10>, <Person: Test11>]
    """
    @transaction.commit_on_success
    def _expect_rollback():
        Person.objects.create(name="Test1")
        Person.objects.create(name="Test1")

    @transaction.commit_on_success
    def _expect_commit():
        Person.objects.create(name="Test10")
        try:
            _expect_rollback()
        except: pass
        Person.objects.create(name="Test11")

    try:
        _expect_commit()
    except: pass