尝试保存Django User模型实例时,我收到TransactionManagementError,并且在其post_save信号中,我保存了一些将用户作为外键的模型。
上下文和错误与此问题非常相似django TransactionManagementError when using signals
但是,在这种情况下,发生错误仅在单元测试时。
在手动测试中效果很好,但是单元测试失败。
我有什么想念的吗?
以下是代码段:
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.
----------------------------------------------------------------------
我本人也遇到了同样的问题。这是由于在新版本的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中,每个测试都包装在一个事务中,因此,如果发生异常,它将破坏该事务,直到您显式回滚它为止。因此,该事务中的任何其他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
这将防止故意抛出的异常破坏整个单元测试的事务。
由于@mkoistinen从未提出his comment这样的答案,因此我将发布他的建议,这样人们就不必再挖掘评论了。
只需考虑将您的测试类声明为TransactionTestCase,而不只是TestCase。
从docs:TransactionTestCase可以调用提交和回滚,并观察这些调用对数据库的影响。
如果使用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
对我来说,建议的修补程序不起作用。在我的测试中,我使用Popen
打开了一些子流程来分析/皮棉迁移(例如,一项测试检查是否没有模型更改)。
对我来说,从SimpleTestCase
而不是TestCase
继承确实可以解决问题。
请注意,SimpleTestCase
不允许使用数据库。
虽然这不能回答原始问题,但我希望这对某些人有帮助。
我在使用Django 1.9.7在create_test_data函数中运行单元测试时遇到此错误。它在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})
我有相同的问题,但with transaction.atomic()
和TransactionTestCase
对我不起作用。
python manage.py test -r
而不是python manage.py test
对我来说没问题,也许执行的顺序很关键
然后我找到了有关Order in which tests are executed的文档,其中提到哪个测试将首先运行。
所以,我使用TestCase进行数据库交互,unittest.TestCase
进行其他简单测试,现在可以使用!
@ kdazzle的答案是正确的。我没有尝试过,因为人们说“ Django的TestCase类是TransactionTestCase的一个更常用的子类”,所以我认为这是一种或另一种相同的用法。但是blog of Jahongir Rahmonov解释得更好:
TestCase类将测试包装在两个嵌套的atomic()块中:全班一次,每项测试一次。这是哪里应该使用TransactionTestCase。它不会用atomic()块,因此您可以测试需要的特殊方法交易没有任何问题。
编辑:它没有用,我以为是,但不是。
他们可以在4年内解决此问题.......................................
当我尝试创建具有用户名的用户时,发生单元测试我有此问题(django.db.transaction.TransactionManagementError: An error occurred in the current transaction. You can't execute queries until the end of the 'atomic' block.
):
User.objects.create_user(username)
如果用户名已经存在,我将通过跳过用户创建来解决它。
if not User.objects.filter(username=username):
User.objects.create_user(username)
我有同样的问题。
就我而言,我正在这样做
author.tasks.add(tasks)
因此将其转换为
author.tasks.add(*tasks)
已消除该错误。