4
votes

I'm using Django 1.4.1 with postgresql 9.1.

I need to add a profile to the User given with the auth app, and to allow the admin app to create and edit this profile. Thus I've been following the docs section Storing additional information about users :

models.py

class UserProfile(models.Model):
    user = models.OneToOneField(User)

    bio = models.TextField(null = True, blank = True)
    contact = models.TextField(null = True, blank = True)

def create_user_profile(sender, instance, created, **kwargs):
    if created:
        UserProfile.objects.create(user=instance)

post_save.connect(create_user_profile, sender=User)

settings.py

...
AUTH_PROFILE_MODULE = 'userprofile.UserProfile'
...

I also activated the django.contrib.auth and django.contrib.admin apps in INSTALLED_APPS.

admin.py

class UserProfileInline(admin.StackedInline):
    model = UserProfile
    can_delete = False
    verbose_name_plural = 'profile'

class UserAdmin(UserAdmin):
    inlines = (UserProfileInline, )

# Re-register UserAdmin
admin.site.unregister(User)
admin.site.register(User, UserAdmin)

Problem

Now, when I run the admin app and ask to add (create) a new user, I'm asked to create my user through a two-step process : first, a page asking for only the username, password (twice), and my two UserProfile fields.

If I type only the username and the password (twice) and click "Save", I'm showed the second page of the process, which allows to fill in all the other User fields, as well as my UserProfile fields. There's a message saying "The user "xxxxx" was added successfully. You may edit it again below.", and fortunately I can edit fields from both models, it works.

But if I try to type anything into one or both of my UserProfile fields in the first page, the submit fails with the message :

IntegrityError at /admin/auth/user/add/

duplicate key value violates unique constraint "userprofile_userprofile_user_id_key"
DETAIL:  Key (user_id)=(7) already exists.

The "7" is incremented each time I try.

How can that behavior be avoided, or alternatively how can I prevent the profile fields to be editable in the first page, but letting them be edited in the second page ?

Full traceback :

Environment:

Request Method: POST
Request URL: http://127.0.0.1:8000/admin/auth/user/add/

Django Version: 1.4.1
Python Version: 2.7.3
Installed Applications:
('django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.messages',
 'django.contrib.staticfiles',
 'django.contrib.admin',
 'django.contrib.admindocs',
 'userprofile')
Installed Middleware:
('django.middleware.common.CommonMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware')

