Django - UserProfile m2m field in admin - error
Asked Answered
G

1

9

My models:

class UserProfile(models.Model):
    TYPES_CHOICES = (
        (0, _(u'teacher')),
        (1, _(u'student')),
    )
    user = models.ForeignKey(User, unique=True)
    type = models.SmallIntegerField(default=0, choices=TYPES_CHOICES, db_index=True)
    cities = models.ManyToManyField(City)
class City(models.Model):
    name = models.CharField(max_length=50)
    slug = models.SlugField(max_length=50)

In admin.py:

admin.site.unregister(User) 
class UserProfileInline(admin.StackedInline):
    model = UserProfile

class UserProfileAdmin(UserAdmin):
    inlines = [UserProfileInline]

admin.site.register(User, UserProfileAdmin)

@receiver(post_save, sender=User)
def create_profile(sender, instance, created, **kwargs):
    """Create a matching profile whenever a user object is created."""
    if created:
        profile, new = UserProfile.objects.get_or_create(user=instance)

But when I add new user and select a city I get that error: IntegrityError at /admin/auth/user/add/ (1062, "Duplicate entry '3' for key 'user_id'")

What is wrong with my code? If I don't select any city - user is added properly. Some way, user is being added to UserProfile more than once.

Grout answered 24/5, 2011 at 21:51 Comment(0)
M
20

I had this same issue recently. It actually makes perfect sense when you think about it. When you save a form with inlines in the admin, it saves the main model first, and then proceeds to save each inline. When it saves the model, your post_save signal is fired off and a UserProfile is created to match, but now it's time to save the inlines. The UserProfile inline is considered new, because it didn't exist previously (has no pk value), so it tries to save as an entirely new and different UserProfile and you get that integrity error for violating the unique constraint. The solution is simple. Just override UserProfile.save:

def save(self, *args, **kwargs):
    if not self.pk:
        try:
            p = UserProfile.objects.get(user=self.user)
            self.pk = p.pk
        except UserProfile.DoesNotExist:
            pass

    super(UserProfile, self).save(*args, **kwargs)

Essentially, this just checks if there's an existing UserProfile for the user in question. If so, it sets this UserProfile's pk to that one's so that Django does an update instead of a create.

Montenegro answered 24/5, 2011 at 22:0 Comment(4)
Thanks! I had this same problem. This solution makes sense and worked perfectly.Ligate
Excellently explained. Perhaps the Django documentation on storing additional user data should be updated to mention this as the example presented in the documentation will fail to work otherwise.Seaborne
This is probably also an indication that the post_save signal was registered twice due to an import naming issue. The dispatch_uid="unique-name-goes-here" would probably have solved this too.Mantua
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?Tryst

© 2022 - 2024 — McMap. All rights reserved.