Trình quản lý giao dịchError, Bạn không thể thực hiện các truy vấn cho đến khi kết thúc khối 'nguyên tử' trong khi sử dụng tín hiệu, nhưng chỉ trong khi Kiểm tra đơn vị


194

Tôi đang nhận được Trình quản lý giao dịchError khi cố gắng lưu phiên bản mô hình Người dùng Django và trong tín hiệu post_save của nó, tôi đang lưu một số mô hình có người dùng làm khóa ngoại.

Bối cảnh và lỗi khá giống với câu hỏi này django TransactionManloymentError khi sử dụng tín hiệu

Tuy nhiên, trong trường hợp này, lỗi chỉ xảy ra trong khi kiểm tra đơn vị .

Nó hoạt động tốt trong thử nghiệm thủ công, nhưng thử nghiệm đơn vị thất bại.

Có điều gì tôi đang thiếu?

Dưới đây là các đoạn mã:

lượt xem

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

Tìm lại:

======================================================================
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.

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

Từ các tài liệu: "Mặt khác, TestCase không cắt bớt các bảng sau khi thử nghiệm. Thay vào đó, nó chứa mã kiểm tra trong một giao dịch cơ sở dữ liệu được khôi phục ở cuối thử nghiệm. Cả hai cam kết rõ ràng như giao dịch. () và những lỗi tiềm ẩn có thể do giao dịch.atomic () được thay thế bằng thao tác nop. Điều này đảm bảo rằng việc khôi phục ở cuối thử nghiệm sẽ khôi phục cơ sở dữ liệu về trạng thái ban đầu. "
Gaurav Toshniwal

6
Tôi tìm thấy vấn đề của tôi. Có một ngoại lệ IntegrityError như thế này "thử: ... ngoại trừ IntegrityError: ..." điều tôi phải làm là sử dụng giao dịch.atomic bên trong khối thử: "thử: với giao dịch.atomic (): .. . ngoại trừ IntegrityError: ... "bây giờ mọi thứ đều hoạt động tốt.
caio

docs.djangoproject.com/en/dev/topics/db/transilities và sau đó tìm kiếm "Gói nguyên tử trong khối thử / ngoại trừ cho phép xử lý tự nhiên các lỗi toàn vẹn:"
CamHart

Câu trả lời:


236

Tôi đã gặp vấn đề tương tự bản thân mình. Điều này được gây ra bởi một sự châm biếm trong cách xử lý các giao dịch trong các phiên bản mới hơn của Django kết hợp với một điều không đáng tin nhất gây ra một ngoại lệ.

Tôi đã kiểm tra một cách rõ ràng nhất để đảm bảo ràng buộc cột duy nhất được thi hành bằng cách kích hoạt một cách có chủ đích một ngoại lệ 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()

Trong Django 1.4, điều này hoạt động tốt. Tuy nhiên, trong Django 1.5 / 1.6, mỗi thử nghiệm được gói trong một giao dịch, vì vậy nếu có ngoại lệ xảy ra, nó sẽ phá vỡ giao dịch cho đến khi bạn hoàn trả lại. Do đó, mọi hoạt động ORM tiếp theo trong giao dịch đó, chẳng hạn như của tôi do_more_model_stuff(), sẽ thất bại với điều đódjango.db.transaction.TransactionManagementError ngoại lệ .

Giống như caio được đề cập trong các bình luận, giải pháp là nắm bắt ngoại lệ của bạn bằng transaction.atomiclike:

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

Điều đó sẽ ngăn ngoại lệ ném có chủ đích phá vỡ toàn bộ giao dịch không đáng tin cậy nhất.


70
Ngoài ra, hãy xem xét việc chỉ khai báo lớp thử nghiệm của bạn là một TransactionTestCase chứ không chỉ là TestCase.
mkoistinen

1
Ồ, tôi tìm thấy tài liệu liên quan từ một câu hỏi khác . Tài liệu ở đây .
yaobin

2
Đối với tôi, tôi đã có một transaction.atomic()khối, nhưng tôi đã gặp lỗi này và tôi không biết tại sao. Tôi đã lấy lời khuyên của câu trả lời này và đặt một khối nguyên tử lồng vào bên trong khối nguyên tử của tôi xung quanh khu vực rắc rối. Sau đó, nó đưa ra một lỗi chi tiết về lỗi toàn vẹn mà tôi gặp phải, cho phép tôi sửa mã của mình và làm những gì tôi đang cố gắng làm.
AlanSE

5
@mkoistinen TestCaseđược thừa hưởng từ TransactionTestCaseđó nên không cần thay đổi điều đó. Nếu bạn không hoạt động trên DB trong sử dụng thử nghiệm SimpleTestCase.
bns

1
@bns bạn đang thiếu điểm bình luận. Có TestCasekế thừa từ TransactionTestCasenhưng hành vi của nó khá khác nhau: nó bao bọc từng phương thức thử nghiệm trong một giao dịch. TransactionTestCasemặt khác, có lẽ được đặt tên sai: nó cắt bớt các bảng để đặt lại db - việc đặt tên dường như phản ánh rằng bạn có thể kiểm tra các giao dịch trong một thử nghiệm, không phải là thử nghiệm được gói như một giao dịch!
CS

48

Vì @mkoistinen không bao giờ đưa ra nhận xét của mình , nên một câu trả lời, tôi sẽ đăng đề xuất của anh ấy để mọi người không phải tìm hiểu ý kiến.

