TransactionManagementError 신호를 사용하는 동안 '원자'블록이 끝날 때까지 쿼리를 실행할 수 없습니다.


195

Django User 모델 인스턴스를 저장하려고 할 때 TransactionManagementError가 발생하고 post_save 신호에 사용자를 외래 키로 사용하는 일부 모델을 저장하고 있습니다.

문맥과 오류는 신호를 사용할 때 django TransactionManagementError 질문과 매우 유사합니다.

그러나이 경우 오류는 단위 테스트 중에 만 발생합니다 .

수동 테스트에서는 잘 작동하지만 단위 테스트는 실패합니다.

내가 놓친 것이 있습니까?

다음은 코드 스 니펫입니다.

views.py

@csrf_exempt
def mobileRegister(request):
    if request.method == 'GET':
        response = {"error": "GET request not accepted!!"}
        return HttpResponse(json.dumps(response), content_type="application/json",status=500)
    elif request.method == 'POST':
        postdata = json.loads(request.body)
        try:
            # Get POST data which is to be used to save the user
            username = postdata.get('phone')
            password = postdata.get('password')
            email = postdata.get('email',"")
            first_name = postdata.get('first_name',"")
            last_name = postdata.get('last_name',"")
            user = User(username=username, email=email,
                        first_name=first_name, last_name=last_name)
            user._company = postdata.get('company',None)
            user._country_code = postdata.get('country_code',"+91")
            user.is_verified=True
            user._gcm_reg_id = postdata.get('reg_id',None)
            user._gcm_device_id = postdata.get('device_id',None)
            # Set Password for the user
            user.set_password(password)
            # Save the user
            user.save()

signal.py

def create_user_profile(sender, instance, created, **kwargs):
    if created:
        company = None
        companycontact = None
        try:   # Try to make userprofile with company and country code provided
            user = User.objects.get(id=instance.id)
            rand_pass = random.randint(1000, 9999)
            company = Company.objects.get_or_create(name=instance._company,user=user)
            companycontact = CompanyContact.objects.get_or_create(contact_type="Owner",company=company,contact_number=instance.username)
            profile = UserProfile.objects.get_or_create(user=instance,phone=instance.username,verification_code=rand_pass,company=company,country_code=instance._country_code)
            gcmDevice = GCMDevice.objects.create(registration_id=instance._gcm_reg_id,device_id=instance._gcm_reg_id,user=instance)
        except Exception, e:
            pass

tests.py

class AuthTestCase(TestCase):
    fixtures = ['nextgencatalogs/fixtures.json']
    def setUp(self):
        self.user_data={
            "phone":"0000000000",
            "password":"123",
            "first_name":"Gaurav",
            "last_name":"Toshniwal"
            }

    def test_registration_api_get(self):
        response = self.client.get("/mobileRegister/")
        self.assertEqual(response.status_code,500)

    def test_registration_api_post(self):
        response = self.client.post(path="/mobileRegister/",
                                    data=json.dumps(self.user_data),
                                    content_type="application/json")
        self.assertEqual(response.status_code,201)
        self.user_data['username']=self.user_data['phone']
        user = User.objects.get(username=self.user_data['username'])
        # Check if the company was created
        company = Company.objects.get(user__username=self.user_data['phone'])
        self.assertIsInstance(company,Company)
        # Check if the owner's contact is the same as the user's phone number
        company_contact = CompanyContact.objects.get(company=company,contact_type="owner")
        self.assertEqual(user.username,company_contact[0].contact_number)

역 추적:

