TransactionManagementError "Non è possibile eseguire query fino alla fine del blocco" atomico "" durante l'utilizzo dei segnali, ma solo durante il Test unità


196

Ricevo TransactionManagementError quando provo a salvare un'istanza del modello Utente Django e nel suo segnale post_save, sto salvando alcuni modelli che hanno l'utente come chiave esterna.

Il contesto e l'errore sono abbastanza simili a questa domanda django TransactionManagementError quando si usano i segnali

Tuttavia, in questo caso, l'errore si verifica solo durante il test dell'unità .

Funziona bene nei test manuali, ma i test unitari falliscono.

C'è qualcosa che mi manca?

Ecco i frammenti di codice:

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)

Rintracciare:

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

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

Dai documenti: "Un TestCase, d'altra parte, non tronca le tabelle dopo un test. Al contrario, racchiude il codice di test in una transazione di database che viene ripristinata alla fine del test. Entrambi i commit espliciti come transaction.commit () e quelli impliciti che possono essere causati da transazioni.atomic () vengono sostituiti con un'operazione nop. Ciò garantisce che il rollback alla fine del test ripristini il database al suo stato iniziale. "
Gaurav Toshniwal,

6
Ho trovato il mio problema C'era un'eccezione IntegrityError come questa "prova: ... tranne IntegrityError: ..." quello che dovevo fare è usare la transazione.atomica all'interno del blocco di prova: "prova: con transazione.atomica (): .. eccetto IntegrityError: ... "ora tutto funziona bene.
caio,

docs.djangoproject.com/en/dev/topics/db/transactions e quindi cercare "L'avvolgimento atomico in un blocco try / tranne consente la gestione naturale degli errori di integrità:"
CamHart

Risposte:


238

Mi sono imbattuto in questo stesso problema da solo. Ciò è causato da una stranezza nel modo in cui le transazioni vengono gestite nelle versioni più recenti di Django accoppiate a un unittest che attiva intenzionalmente un'eccezione.

Ho avuto un unittest che ha verificato che un vincolo di colonna univoco fosse applicato attivando intenzionalmente un'eccezione 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()

In Django 1.4, funziona perfettamente. Tuttavia, in Django 1.5 / 1.6, ogni test è racchiuso in una transazione, quindi se si verifica un'eccezione, interrompe la transazione fino a quando non la ripristini esplicitamente. Pertanto, qualsiasi ulteriore operazione ORM in quella transazione, come la mia do_more_model_stuff(), fallirà con tale django.db.transaction.TransactionManagementErroreccezione.

Come caio menzionato nei commenti, la soluzione è catturare la tua eccezione con 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

Ciò eviterà che l'eccezione generata intenzionalmente interrompa l'intera transazione unittest.


71
Considera anche di dichiarare la tua classe di test come TransactionTestCase piuttosto che come TestCase.
mkoistinen,

1
Oh, ho trovato il documento correlato da un'altra domanda . Il documento è qui .
Yaobin,

2
Per me, avevo già un transaction.atomic()blocco, ma ho avuto questo errore e non avevo idea del perché. Ho seguito il consiglio di questa risposta e ho inserito un blocco atomico annidato all'interno del mio blocco atomico attorno all'area problematica. Successivamente, ha fornito un errore dettagliato dell'errore di integrità che ho riscontrato, permettendomi di correggere il mio codice e fare quello che stavo cercando di fare.
AlanSE,

5
@mkoistinen TestCaseeredita TransactionTestCasequindi non è necessario modificarlo. Se non si utilizza DB nel test, utilizzare SimpleTestCase.
BN

1
@bns ti manca il punto del commento. Sì TestCaseeredita da TransactionTestCasema il suo comportamento è piuttosto diverso: avvolge ogni metodo di prova in una transazione. TransactionTestCased'altra parte, è forse chiamato in modo fuorviante: tronca le tabelle per ripristinare il db - la denominazione sembra riflettere che è possibile testare le transazioni all'interno di un test, non che il test sia racchiuso in una transazione!
CS

48

Poiché @mkoistinen non ha mai fatto il suo commento , una risposta, posterò il suo suggerimento in modo che le persone non debbano scavare nei commenti.

