Reputation: 1212
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 (why?), when I call instance.profile
I get something (how?), 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.
I tried updating the signal with if created and not kwargs.get('raw', False):
, but it doesn't work.
Just in case, the error message in full :
=================================== FAILURES ===================================
________________ TestAdminPage.test_profile_creation_from_admin ________________
self = <django.db.backends.utils.CursorWrapper object at 0x7f6f62521790>
sql = 'INSERT INTO "profiles_profile" ("user_id", "description") VALUES (%s, %s) RETURNING "profiles_profile"."id"'
params = (2, 'Super description')
ignored_wrapper_args = (False, {'connection': <DatabaseWrapper vendor='postgresql' alias='default'>, 'cursor': <django.db.backends.utils.CursorWrapper object at 0x7f6f62521790>})
def _execute(self, sql, params, *ignored_wrapper_args):
self.db.validate_no_broken_transaction()
with self.db.wrap_database_errors:
if params is None:
# params default might be backend specific.
return self.cursor.execute(sql)
else:
> return self.cursor.execute(sql, params)
E psycopg2.errors.UniqueViolation: duplicate key value violates unique constraint "profiles_profile_user_id_key"
E DETAIL: Key (user_id)=(2) already exists.
/usr/local/lib/python3.12/site-packages/django/db/backends/utils.py:89: UniqueViolation
The above exception was the direct cause of the following exception:
self = <profiles.tests.test_admin.TestAdminPage object at 0x7f6f62a543b0>
browser = <splinter.driver.djangoclient.DjangoClient object at 0x7f6f62822840>
admin_user = <User: admin>
def test_profile_creation_from_admin(self, browser, admin_user):
self.login_as_admin(browser, admin_user)
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()
profiles/tests/test_admin.py:28:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/usr/local/lib/python3.12/site-packages/splinter/driver/lxmldriver.py:433: in click
return self.parent.submit_data(parent_form)
/usr/local/lib/python3.12/site-packages/splinter/driver/djangoclient.py:130: in submit_data
return super(DjangoClient, self).submit(form).content
/usr/local/lib/python3.12/site-packages/splinter/driver/lxmldriver.py:89: in submit
self._do_method(method, url, data=data)
/usr/local/lib/python3.12/site-packages/splinter/driver/djangoclient.py:118: in _do_method
self._response = func_method(url, data=data, follow=True, **extra)
/usr/local/lib/python3.12/site-packages/django/test/client.py:948: in post
response = super().post(
/usr/local/lib/python3.12/site-packages/django/test/client.py:482: in post
return self.generic(
/usr/local/lib/python3.12/site-packages/django/test/client.py:609: in generic
return self.request(**r)
/usr/local/lib/python3.12/site-packages/django/test/client.py:891: in request
self.check_exception(response)
/usr/local/lib/python3.12/site-packages/django/test/client.py:738: in check_exception
raise exc_value
/usr/local/lib/python3.12/site-packages/django/core/handlers/exception.py:55: in inner
response = get_response(request)
/usr/local/lib/python3.12/site-packages/django/core/handlers/base.py:197: in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
/usr/local/lib/python3.12/contextlib.py:81: in inner
return func(*args, **kwds)
/usr/local/lib/python3.12/site-packages/django/contrib/admin/options.py:688: in wrapper
return self.admin_site.admin_view(view)(*args, **kwargs)
/usr/local/lib/python3.12/site-packages/django/utils/decorators.py:134: in _wrapper_view
response = view_func(request, *args, **kwargs)
/usr/local/lib/python3.12/site-packages/django/views/decorators/cache.py:62: in _wrapper_view_func
response = view_func(request, *args, **kwargs)
/usr/local/lib/python3.12/site-packages/django/contrib/admin/sites.py:242: in inner
return view(request, *args, **kwargs)
/usr/local/lib/python3.12/site-packages/django/contrib/admin/options.py:1886: in add_view
return self.changeform_view(request, None, form_url, extra_context)
/usr/local/lib/python3.12/site-packages/django/utils/decorators.py:46: in _wrapper
return bound_method(*args, **kwargs)
/usr/local/lib/python3.12/site-packages/django/utils/decorators.py:134: in _wrapper_view
response = view_func(request, *args, **kwargs)
/usr/local/lib/python3.12/site-packages/django/contrib/admin/options.py:1747: in changeform_view
return self._changeform_view(request, object_id, form_url, extra_context)
/usr/local/lib/python3.12/site-packages/django/contrib/admin/options.py:1799: in _changeform_view
self.save_related(request, form, formsets, not add)
/usr/local/lib/python3.12/site-packages/django/contrib/admin/options.py:1255: in save_related
self.save_formset(request, form, formset, change=change)
/usr/local/lib/python3.12/site-packages/django/contrib/admin/options.py:1243: in save_formset
formset.save()
/usr/local/lib/python3.12/site-packages/django/forms/models.py:784: in save
return self.save_existing_objects(commit) + self.save_new_objects(commit)
/usr/local/lib/python3.12/site-packages/django/forms/models.py:944: in save_new_objects
self.new_objects.append(self.save_new(form, commit=commit))
/usr/local/lib/python3.12/site-packages/django/forms/models.py:1142: in save_new
return super().save_new(form, commit=commit)
/usr/local/lib/python3.12/site-packages/django/forms/models.py:757: in save_new
return form.save(commit=commit)
/usr/local/lib/python3.12/site-packages/django/forms/models.py:542: in save
self.instance.save()
/usr/local/lib/python3.12/site-packages/django/db/models/base.py:814: in save
self.save_base(
/usr/local/lib/python3.12/site-packages/django/db/models/base.py:877: in save_base
updated = self._save_table(
/usr/local/lib/python3.12/site-packages/django/db/models/base.py:1020: in _save_table
results = self._do_insert(
/usr/local/lib/python3.12/site-packages/django/db/models/base.py:1061: in _do_insert
return manager._insert(
/usr/local/lib/python3.12/site-packages/django/db/models/manager.py:87: in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
/usr/local/lib/python3.12/site-packages/django/db/models/query.py:1805: in _insert
return query.get_compiler(using=using).execute_sql(returning_fields)
/usr/local/lib/python3.12/site-packages/django/db/models/sql/compiler.py:1820: in execute_sql
cursor.execute(sql, params)
/usr/local/lib/python3.12/site-packages/django/db/backends/utils.py:67: in execute
return self._execute_with_wrappers(
/usr/local/lib/python3.12/site-packages/django/db/backends/utils.py:80: in _execute_with_wrappers
return executor(sql, params, many, context)
/usr/local/lib/python3.12/site-packages/django/db/backends/utils.py:84: in _execute
with self.db.wrap_database_errors:
/usr/local/lib/python3.12/site-packages/django/db/utils.py:91: in __exit__
raise dj_exc_value.with_traceback(traceback) from exc_value
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <django.db.backends.utils.CursorWrapper object at 0x7f6f62521790>
sql = 'INSERT INTO "profiles_profile" ("user_id", "description") VALUES (%s, %s) RETURNING "profiles_profile"."id"'
params = (2, 'Super description')
ignored_wrapper_args = (False, {'connection': <DatabaseWrapper vendor='postgresql' alias='default'>, 'cursor': <django.db.backends.utils.CursorWrapper object at 0x7f6f62521790>})
def _execute(self, sql, params, *ignored_wrapper_args):
self.db.validate_no_broken_transaction()
with self.db.wrap_database_errors:
if params is None:
# params default might be backend specific.
return self.cursor.execute(sql)
else:
> return self.cursor.execute(sql, params)
E django.db.utils.IntegrityError: duplicate key value violates unique constraint "profiles_profile_user_id_key"
E DETAIL: Key (user_id)=(2) already exists.
/usr/local/lib/python3.12/site-packages/django/db/backends/utils.py:89: IntegrityError
Upvotes: 1
Views: 233