Search code examples
djangodjango-modelsdjango-usersdjango-errors

In Django, when I call User.objects.create_user(username, email, password) - why does post_save get called twice?


In views.py I have the following view which gets called when a new user account is registered. All it does is get the username, email, and password from the request, then try to create a user with those credentials. In the code below, "A" gets printed, but "B" does not, because it crashes:

views.py

def register(request):
    if request.method == 'POST':
        query_dict = request.POST
        username = query_dict['username']
        email = query_dict['user_email']
        password = query_dict['password']
        role = query_dict['role']
        print "A"
        user = User.objects.create_user(username, email, password)
        # the handler is called here and creates the user profile
        print "B"
        user = authenticate(username=username, password=password)
        user_profile = user.get_profile()
        user_profile.role = role
        user_profile.save()
        if user is not None and user.is_active:
            login(request, user)
            return HttpResponseRedirect("/")

In myapp/models.py I have the following code for the handler.

'models.py`

post_save.connect(create_user_profile, sender=User)

def create_user_profile(sender, instance, created, **kwargs):
    print "created="+str(created)
    if created: # as opposed to being saved after being created earlier, I assume
        print "attempting to create a user profile for "+instance.username
        up = UserProfile.objects.create(user=instance)
        print "created a new UserProfile: <"+str(up)+">"

When I run my app, the print output is:

A
created=True
attempting to create a user profile for john
created a new UserProfile: <john - >
created=True
attempting to create a user profile for john
~~~~CRASHES~~~~

I have noticed throughout my development over the last couple of days that the create_user_profile method gets called twice when I run User.objects.create_user(username, email, password) I copied the handler code directly out of the docs, so I'm pretty sure it correct (https://docs.djangoproject.com/en/dev/topics/auth/#storing-additional-information-about-users). I don't know exactly where in the django code the create_user_profile method gets called from, but I am assuming that it sets the created flag to true only if the save method that invoked the handler was due to an object being created. Over the last couple of days this code has been working correctly, but all of a sudden today the second time the create_user_profile method gets called by the handler, the created flag is set to true, just like it was the first time. This is causing my app to crash with an IntegrityError, complaining that it can't create two objects with the same primary key.

So there are two things I don't understand. First: why does post_save happen twice when I only called User.objects.create_user(username, email, password) once; second: why did the created flag all of a sudden stop working like expected?

How can I debug this?

Thanks


Solution

  • It is possible that your models.py file is being imported, and run, twice. In this case, the same signal handler would be attached twice, and would fire twice when a model instance is saved.

    This can easily happen if you import it once as

    import myapp.models
    

    and again as

    import myproject.myapp.models
    

    Printing something to the console at the top of the file might give you an idea whether that is what is happening or not.

    Regardless, you can prevent a signal handler from being registered multiple times by giving it a unique dispatch_uid. Django will check on Signal.connect to see if a handler has already been registered for the same sender with the same UID, and will not re-register it if it has.

    Change your signal connecting code to something like

    post_save.connect(create_user_profile, sender=User, dispatch_uid='create_user_profile')
    

    and see if that helps