Je reçois TransactionManagementError lorsque j'essaie de sauvegarder une instance de modèle Django User et que, dans son signal post_save, j'enregistre certains modèles dont l'utilisateur est la clé étrangère.
Le contexte et l'erreur sont assez similaires à cette question Django TransactionManagementError lors de l'utilisation de signaux
Cependant, dans ce cas, l'erreur se produit uniquement pendant le test de l'unité.
Cela fonctionne bien dans les tests manuels, mais les tests unitaires échouent.
Y a-t-il quelque chose qui me manque?
Voici les extraits de code:
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)
Traceback:
======================================================================
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.
----------------------------------------------------------------------
J'ai rencontré ce même problème moi-même. Cela est dû à une bizarrerie dans la façon dont les transactions sont gérées dans les versions plus récentes de Django, associée à un unittest qui déclenche intentionnellement une exception.
Un unestest vérifiait qu'une contrainte de colonne unique était appliquée en déclenchant intentionnellement une exception 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()
Dans Django 1.4, cela fonctionne bien. Cependant, dans Django 1.5/1.6, chaque test est encapsulé dans une transaction. Ainsi, si une exception se produit, elle est interrompue jusqu'à ce que vous l'annuliez explicitement. Par conséquent, toute opération ORM ultérieure dans cette transaction, telle que ma do_more_model_stuff()
, échouera avec cette exception Django.db.transaction.TransactionManagementError
.
Comme caio mentionné dans les commentaires, la solution consiste à capturer votre exception avec transaction.atomic
comme:
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
Cela empêchera l’exception générée à dessein de rompre l’ensemble de la transaction de unittest.
Puisque @mkoistinen n'a jamais fait son commentaire , une réponse, je posterai sa suggestion pour que les gens ne soient pas obligés de creuser avec des commentaires.
pensez simplement à déclarer votre classe de test en tant que TransactionTestCase plutôt que simplement TestCase.
A partir de docs : Un TransactionTestCase peut appeler commit et annulation et observer les effets de ces appels sur la base de données.
Pour moi, les correctifs proposés ne fonctionnaient pas. Dans mes tests, j'ouvre certains sous-processus avec Popen
pour analyser/migrer les migrations (par exemple, un test vérifie s'il n'y a pas de modification de modèle).
Pour moi, le sous-classement de SimpleTestCase
au lieu de TestCase
a fait l'affaire.
Notez que SimpleTestCase
ne permet pas d'utiliser la base de données.
Bien que cela ne réponde pas à la question initiale, j'espère que cela aidera quand même certaines personnes.
J'ai le même problème, mais with transaction.atomic()
et TransactionTestCase
n'ont pas fonctionné pour moi.
python manage.py test -r
au lieu de python manage.py test
me convient, peut-être que l'ordre d'exécution est crucial
alors je trouve un doc sur Ordre dans lequel les tests sont exécutés , Il mentionne quel test sera exécuté en premier.
Donc, j'utilise TestCase pour l'interaction avec la base de données, unittest.TestCase
pour un autre test simple, cela fonctionne maintenant!
Si vous utilisez pytest-Django, vous pouvez transmettre transaction=True
au décorateur Django_db
pour éviter cette erreur.
Voir https://pytest-Django.readthedocs.io/en/latest/database.html#testing-transactions
Django a lui-même le TransactionTestCase qui vous permet de tester transactions et va vider la base de données entre les tests pour isoler leur. L'inconvénient est que la configuration de ces tests est beaucoup plus lente en raison du vidage requis de la base de données. pytest-Django prend également en charge ce type de tests, que vous pouvez sélectionner à l'aide d'un argument de la marque Django_db:
@pytest.mark.Django_db(transaction=True)
def test_spam():
pass # test relying on transactions
La réponse de @kdazzle est correcte. Je n’ai pas essayé car les gens disaient que «la classe TestCase de Django est une sous-classe plus couramment utilisée de TransactionTestCase», j’ai donc pensé que c’était la même utilisation, l’un ou l’autre. Mais le blog de Jahongir Rahmonov l'expliquait mieux:
la classe TestCase encapsule les tests dans deux blocs atomic () imbriqués: un pour toute la classe et un pour chaque test. C'est ici que TransactionTestCase doit être utilisé. Il n’enveloppe pas les tests avec atomic () et ainsi vous pouvez tester vos méthodes spéciales qui nécessitent une transaction sans aucun problème.
EDIT: Cela n'a pas fonctionné, je pensais que oui, mais non.
En 4 ans, ils pourraient résoudre ce problème .......................................
Je recevais cette erreur lors de l'exécution de tests unitaires dans ma fonction create_test_data avec Django 1.9.7. Cela fonctionnait dans les versions précédentes de Django.
Cela ressemblait à ceci:
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()
Ma solution consistait à utiliser update_or_create à la place:
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})