TransactionManagementError “You can't execute queries until the end of the 'atomic' block” while using signals, but only during Unit Testing

后端 未结 11 2034
有刺的猬
有刺的猬 2020-12-02 06:03

I am getting TransactionManagementError when trying to save a Django User model instance and in its post_save signal, I\'m saving some models that have the user as the forei

相关标签:
11条回答
  • 2020-12-02 06:23

    I was getting this error on running unit tests in my create_test_data function using django 1.9.7. It worked in earlier versions of django.

    It looked like this:

    cls.localauth,_ = Organisation.objects.get_or_create(organisation_type=cls.orgtypeLA, name='LA for test', email_general='test@test.com', address='test', postcode='test', telephone='test')
    cls.chamber,_ = Organisation.objects.get_or_create(organisation_type=cls.orgtypeC, name='chamber for test', email_general='test@test.com', address='test', postcode='test', telephone='test')
    cls.lawfirm,_ = Organisation.objects.get_or_create(organisation_type=cls.orgtypeL, name='lawfirm for test', email_general='test@test.com', address='test', postcode='test', telephone='test')
    
    cls.chamber.active = True
    cls.chamber.save()
    
    cls.localauth.active = True
    cls.localauth.save()    <---- error here
    
    cls.lawfirm.active = True
    cls.lawfirm.save()
    

    My solution was to use update_or_create instead:

    cls.localauth,_ = Organisation.objects.update_or_create(organisation_type=cls.orgtypeLA, name='LA for test', email_general='test@test.com', address='test', postcode='test', telephone='test', defaults={'active': True})
    cls.chamber,_ = Organisation.objects.update_or_create(organisation_type=cls.orgtypeC, name='chamber for test', email_general='test@test.com', address='test', postcode='test', telephone='test', defaults={'active': True})
    cls.lawfirm,_ = Organisation.objects.update_or_create(organisation_type=cls.orgtypeL, name='lawfirm for test', email_general='test@test.com', address='test', postcode='test', telephone='test', defaults={'active': True})
    
    0 讨论(0)
  • 2020-12-02 06:24

    I had the same issue.

    In My Case I was doing this

    author.tasks.add(tasks)
    

    so converting it to

    author.tasks.add(*tasks)
    

    Removed that error.

    0 讨论(0)
  • 2020-12-02 06:28

    If using pytest-django you can pass transaction=True to the django_db decorator to avoid this error.

    See https://pytest-django.readthedocs.io/en/latest/database.html#testing-transactions

    Django itself has the TransactionTestCase which allows you to test transactions and will flush the database between tests to isolate them. The downside of this is that these tests are much slower to set up due to the required flushing of the database. pytest-django also supports this style of tests, which you can select using an argument to the django_db mark:

    @pytest.mark.django_db(transaction=True)
    def test_spam():
        pass  # test relying on transactions
    
    0 讨论(0)
  • 2020-12-02 06:28

    The answer of @kdazzle is correct. I didnt try it because people said that 'Django’s TestCase class is a more commonly used subclass of TransactionTestCase' so I thought it was the same use one or another. But the blog of Jahongir Rahmonov explained it better:

    the TestCase class wraps the tests within two nested atomic() blocks: one for the whole class and one for each test. This is where TransactionTestCase should be used. It does not wrap the tests with atomic() block and thus you can test your special methods that require a transaction without any problem.

    EDIT: It didn't work, I thought yes, but NO.

    In 4 years they could fixed this.......................................

    0 讨论(0)
  • 2020-12-02 06:29

    In my case it was caused but not calling super().tearDownClass()

    class TnsFileViewSetTestCase(APITestCase):
        @classmethod
        def tearDownClass(self):
            super().tearDownClass()    # without this line we will get TransactionManagementError
            for tnsfile in TnsFile.objects.all():
                tnsfile.file.delete()
    
    0 讨论(0)
  • 2020-12-02 06:36

    I ran into this same problem myself. This is caused by a quirk in how transactions are handled in the newer versions of Django coupled with a unittest that intentionally triggers an exception.

    I had a unittest that checked to make sure a unique column constraint was enforced by purposefully triggering an IntegrityError exception:

    def test_constraint(self):
        try:
            # Duplicates should be prevented.
            models.Question.objects.create(domain=self.domain, slug='barks')
            self.fail('Duplicate question allowed.')
        except IntegrityError:
            pass
    
        do_more_model_stuff()
    

    In Django 1.4, this works fine. However, in Django 1.5/1.6, each test is wrapped in a transaction, so if an exception occurs, it breaks the transaction until you explicitly roll it back. Therefore, any further ORM operations in that transaction, such as my do_more_model_stuff(), will fail with that django.db.transaction.TransactionManagementError exception.

    Like caio mentioned in the comments, the solution is to capture your exception with transaction.atomic like:

    from django.db import transaction
    def test_constraint(self):
        try:
            # Duplicates should be prevented.
            with transaction.atomic():
                models.Question.objects.create(domain=self.domain, slug='barks')
            self.fail('Duplicate question allowed.')
        except IntegrityError:
            pass
    

    That will prevent the purposefully-thrown exception from breaking the entire unittest's transaction.

    0 讨论(0)
提交回复
热议问题