Django / Pytest / Splinter : IntegrityError duplicate key in test only

I know it's a very common problem and I read a lot of similar questions. But I can't find any solution, so, here I am with the 987th question on Stackoverflow about a Django Integrity error.

I'm starting a Django project with a Postgres db to learn about the framework. I did the classic Profile creation for users, automated with a post_save signal. Here is the model:

class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    description = models.TextField(max_length=280, blank=True)
    contacts = models.ManyToManyField(
        "self",
        symmetrical=True,
        blank=True
    )

And this is the signal that goes with it :

def create_profile(sender, instance, created, **kwargs):
    if created:
        user_profile = Profile(user=instance)
        user_profile.save()

post_save.connect(create_profile, sender=User, dispatch_uid="profile_creation")

The project is just starting, and for now I only create users in the admin view. With the post_save signal, it's supposed to create a Profile with the same form.

Here is the admin setup :

class ProfileInline(admin.StackedInline):
    model = Profile

class UserAdmin(admin.ModelAdmin):
    model = User
    list_display = ["username", "is_superuser"]
    fields = ["username", "is_superuser"]
    inlines = [ProfileInline]

I'm using pytest and Splinter for my test, and this is the integration test that don't work :

@pytest.mark.django_db
class TestAdminPage:
    def test_profile_creation_from_admin(self, browser, admin_user):
        browser.visit('/admin/login/')

        username_field = browser.find_by_css('form input[name="username"]')
        password_field = browser.find_by_css('form input[name="password"]')

        username_field.fill(admin_user.username)
        password_field.fill('password')

        submit = browser.find_by_css('form input[type="submit"]')
        submit.click()

        browser.links.find_by_text("Users").click()
        browser.links.find_by_partial_href("/user/add/").click()
        
        browser.find_by_css('form input[name="username"]').fill('Super_pseudo')
        browser.find_by_css('textarea[name="profile-0-description"]').fill('Super description')

        browser.find_by_css('input[name="_save"]').click()

        assert browser.url is '/admin/auth/user/'
        assert Profile.objects.last().description is 'Super description'

when I run this, I get this error :

django.db.utils.IntegrityError: duplicate key value violates unique constraint "profiles_profile_user_id_key"
DETAIL:  Key (user_id)=(2) already exists.

At first, I also saw this error when I was creating a user using my local server. But only if I wrote a description. If I let the description field empty, everything was working fine. So I wrote this integration test, to solve the issue. And then I read a lot, tweaked a few things, and the error stopped happening in my local browser. But not in my test suite.

So I used a breakpoint, there :

def create_profile(sender, instance, created, **kwargs):
    breakpoint()
    if created:
        user_profile = Profile(user=instance)
        user_profile.save()

And that's where the fun begins. This single test is calling the signal 3 times.

The first time it's called by the admin_user fixture that I'm using.

(Pdb) from profiles.models import Profile
(Pdb) instance
<User: admin>
(Pdb) created
True
(Pdb) instance.profile
*** django.contrib.auth.models.User.profile.RelatedObjectDoesNotExist: User has no profile.
(Pdb) Profile.objects.count()
0
(Pdb) continue

Seems legit, the admin user don't have a profile, why not. Then the signal is called again on the same instance.

(Pdb) instance
<User: admin>
(Pdb) instance.profile
<Profile: admin>
(Pdb) Profile.objects.count()
1
(Pdb) Profile.objects.last()
<Profile: admin>
(Pdb) created
False
(Pdb) continue

Still legit, weird, but it's not doing anything. created is False, so it's not creating a second profile. Didn't need the first one, but it's not making the test fail. And then :

(Pdb) instance
<User: Super_pseudo>
(Pdb) created
True
(Pdb) instance.profile
<Profile: Super_pseudo>
(Pdb) Profile.objects.count()
1
(Pdb) Profile.objects.last()
<Profile: admin>

This is so weird. The profile is not saved, but it's raising an Integrity error anyway. It looks instanciated, when I call instance.profile I get something, but it don't look like it's saved in the db. But the error happens anyway. I have no clue, I spent a few hours already, and I don't know what to look.

Feels like I'm missing something important, and that's why I'm asking for your help.

Edit

I tried updating the signal with if created and not kwargs.get('raw', False):, but it doesn't work.

Back to Top