问题
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?
- No matter how the new user is created, there will always be a
profilemodel linking to it as well afterwards. - If the user selects an
accountin the admin during user creation, thisaccountwill be set on the newprofilemodel afterwards. If not, the field isnull.
Environment: Django 1.5, Python 2.7
Related questions:
- Creating a extended user profile (similar symptoms, but the cause turned out to be a different one)
回答1:
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:
- Django Admin creates the new
Userinstance, with noid. - Django Admin saves the
Userinstance.- Because the
pk(in this caseid) is not set, Django attempts to create a new record. - The new record's
idis set automatically by the database. - The
post_savehook creates a newProfileinstance for thatUserinstance.
- Because the
- Django Admin creates the new
Profileinstance, with itsuserset to the user'sid. - Django Admin saves the
Profileinstance.- Because the
pk(in this caseuser) is already set, Django attempts to fetch an existing record with thatpk. - Django finds the existing record and updates it.
- Because the
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:
- Django Admin creates the new
Userinstance, with noid. - Django Admin saves the
Userinstance, thepost_savehook creating a newProfileinstance as before. - Django Admin creates the new
Profileinstance, with noid(the automatically addedpkfield). - Django Admin saves the
Profileinstance.- Because the
pk(in this caseid) is not set, Django attempts to create a new record. - The database reports a violation of the table's uniqueness-constraint on the
userfield. - Django throws an Exception. You will not go to space today.
- Because the
回答2:
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.
来源:https://stackoverflow.com/questions/14345303/creating-a-profile-model-with-both-an-inlineadmin-and-a-post-save-signal-in-djan