it-swarm.com.ru

TransactionManagementError "Вы не можете выполнять запросы до конца" атомарного "блока" при использовании сигналов, но только во время модульного тестирования

Я получаю TransactionManagementError при попытке сохранить экземпляр модели Django User, а в сигнале 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.

----------------------------------------------------------------------
142
Gaurav Toshniwal

Я столкнулся с этой же проблемой сам. Это вызвано причудой в том, как транзакции обрабатываются в более новых версиях Django в сочетании с unittest, который преднамеренно вызывает исключение.

У меня был unittest, который проверял, чтобы убедиться, что ограничение уникального столбца было применено, целенаправленно вызывая исключение 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 каждый тест заключен в транзакцию, поэтому, если возникает исключение, он прерывает транзакцию до тех пор, пока вы явно не откатите ее. Поэтому любые дальнейшие операции 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

Это предотвратит намеренно выброшенное исключение, чтобы прервать транзакцию всего юнит-теста.

185
Cerin

Поскольку @mkoistinen никогда не делал его комментарий , ответ, я опубликую его предложение, чтобы людям не приходилось копаться в комментариях.

рассмотрите возможность объявления своего тестового класса как TransactionTestCase, а не просто TestCase.

Из docs : TransactionTestCase может вызывать commit и rollback и наблюдать за эффектами этих вызовов для базы данных.

30
kdazzle

Для меня предложенные исправления не сработали. В моих тестах я открываю некоторые подпроцессы с помощью Popen для анализа/миграции миграций (например, один тест проверяет, нет ли изменений в модели).

Для меня подклассы из SimpleTestCase вместо TestCase сделали свое дело.

Обратите внимание, что SimpleTestCase не позволяет использовать базу данных.

Хотя это не отвечает на первоначальный вопрос, я надеюсь, что это все равно поможет некоторым людям.

1
flix

У меня та же проблема, но with transaction.atomic() и TransactionTestCase у меня не сработали.

python manage.py test -r вместо python manage.py test мне подходит, может быть, порядок выполнения имеет решающее значение

затем я нахожу документ о порядок, в котором выполняются тесты , он упоминает, какой тест будет выполняться первым.

Итак, я использую TestCase для взаимодействия с базой данных, unittest.TestCase для другого простого теста, теперь он работает!

0
Leo

Если вы используете pytest-Django, вы можете передать transaction=True декоратору Django_db, чтобы избежать этой ошибки.

Смотрите https://pytest-Django.readthedocs.io/en/latest/database.html#testing-transactions

Сам Django имеет TransactionTestCase, который позволяет вам тестировать транзакции и очистит базу данных между тестами, чтобы изолировать их. Недостатком этого является то, что эти тесты гораздо медленнее настраиваются из-за требуемой очистки базы данных. pytest-Django также поддерживает этот стиль тестов, который вы можете выбрать, используя аргумент для метки Django_db:

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

Ответ @kdazzle правильный. Я не пробовал, потому что люди говорили, что «класс TestCase Джанго является более часто используемым подклассом TransactionTestCase», поэтому я подумал, что это одно и то же использование. Но блог Джахонгира Рахмонова объяснил это лучше:

класс TestCase оборачивает тесты в два вложенных блока atomic (): один для всего класса и один для каждого теста. Это где TransactionTestCase должен быть использован. Это не обернуть тесты с Блок atomic () и, таким образом, вы можете проверить свои специальные методы, которые требуют сделка без проблем.

Правка: Это не сработало, я думал, да, но нет.

Через 4 года они могли это исправить .......................................

0
Shil Nevado

Я получал эту ошибку при запуске модульных тестов в моей функции create_test_data с использованием Django 1.9.7. Это работало в более ранних версиях Django.

Это выглядело так:

cls.localauth,_ = Organisation.objects.get_or_create(organisation_type=cls.orgtypeLA, name='LA for test', email_general='[email protected]', address='test', postcode='test', telephone='test')
cls.chamber,_ = Organisation.objects.get_or_create(organisation_type=cls.orgtypeC, name='chamber for test', email_general='[email protected]', address='test', postcode='test', telephone='test')
cls.lawfirm,_ = Organisation.objects.get_or_create(organisation_type=cls.orgtypeL, name='lawfirm for test', email_general='[email protected]', 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='[email protected]', 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='[email protected]', 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='[email protected]', address='test', postcode='test', telephone='test', defaults={'active': True})
0
PhoebeB