======================================================================
ERROR: test_registration_api_post (nextgencatalogs.apps.catalogsapp.tests.AuthTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/gauravtoshniwal1989/Developer/Web/Server/ngc/nextgencatalogs/apps/catalogsapp/tests.py", line 29, in test_registration_api_post
    user = User.objects.get(username=self.user_data['username'])
  File "/Users/gauravtoshniwal1989/Developer/Web/Server/ngc/ngcvenv/lib/python2.7/site-packages/django/db/models/manager.py", line 151, in get
    return self.get_queryset().get(*args, **kwargs)
  File "/Users/gauravtoshniwal1989/Developer/Web/Server/ngc/ngcvenv/lib/python2.7/site-packages/django/db/models/query.py", line 301, in get
    num = len(clone)
  File "/Users/gauravtoshniwal1989/Developer/Web/Server/ngc/ngcvenv/lib/python2.7/site-packages/django/db/models/query.py", line 77, in __len__
    self._fetch_all()
  File "/Users/gauravtoshniwal1989/Developer/Web/Server/ngc/ngcvenv/lib/python2.7/site-packages/django/db/models/query.py", line 854, in _fetch_all
    self._result_cache = list(self.iterator())
  File "/Users/gauravtoshniwal1989/Developer/Web/Server/ngc/ngcvenv/lib/python2.7/site-packages/django/db/models/query.py", line 220, in iterator
    for row in compiler.results_iter():
  File "/Users/gauravtoshniwal1989/Developer/Web/Server/ngc/ngcvenv/lib/python2.7/site-packages/django/db/models/sql/compiler.py", line 710, in results_iter
    for rows in self.execute_sql(MULTI):
  File "/Users/gauravtoshniwal1989/Developer/Web/Server/ngc/ngcvenv/lib/python2.7/site-packages/django/db/models/sql/compiler.py", line 781, in execute_sql
    cursor.execute(sql, params)
  File "/Users/gauravtoshniwal1989/Developer/Web/Server/ngc/ngcvenv/lib/python2.7/site-packages/django/db/backends/util.py", line 47, in execute
    self.db.validate_no_broken_transaction()
  File "/Users/gauravtoshniwal1989/Developer/Web/Server/ngc/ngcvenv/lib/python2.7/site-packages/django/db/backends/__init__.py", line 365, in validate_no_broken_transaction
    "An error occurred in the current transaction. You can't "
TransactionManagementError: An error occurred in the current transaction. You can't execute queries until the end of the 'atomic' block.

----------------------------------------------------------------------

문서에서 : "다른 한편으로 TestCase는 테스트 후 테이블을 자르지 않습니다. 대신 테스트가 끝날 때 롤백되는 데이터베이스 트랜잭션에 테스트 코드를 포함합니다. 둘 다 transaction.commit과 같은 명시 적 커밋 transaction.atomic ()에 의해 야기 될 수있는 () 및 암시적인 것들이 nop 연산으로 대체됩니다. 이것은 테스트가 끝났을 때 롤백이 데이터베이스를 초기 상태로 복원하도록합니다. "
Gaurav Toshniwal

6
내 문제를 찾았습니다. "try : ... except IntegrityError : ..."와 같은 IntegrityError 예외가있었습니다. try-block 내부에서 transaction.atomic을 사용하는 것입니다. "try : with transaction.atomic () : .. IntegrityError : ... "를 제외하고 모든 것이 정상적으로 작동합니다.
caio

docs.djangoproject.com/en/dev/topics/db/transactions 를 검색 한 다음 "try / except 블록에서 원자를 래핑하면 무결성 오류를 자연스럽게 처리 할 수 ​​있습니다."
CamHart

답변:


238

나는이 같은 문제를 직접 만났다. 이것은 최신 버전의 Django에서 트랜잭션이 처리되는 방식에 문제가 있기 때문에 의도적으로 예외를 트리거하는 단위 테스트와 결합됩니다.

IntegrityError 예외를 의도적으로 트리거하여 고유 한 열 제약 조건이 적용되었는지 확인한 단위 테스트를 수행했습니다.

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()

Django 1.4에서는 이것이 잘 작동합니다. 그러나 Django 1.5 / 1.6에서는 각 테스트가 트랜잭션으로 래핑되므로 예외가 발생하면 명시 적으로 롤백 할 때까지 트랜잭션이 중단됩니다. 따라서 my와 같은 해당 트랜잭션의 추가 ORM 조작은 예외로 do_more_model_stuff()실패합니다 django.db.transaction.TransactionManagementError.

의견에 언급 된 caio와 같이 해결책은 다음과 transaction.atomic같이 예외를 캡처하는 것입니다.

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

이렇게하면 의도적으로 throw 된 예외가 전체 단위 테스트의 트랜잭션을 위반하지 못하게됩니다.


71
또한 테스트 클래스를 TestCase가 아닌 TransactionTestCase로 선언하는 것을 고려하십시오.
mkoistinen

1
오, 나는 다른 질문 에서 관련 문서를 찾았습니다 . 문서는 여기에 있습니다 .
yaobin 2016 년

2
나를 위해, 나는 이미 했다 transaction.atomic()블록을,하지만 난이 오류가 있고 난 이유를 몰랐다. 이 답변의 조언을 받아 문제 영역 주변의 원자 블록 안에 중첩 된 원자 블록을 넣었습니다 . 그 후, 그것은 내가 쳤던 무결성 오류에 대한 자세한 오류를 일으켜서 코드를 수정하고 내가하려는 일을 할 수있게했습니다.
AlanSE

5
@mkoistinen TestCase은 상속을받지 TransactionTestCase않으므로 변경할 필요가 없습니다. 테스트 용 DB에서 작동하지 않는 경우 SimpleTestCase.
bns

1
@bns 당신은 의견의 요점을 놓치고 있습니다. 네, TestCase상속을 TransactionTestCase받았지만 그 동작은 상당히 다릅니다. 트랜잭션에서 각 테스트 방법을 래핑합니다. TransactionTestCase반면에, 이름이 잘못되어 이름이 바뀔 수 있습니다 .DB를 재설정하기 위해 테이블을 자릅니다. 이름 지정은 테스트가 트랜잭션으로 래핑되는 것이 아니라 테스트 내에서 트랜잭션을 테스트 할 수 있음을 반영하는 것 같습니다!
CS

48

@mkoistinen은 자신의 의견 , 답변을 한 적이 없으므로 사람들이 의견을 파지 않아도되도록 제안을 게시 할 것입니다.

테스트 클래스를 TestCase가 아닌 TransactionTestCase로 선언하는 것을 고려하십시오.

로부터 문서 , • TransactionTestCase 커밋 전화를 롤백하고 데이터베이스에 이러한 호출의 효과를 관찰 할 수 있습니다.


2
+1이지만 문서에서 말하는 것처럼 "Django의 TestCase 클래스는 TransactionTestCase의 더 일반적으로 사용되는 서브 클래스"입니다. 원래 질문에 대답하기 위해 TestCase 대신 SimpleTestCase를 사용해서는 안됩니까? SimpleTestCase에는 원자 데이터베이스 기능이 없습니다.
daigorocub

상속 할 때 @daigorocub SimpleTestCase, allow_database_queries = True그것이 침을하지 않도록, 테스트 클래스 안에 추가해야합니다 AssertionError("Database queries aren't allowed in SimpleTestCase...",).
CristiFati

이 integrityerror가 발생합니다 그리고 이후 내가 쿼리를 저장 이상의 데이터베이스를 실행하는 데 필요한 위해 내가 시험려고 나를 가장 적합한 답변입니다
김 스택을

8

사용하는 경우 당신은 통과 할 수 pytest을-장고 transaction=True받는 사람 django_db이 오류를 방지하기 위해 장식.

https://pytest-django.readthedocs.io/en/latest/database.html#testing-transactions를 참조 하십시오.

장고 자체에는 TransactionTestCase가있어 트랜잭션을 테스트하고 테스트 사이에 데이터베이스를 플러시하여 격리합니다. 이것의 단점은 데이터베이스를 플러시해야하기 때문에 이러한 테스트를 설정하는 것이 훨씬 느리다는 것입니다. pytest-django는 다음과 같은 스타일의 테스트도 지원합니다. django_db 표시에 대한 인수를 사용하여 선택할 수 있습니다.

@pytest.mark.django_db(transaction=True)
def test_spam():
    pass  # test relying on transactions

이 솔루션에 문제가 있었고 DB에 초기 데이터가 있습니다 (이주로 추가됨). 이 솔루션은 데이터베이스를 플러시하므로이 초기 데이터에 따른 다른 테스트가 실패하기 시작했습니다.
abumalick

1

나에게 제안 된 수정 사항이 작동하지 않았습니다. 테스트에서 Popen마이그레이션을 분석 / 린트하기 위해 일부 하위 프로세스를 엽니 다 (예 : 모델 변경이없는 경우 하나의 테스트 확인).

나를 위해 SimpleTestCase대신 서브 클래스를 사용 TestCase하여 트릭을 수행했습니다.

참고 SimpleTestCase데이터베이스를 사용하는 것을 허용하지 않습니다.

이것이 원래의 질문에 대한 답은 아니지만 어쨌든 일부 사람들에게 도움이되기를 바랍니다.


1

이 질문에 대한 답변을 바탕으로 다른 방법을 사용하십시오.

with transaction.atomic():
    self.assertRaises(IntegrityError, models.Question.objects.create, **{'domain':self.domain, 'slug':'barks'})

0

django 1.9.7을 사용하여 create_test_data 함수에서 단위 테스트를 실행할 때이 오류가 발생했습니다. 이전 버전의 장고에서 작동했습니다.

다음과 같이 보였습니다 :

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()

내 솔루션은 대신 update_or_create를 사용하는 것입니다.

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})

