46

I have an extended UserProfile model in django:

class UserProfile(models.Model):
  user = models.ForeignKey(User, unique=True)
  #other things in that profile

And a signals.py:

from registration.signals import user_registered
from models import UserProfile
from django.contrib.auth.models import User

def createUserProfile(sender, instance, **kwargs):
  profile = users.models.UserProfile()
  profile.setUser(sender)
  profile.save()

user_registered.connect(createUserProfile, sender=User)

I make sure the signal gets registered by having this in my __init__.py:

import signals

So that should create me a new UserProfile for every user that registers, right? But it doesn't. I always get "UserProfile matching query does not exist" errors when I try to log in, which means that the database entry isn't there.

I should say that I use django-registration, which provides the user_registered signal.

The structure of the important apps for this is, that I have one application called "users", there I have: models.py, signals.py, urls.py and views.py (and some other things which shouldn't matter here). The UserProfile class is defined in models.py.

Update: I changed the signals.py to:

from django.db.models.signals import post_save
from models import UserProfile
from django.contrib.auth.models import User

def create_profile(sender, **kw):
    user = kw["instance"]
    if kw["created"]:
        profile = UserProfile()
        profile.user = user
        profile.save()

post_save.connect(create_profile, sender=User)

But now I get a "IntegrityError":

"column user_id is not unique"

Edit 2:

I found it. Looks like somehow I registred the signal twice. The workaround for this is described here: http://code.djangoproject.com/wiki/Signals#Helppost_saveseemstobeemittedtwiceforeachsave

I had to add a dispatch_uid, now my signals.py looks like this and is working:

from django.db.models.signals import post_save
from django.contrib.auth.models import User
from models import UserProfile
from django.db import models

def create_profile(sender, **kw):
    user = kw["instance"]
    if kw["created"]:
        profile = UserProfile(user=user)
        profile.save()

post_save.connect(create_profile, sender=User, dispatch_uid="users-profilecreation-signal")
mlissner
  • 17,359
  • 18
  • 106
  • 169
Kai
  • 2,205
  • 3
  • 32
  • 43
  • Could you post the structure your django app. I am curious about a couple of lines in your code like `profile=user.models.UserProfile()` - do you have a module named 'user'? Where is UserProfile() located. – czarchaic Dec 15 '09 at 22:28
  • it is users, I don't know how that typo got in there, but the problem is the same. I wonder why python did not throw an error for the misspelled path. – Kai Dec 15 '09 at 22:44
  • thanks for this solution, im new with django, and i dont khow how ill save others data about the user profile. i see that your just save the user in the model UserProfile, but how ill save other's data (using your signals.py) from the register form? Thansk (sorry with the english) – Asinox Aug 03 '10 at 00:33

6 Answers6

31

You can implement it using post_save on the user:

from django.db.models.signals import post_save
from models import UserProfile
from django.contrib.auth.models import User

def create_profile(sender, **kwargs):
    user = kwargs["instance"]
    if kwargs["created"]:
        profile = users.models.UserProfile()
        profile.setUser(sender)
        profile.save()

post_save.connect(create_profile, sender=User)

Edit:
Another possible solution, which is tested and works (I'm using it on my site):

from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
def create_profile(sender, **kwargs):
    user = kwargs["instance"]
    if kwargs["created"]:
        up = UserProfile(user=user, stuff=1, thing=2)
        up.save()
post_save.connect(create_profile, sender=User)
mlissner
  • 17,359
  • 18
  • 106
  • 169
Agos
  • 18,542
  • 11
  • 56
  • 70
  • 2
    has to be "profile.setUser(user)", i think. But then i get "column user_id is not unique". Any thoughts on that? – Kai Dec 16 '09 at 12:36
  • I added another possible solution to my answer, check it out. – Agos Dec 16 '09 at 16:30
  • I get "column user_id is not unique", can you please show me your UserProfile model? Maybe I got something with the ForeignKey() wrong. – Kai Dec 17 '09 at 11:17
  • Got it to work. Look in my initial post, if you are interested in the solution. – Kai Dec 17 '09 at 11:34
  • 2
    @Agos It would be very helpful to describe how one gets the values for `stuff` and `thing` in `create_profile`. – veddermatic Feb 24 '15 at 18:16
  • I simply make all the fields on my profile model `default=''` and `blank=True`, so with that, you wouldn't need to pass in any arguments or the stuff=1 and thing=2 @veddermatic – KhoPhi Nov 18 '15 at 17:33
6

You can get the extended profile to be created when first accessed for each user instead:

from django.db import models
from django.contrib.auth.models import User

class UserProfile(models.Model):
    user = models.ForeignKey(User, unique=True)
    additional_info_field = models.CharField(max_length=50)

User.profile = property(lambda u: UserProfile.objects.get_or_create(user=u)[0])

then use

from django.contrib.auth.models import User
user = User.objects.get(pk=1)
user.profile.additional_info_field

ref: http://www.codekoala.com/blog/2009/quick-django-tip-user-profiles/

Tim Abell
  • 11,186
  • 8
  • 79
  • 110
6

This helped me: primary_key=True

class UserProfile(models.Model):
    user = models.OneToOneField(User, unique=True, primary_key=True, related_name="user")
    phone = models.CharField(('phone'),max_length=30, blank=False, null=True)
    user_building = models.ManyToManyField(Building, blank=True)
    added_by = models.ForeignKey(User, blank=True, null=True, related_name="added")
Vári Zorán
  • 101
  • 1
  • 2
5

When you call profile.setUser(), I think you want to pass instance rather than sender as the parameter.

From the documentation of the user_registered signal, sender refers to the User class; instance is the actual user object that was registered.

Ben James
  • 121,135
  • 26
  • 193
  • 155
1

According to my latest research, creating a separate file, e.g., singals.py, does not work.

You'd better connect 'create_profile' to 'post_save' in your models.py directly, otherwise this piece of code won't be executed since it's in a separate file and no one imports it.

My final code for your reference:

# models.py

# Here goes the definition of class UserProfile.
class UserProfile(models.Model):
    ...

# Use signal to automatically create user profile on user creation.

# Another implementation:
# def create_user_profile(sender, **kwargs):
#     user = kwargs["instance"]
#     if kwargs["created"]:
#         ...
def create_user_profile(sender, instance, created, **kwargs):
    """
    :param sender: Class User.
    :param instance: The user instance.
    """
    if created:
        # Seems the following also works:
        #   UserProfile.objects.create(user=instance)
        # TODO: Which is correct or better?
        profile = UserProfile(user=instance)
        profile.save()

post_save.connect(create_user_profile,
                  sender=User,
                  dispatch_uid="users-profilecreation-signal")
Adam Gu
  • 166
  • 2
  • 5
0

Update for 2018:

This question has collected a lot of views, maybe it is time for an update.

This is the latest version for latest Django.

from django.dispatch import receiver
from django.db.models.signals import post_save
from django.conf import settings
from models import UserProfile

@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_user_profile(sender, instance=None, created=False, **kwargs):
    if created:
        UserProfile.objects.create(user=instance)
Oliver Ni
  • 2,606
  • 7
  • 29
  • 44
  • I think (for 2018, django >=1.11) it should always be: `sender=get_user_model()`. https://docs.djangoproject.com/en/2.0/topics/auth/customizing/#django.contrib.auth.get_user_model – Paolo Apr 30 '18 at 10:38
  • Should `UserProfile.objects.create(user=instance)` instead be `UserProfile.objects.create(user=instance, **kwargs)`? – alphazwest Oct 25 '18 at 22:48
  • @theeastcoastwest nope. The `**kwargs` relate to the signal itself, it doesn't contain any data that you would want to use when creating the profile. In fact, you just want to create a clean UserProfile model, not changing it in any way. – aherok Jan 16 '19 at 22:02
  • Is there a source where I can read about using signals to create related objects (such as `UserProfile` in this case) vs using Model Managers? Pros and Cons of their approach and best practice kinda recommendation? Thank you. – Deep Mar 12 '20 at 00:52