I created a 'profile' model (with a 1-to-1 relationship to the User model) as described on Extending the existing user model. The profile model has an optional many-to-one relationship to another model:
class Profile(models.Model):
user = models.OneToOneField(User, primary_key=True)
account = models.ForeignKey(Account, blank=True, null=True, on_delete=models.SET_NULL)
As documented there, I also created an inline admin:
class ProfileInline(admin.StackedInline):
model = Profile
can_delete = False
verbose_name_plural = 'profiles'
# UserAdmin and unregister()/register() calls omitted, they are straight copies from the Django docs
Now if I don't select an account
in the admin when creating the user, the profile model won't be created. So I connect to the post_save signal, again just following the documentation:
@receiver(post_save, sender=User)
def create_profile_for_new_user(sender, created, instance, **kwargs):
if created:
profile = Profile(user=instance)
profile.save()
This works fine as long as I do not select an account
in the admin, but if I do, I'll get an IntegrityError
exception, telling me that duplicate key value violates unique constraint "app_profile_user_id_key" DETAIL: Key (user_id)=(15) already exists.
Apparently, the inline admin tries to creates the profile
instance itself, but my post_save
signal handler has already created it at that time.
How do I fix this problem, while keeping all of the following requirements?
profile
model linking to it as well afterwards.account
in the admin during user creation, this account
will be set on the new profile
model afterwards. If not, the field is null.
Environment: Django 1.5, Python 2.7
Related questions:
The problem can be avoided by setting primary_key=True
on the OneToOneField
pointing at the User
model, as you have figured out yourself.
The reason that this works seems to be rather simple.
When you try to create a model instance and set the pk
manually before saving it, Django will try to find a record in the database with that pk
and update it rather than blindly attempting to create a new one. If none exists, it creates the new record as expected.
When you set the OneToOneField
as the primary key and Django Admin sets that field to the related User
model's ID, that means the pk
is already set and Django will attempt to find an existing record first.
This is what happens with the OneToOneField
set as primary key:
User
instance, with no id
.User
instance.
pk
(in this case id
) is not set, Django attempts to create a new record.id
is set automatically by the database.post_save
hook creates a new Profile
instance for that User
instance.Profile
instance, with its user
set to the user's id
.Profile
instance.
pk
(in this case user
) is already set, Django attempts to fetch an existing record with that pk
.If you don't set the primary key explicitly, Django instead adds a field that uses the database's auto_increment
functionality: the database sets the pk
to the next largest value that doesn't exist. This means the field will actually be left blank unless you set it manually and Django will therefore always attempt to insert a new record, resulting in a conflict with the uniqueness-constraint on the OneToOneField
.
This is what causes the original problem:
User
instance, with no id
.User
instance, the post_save
hook creating a new Profile
instance as before.Profile
instance, with no id
(the automatically added pk
field).Profile
instance.
pk
(in this case id
) is not set, Django attempts to create a new record.user
field.It seems like setting primary_key=True
on the OneToOneField
connecting the profile model to the User
model fixes this issue. However, I don't think I understand all the implications of that and why it helps.
I'll leave this here as a hint, but if that's the best solution and someone could come up with a well-written explanation, I'd upvote/accept that and possibly delete mine.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With