considera solo la dichiarazione della tua classe di test come TransactionTestCase piuttosto che come TestCase.

Dai documenti : Un TransactionTestCase può chiamare commit e rollback e osservare gli effetti di queste chiamate sul database.


2
+1 per questo, ma, come dicono i documenti, "la classe TestCase di Django è una sottoclasse più comunemente usata di TransactionTestCase". Per rispondere alla domanda originale, non dovremmo usare SimpleTestCase invece di TestCase? SimpleTestCase non ha le funzionalità del database atomico.
daigorocub,

@daigorocub Quando si eredita da SimpleTestCase, allow_database_queries = Truedeve essere aggiunto all'interno della classe test, quindi non sputa un AssertionError("Database queries aren't allowed in SimpleTestCase...",).
CristiFati,

Questa è la risposta che funziona meglio per me poiché stavo provando a testare l'integrità del problema verrà sollevata e successivamente ho dovuto eseguire più query di salvataggio del database
Kim Stacks

8

Se usi pytest-django puoi passare transaction=Trueal django_dbdecoratore per evitare questo errore.

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

Django stesso ha TransactionTestCase che consente di testare le transazioni e svuota il database tra i test per isolarle. Il rovescio della medaglia di questo è che questi test sono molto più lenti da configurare a causa del necessario svuotamento del database. pytest-django supporta anche questo stile di test, che è possibile selezionare usando un argomento per il segno django_db:

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

Ho avuto un problema con questa soluzione, avevo i dati iniziali nel mio DB (aggiunti dalle migrazioni). Questa soluzione scarica il database, quindi altri test dipendenti da questi dati iniziali hanno iniziato a fallire.
abumalick,

2

Ecco un altro modo per farlo, basato sulla risposta a questa domanda:

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

1

Per me, le correzioni proposte non hanno funzionato. Nei miei test, apro alcuni sottoprocessi con Popenper analizzare / sfogliare le migrazioni (ad esempio un test verifica se non ci sono modifiche al modello).

Per me, subclassare SimpleTestCaseinvece di TestCasefare il trucco.

Si noti che SimpleTestCasenon consente di utilizzare il database.

Anche se questo non risponde alla domanda originale, spero che ciò aiuti comunque alcune persone.


0

Stavo ottenendo questo errore durante l'esecuzione di unit test nella mia funzione create_test_data utilizzando django 1.9.7. Ha funzionato nelle versioni precedenti di Django.

Sembrava così:

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

La mia soluzione era usare update_or_create invece:

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()funziona anche, sembra che sia .save () che non gli piace all'interno di una funzione decorata transazione.atomic () (la mia non è riuscita con solo 1 chiamata lì dentro).
Timothy Makobu,

0

Ho lo stesso problema, ma with transaction.atomic()e TransactionTestCasenon ha funzionato per me.

python manage.py test -rinvece di python manage.py testva bene per me, forse l'ordine di esecuzione è cruciale

poi trovo un documento sull'ordine in cui vengono eseguiti i test , menziona quale test verrà eseguito per primo.

Quindi, utilizzo TestCase per l'interazione con il database, unittest.TestCaseper altri semplici test, funziona ora!


0

La risposta di @kdazzle è corretta. Non l'ho provato perché la gente ha detto che "la classe TestCase di Django è una sottoclasse più comunemente usata di TransactionTestCase", quindi ho pensato che fosse lo stesso uso l'uno o l'altro. Ma il blog di Jahongir Rahmonov lo ha spiegato meglio:

la classe TestCase racchiude i test in due blocchi atomici () nidificati: uno per l'intera classe e uno per ciascun test. Questo è dove TransactionTestCase dovrebbe essere usato. Non avvolge i test con il blocco atomic () e quindi puoi testare i tuoi metodi speciali che richiedono una transazione senza alcun problema.

EDIT: Non ha funzionato, ho pensato di sì, ma NO.

In 4 anni hanno potuto risolvere questo .......................................


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

Ho avuto lo stesso problema.

Nel mio caso lo stavo facendo

author.tasks.add(tasks)

quindi convertendolo in

author.tasks.add(*tasks)

Rimosso quell'errore.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.