In a Django 2.0 project, I have the following problem with my unit tests, and I cannot find the reason.
- UPDATE: I am using Postgres 10.1. The problem does not occur when I switch to sqlite3
I am implementing a model that tracks any changes on another model
from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
class Investment(models.Model):
"""the main model"""
status = models.IntegerField()
class InvestmentStatusTrack(models.Model):
"""track every change of status on an investment"""
investment = models.ForeignKey(Investment, on_delete=models.CASCADE)
status = models.IntegerField()
modified_on = models.DateTimeField(
blank=True, null=True, default=None, verbose_name=_('modified on'), db_index=True
)
modified_by = models.ForeignKey(
User, blank=True, null=True, default=None, verbose_name=_('modified by'), on_delete=models.CASCADE
)
class Meta:
ordering = ('-modified_on', )
def __str__(self):
return '{0} - {1}'.format(self.investment, self.status)
@receiver(post_save, sender=Investment)
def handle_status_track(sender, instance, created, **kwargs):
"""add a new track every time the investment status change"""
request = get_request()
modified_by = None
if request and request.user and request.user.is_authenticated:
modified_by = request.user
InvestmentStatusTrack.objects.create(
investment=instance, status=instance.status, modified_on=datetime.now(), modified_by=modified_by
)
Most of my unit test do not work with the following trace
Traceback (most recent call last):
File "/env/lib/python3.6/site-packages/django/test/testcases.py", line 209, in __call__
self._post_teardown()
File "/env/lib/python3.6/site-packages/django/test/testcases.py", line 893, in _post_teardown
self._fixture_teardown()
File "/env/lib/python3.6/site-packages/django/test/testcases.py", line 1041, in _fixture_teardown
connections[db_name].check_constraints()
File "/env/lib/python3.6/site-packages/django/db/backends/postgresql/base.py", line 235, in check_constraints
self.cursor().execute('SET CONSTRAINTS ALL IMMEDIATE')
File "/env/lib/python3.6/site-packages/django/db/backends/utils.py", line 68, in execute
return self._execute_with_wrappers(sql, params, many=False, executor=self._execute)
File "/env/lib/python3.6/site-packages/django/db/backends/utils.py", line 77, in _execute_with_wrappers
return executor(sql, params, many, context)
File "/env/lib/python3.6/site-packages/django/db/backends/utils.py", line 85, in _execute
return self.cursor.execute(sql, params)
File "/env/lib/python3.6/site-packages/django/db/utils.py", line 89, in __exit__
raise dj_exc_value.with_traceback(traceback) from exc_value
File "/env/lib/python3.6/site-packages/django/db/backends/utils.py", line 83, in _execute
return self.cursor.execute(sql)
django.db.utils.IntegrityError: insert or update on table "investments_investmentstatustrack" violates foreign key constraint "investments_investme_modified_by_id_3a12fb21_fk_auth_user"
DETAIL: Key (modified_by_id)=(1) is not present in table "auth_user".
Any idea how to solve this problem?
- UPDATE: 2 unit test, which shows the problem.
Both are successful when performed alone. It seems that the problem is occurring on unit test tearDown. Foreign key restriction is not performed at this point, because the user has already been deleted.
class TrackInvestmentStatusTest(ApiTestCase):
def login(self, is_staff=False):
password = "abc123"
self.user = mommy.make(User, is_staff=is_staff, is_active=True)
self.user.set_password(password)
self.user.save()
self.assertTrue(self.client.login(username=self.user.username, password=password))
def test_add_investment(self):
"""it should add a new investment and add a track"""
self.login()
url = reverse('investments:investments-list')
data = {}
response = self.client.post(url, data=data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(1, Investment.objects.count())
investment = Investment.objects.all()[0]
self.assertEqual(investment.status, Investment.STATUS_IN_PROJECT)
self.assertEqual(1, InvestmentStatusTrack.objects.count())
track = InvestmentStatusTrack.objects.all()[0]
self.assertEqual(track.status, investment.status)
self.assertEqual(track.investment, investment)
self.assertEqual(track.modified_by, self.user)
self.assertEqual(track.modified_on.date(), date.today())
def test_save_status(self):
"""it should modify the investment and add a track"""
self.login()
investment_status = Investment.STATUS_IN_PROJECT
investment = mommy.make(Investment, asset=asset, status=investment_status)
investment_id = investment.id
self.assertEqual(1, InvestmentStatusTrack.objects.count())
track = InvestmentStatusTrack.objects.all()[0]
self.assertEqual(track.status, investment.status)
self.assertEqual(track.investment, investment)
self.assertEqual(track.modified_by, None)
self.assertEqual(track.modified_on.date(), date.today())
url = reverse('investments:investments-detail', args=[investment.id])
data = {
'status': Investment.STATUS_ACCEPTED
}
response = self.client.patch(url, data=data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(1, Investment.objects.count())
investment = Investment.objects.all()[0]
self.assertEqual(investment.id, investment_id)
self.assertEqual(investment.status, Investment.STATUS_ACCEPTED)
self.assertEqual(2, InvestmentStatusTrack.objects.count())
track = InvestmentStatusTrack.objects.all()[0]
self.assertEqual(track.status, Investment.STATUS_ACCEPTED)
self.assertEqual(track.investment, investment)
self.assertEqual(track.modified_by, self.user)
self.assertEqual(track.modified_on.date(), date.today())
track = InvestmentStatusTrack.objects.all()[1]
self.assertEqual(track.status, Investment.STATUS_IN_PROJECT)
self.assertEqual(track.investment, investment)
self.assertEqual(track.modified_by, None)
self.assertEqual(track.modified_on.date(), date.today())