1
get_or_create()또한 작동합니다. transaction.atomic () 장식 함수 내부에서 좋아하지 않는 .save () 인 것 같습니다 (광원이 하나만 호출되어 실패했습니다).
Timothy Makobu

0

저도 같은 문제를 가지고 있지만, with transaction.atomic()그리고 TransactionTestCase나를 위해 작동하지 않았다.

python manage.py test -r대신에 python manage.py test나에게는 괜찮습니다. 어쩌면 실행 순서가 중요합니다.

그런 다음 테스트가 실행되는 순서에 대한 문서를 찾습니다. 어떤 테스트가 먼저 실행 될지 언급합니다.

따라서 데이터베이스 상호 작용에 TestCase를 사용 unittest.TestCase하고 다른 간단한 테스트에는 지금 작동합니다!


0

@kdazzle의 대답은 맞습니다. 사람들이 'Django의 TestCase 클래스는 TransactionTestCase의 더 일반적으로 사용되는 하위 클래스'라고 말했기 때문에 시도하지 않았으므로 서로 동일한 사용이라고 생각했습니다. 그러나 Jahongir Rahmonov블로그가 더 잘 설명했습니다.

TestCase 클래스는 두 개의 중첩 된 atomic () 블록 내에서 테스트를 래핑합니다. 하나는 전체 클래스를위한 것이고 다른 하나는 각 테스트를위한 것입니다. 여기서 TransactionTestCase를 사용해야합니다. atomic () 블록으로 테스트를 래핑하지 않으므로 문제없이 트랜잭션이 필요한 특수한 메서드를 테스트 할 수 있습니다.

편집 : 그것은 작동하지 않았다, 나는 그렇다고 생각했지만 NO.

4 년 만에 그들은 이것을 고칠 수있었습니다 ..........................................


0
def test_wrong_user_country_db_constraint(self):
        """
        Check whether or not DB constraint doesnt allow to save wrong country code in DB.
        """
        self.test_user_data['user_country'] = 'XX'
        expected_constraint_name = "country_code_within_list_of_countries_check"

        with transaction.atomic():
            with self.assertRaisesRegex(IntegrityError, expected_constraint_name) as cm:
                get_user_model().objects.create_user(**self.test_user_data)

        self.assertFalse(
            get_user_model().objects.filter(email=self.test_user_data['email']).exists()
        )
with transaction.atomic() seems do the job correct

-4

나는 같은 문제가 있었다.

내 경우에는 이것을하고 있었다

author.tasks.add(tasks)

그래서 그것을

author.tasks.add(*tasks)

그 오류를 제거했습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.