Traceback:
File "/usr/local/lib/python2.7/dist-packages/django/core/handlers/base.py" in get_response
  111.                         response = callback(request, *callback_args, **callback_kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/contrib/admin/options.py" in wrapper
  366.                 return self.admin_site.admin_view(view)(*args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/utils/decorators.py" in _wrapped_view
  91.                     response = view_func(request, *args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/views/decorators/cache.py" in _wrapped_view_func
  89.         response = view_func(request, *args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/contrib/admin/sites.py" in inner
  196.             return view(request, *args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/views/decorators/debug.py" in sensitive_post_parameters_wrapper
  69.             return view(request, *args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/utils/decorators.py" in _wrapper
  25.             return bound_func(*args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/utils/decorators.py" in _wrapped_view
  91.                     response = view_func(request, *args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/utils/decorators.py" in bound_func
  21.                 return func(self, *args2, **kwargs2)
File "/usr/local/lib/python2.7/dist-packages/django/db/transaction.py" in inner
  209.                 return func(*args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/contrib/auth/admin.py" in add_view
  114.                                                extra_context)
File "/usr/local/lib/python2.7/dist-packages/django/utils/decorators.py" in _wrapper
  25.             return bound_func(*args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/utils/decorators.py" in _wrapped_view
  91.                     response = view_func(request, *args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/utils/decorators.py" in bound_func
  21.                 return func(self, *args2, **kwargs2)
File "/usr/local/lib/python2.7/dist-packages/django/db/transaction.py" in inner
  209.                 return func(*args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/contrib/admin/options.py" in add_view
  956.                 self.save_related(request, form, formsets, False)
File "/usr/local/lib/python2.7/dist-packages/django/contrib/admin/options.py" in save_related
  733.             self.save_formset(request, form, formset, change=change)
File "/usr/local/lib/python2.7/dist-packages/django/contrib/admin/options.py" in save_formset
  721.         formset.save()
File "/usr/local/lib/python2.7/dist-packages/django/forms/models.py" in save
  497.         return self.save_existing_objects(commit) + self.save_new_objects(commit)
File "/usr/local/lib/python2.7/dist-packages/django/forms/models.py" in save_new_objects
  628.             self.new_objects.append(self.save_new(form, commit=commit))
File "/usr/local/lib/python2.7/dist-packages/django/forms/models.py" in save_new
  731.             obj.save()
File "/usr/local/lib/python2.7/dist-packages/django/db/models/base.py" in save
  463.         self.save_base(using=using, force_insert=force_insert, force_update=force_update)
File "/usr/local/lib/python2.7/dist-packages/django/db/models/base.py" in save_base
  551.                 result = manager._insert([self], fields=fields, return_id=update_pk, using=using, raw=raw)
File "/usr/local/lib/python2.7/dist-packages/django/db/models/manager.py" in _insert
  203.         return insert_query(self.model, objs, fields, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/db/models/query.py" in insert_query
  1576.     return query.get_compiler(using=using).execute_sql(return_id)
File "/usr/local/lib/python2.7/dist-packages/django/db/models/sql/compiler.py" in execute_sql
  910.             cursor.execute(sql, params)
File "/usr/local/lib/python2.7/dist-packages/django/db/backends/util.py" in execute
  40.             return self.cursor.execute(sql, params)
File "/usr/local/lib/python2.7/dist-packages/django/db/backends/postgresql_psycopg2/base.py" in execute
  52.             return self.cursor.execute(query, args)

Exception Type: IntegrityError at /admin/auth/user/add/
Exception Value: duplicate key value violates unique constraint "userprofile_userprofile_user_id_key"
DETAIL:  Key (user_id)=(7) already exists.`
3
Did you manage to solve this? I've run into this exact problem, and from searching on the web quite a number of people run into this too but no one has posted a definite solution.CadentOrange
No, I've not found any solution. Since I'll be the only one using the admin for a time, I've stopped searching for now, sorry. I'd be glad to get a correct answer, though.Manur
I found the solution presented here works. You have to overwrite the save method in your user profile class. stackoverflow.com/questions/6117373/…CadentOrange
Where is the save method of UserProfile? I am using Django 1.6 . I am facing the same issue here. Where do I have to write this code?Pawan

3 Answers

4
votes

As CadentOrange mentioned in a comment, the solution to this problem is described in this answer.

The problem is with using an inline admin form. Here's what happens:

  1. It saves the main model (User)
  2. Due to (1), The post_save signal handler for User is fired, which creates a new UserProfile object
  3. Each inline model is saved (including another copy of your UserProfile, resulting in the dupe).
4
votes

instead of

def create_user_profile(sender, instance, created, **kwargs):
    if created:
        UserProfile.objects.create(user=instance)

do a

def create_user_profile(sender, instance, created, **kwargs):
    if created:
        UserProfile.objects.get_or_create(user=instance)

You are creating new user profile object even for edit.

2
votes

The create_user_profile signal and the admin form try to create the same UserProfile. You can override LocalUserAdmin to exclude the UserProfileInline from the add view :

class LocalUserAdmin(UserAdmin):
    inlines = (UserProfileInline, )

    def get_formsets_with_inlines(self, request, obj=None):
        for inline in self.get_inline_instances(request, obj):
            # hide MyInline in the add view
            if isinstance(inline, UserProfileInline) and obj is None:
                continue
            yield inline.get_formset(request, obj), inline

see https://docs.djangoproject.com/en/2.0/ref/contrib/admin/#django.contrib.admin.ModelAdmin.get_formsets_with_inlines