hãy xem xét việc chỉ khai báo lớp thử nghiệm của bạn là một TransactionTestCase chứ không chỉ là TestCase.

Từ các tài liệu : TransactionTestCase có thể gọi cam kết và khôi phục và quan sát ảnh hưởng của các cuộc gọi này trên cơ sở dữ liệu.


2
+1 cho điều này, nhưng, như các tài liệu nói, "Lớp TestCase của Django là một lớp con được sử dụng phổ biến hơn của TransactionTestCase". Để trả lời câu hỏi ban đầu, chúng ta có nên sử dụng SimpleTestCase thay vì TestCase không? SimpleTestCase không có các tính năng cơ sở dữ liệu nguyên tử.
daigorocub

@daigorocub Khi kế thừa từ SimpleTestCase, allow_database_queries = Truephải được thêm vào trong lớp kiểm tra, vì vậy nó không nhổ AssertionError("Database queries aren't allowed in SimpleTestCase...",).
CristiFati

Đây là câu trả lời phù hợp nhất với tôi khi tôi đang thử kiểm tra tính toàn vẹn sẽ được nêu ra và sau đó tôi cần chạy thêm các truy vấn lưu cơ sở dữ liệu
Kim Stacks

8

Nếu sử dụng pytest-django, bạn có thể chuyển transaction=Trueđến trình django_dbtrang trí để tránh lỗi này.

Xem https://pytest-django.readthedocs.io/en/latest/database.html#testing-transilities

Bản thân Django có TransactionTestCase cho phép bạn kiểm tra các giao dịch và sẽ xóa cơ sở dữ liệu giữa các lần kiểm tra để cô lập chúng. Nhược điểm của việc này là các thử nghiệm này được thiết lập chậm hơn nhiều do yêu cầu xóa cơ sở dữ liệu. pytest-django cũng hỗ trợ kiểu thử nghiệm này, bạn có thể chọn sử dụng một đối số cho dấu django_db:

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

Tôi gặp vấn đề với giải pháp này, tôi có dữ liệu ban đầu trong DB của mình (được thêm bởi các lần di chuyển). Giải pháp này làm xáo trộn cơ sở dữ liệu, vì vậy các thử nghiệm khác phụ thuộc vào dữ liệu ban đầu này bắt đầu thất bại.
abumalick

1

Đối với tôi, các bản sửa lỗi được đề xuất không hoạt động. Trong các thử nghiệm của tôi, tôi mở một số quy trình con vớiPopen để phân tích / di chuyển lint (ví dụ: một kiểm tra kiểm tra nếu không có thay đổi mô hình).

Đối với tôi, phân lớp từ SimpleTestCasethay vìTestCase làm thủ thuật.

Lưu ý rằng SimpleTestCase không cho phép sử dụng cơ sở dữ liệu.

Mặc dù điều này không trả lời câu hỏi ban đầu, tôi hy vọng điều này sẽ giúp một số người.


1

Đây là một cách khác để làm điều đó, dựa trên câu trả lời cho câu hỏi này:

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

0

Tôi đã gặp lỗi này khi chạy các bài kiểm tra đơn vị trong hàm created_test_data của mình bằng cách sử dụng django 1.9.7. Nó hoạt động trong các phiên bản trước của django.

Nó trông như thế này:

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

Giải pháp của tôi là sử dụng update_or_create thay thế:

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()cũng hoạt động, có vẻ như đó là .save () nó không giống như bên trong một hàm trang trí giao dịch.atomic () (tôi đã thất bại chỉ với 1 cuộc gọi trong đó).
Timothy Makobu

0

Tôi có cùng một vấn đề, nhưng with transaction.atomic()TransactionTestCasekhông làm việc cho tôi.

python manage.py test -rthay vì python manage.py testlà ok đối với tôi, có lẽ thứ tự thực hiện là rất quan trọng

sau đó tôi tìm thấy một tài liệu về thứ tự trong đó các bài kiểm tra được thực hiện , Nó đề cập đến bài kiểm tra nào sẽ chạy trước.

Vì vậy, tôi sử dụng TestCase để tương tác cơ sở dữ liệu, unittest.TestCaseđối với thử nghiệm đơn giản khác, nó hoạt động ngay bây giờ!


0

Câu trả lời của @kdazzle là chính xác. Tôi đã không thử vì mọi người nói rằng 'Lớp TestCase của Django là một lớp con được sử dụng phổ biến hơn của TransactionTestCase' nên tôi nghĩ rằng nó cũng được sử dụng giống nhau. Nhưng blog của Jahongir Rahmonov đã giải thích điều đó tốt hơn:

lớp TestCase kết thúc các bài kiểm tra trong hai khối nguyên tử () lồng nhau: một cho cả lớp và một cho mỗi bài kiểm tra. Đây là nơi nên sử dụng TransactionTestCase. Nó không bao gồm các bài kiểm tra với khối nguyên tử () và do đó bạn có thể kiểm tra các phương thức đặc biệt của mình yêu cầu giao dịch mà không gặp vấn đề gì.

EDIT: Nó không hoạt động, tôi nghĩ là có, nhưng KHÔNG.

Trong 4 năm họ có thể sửa cái này .......................................


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

Tôi gặp vấn đề tương tự.

Trong trường hợp của tôi, tôi đã làm điều này

author.tasks.add(tasks)

vì vậy chuyển đổi nó thành

author.tasks.add(*tasks)

Đã xóa lỗi đó.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.