codingcoding
codingcoding

Reputation: 751

Django listener isn't hearing the signal

I'm trying to save a user profile upon activation (not upon user creation), but the listener doesn't call create_user_profile(). The listener is located in models.py.

from registration.signals import user_activated
from mysite.signals import create_user_profile

user_activated.connect(create_user_profile, sender=User)

I'm using django-registration-redux. I haven't overridden anything in the registration process. In registration/signals.py the signal is:

user_activated = Signal(providing_args=["user", "request"])

In registration/default/views.py there is this function:

def activate(self, request, activation_key):
    """
    Given an an activation key, look up and activate the user
    account corresponding to that key (if possible).

    After successful activation, the signal
    ``registration.signals.user_activated`` will be sent, with the
    newly activated ``User`` as the keyword argument ``user`` and
    the class of this backend as the sender.

    """
    activated_user = RegistrationProfile.objects.activate_user(activation_key)
    if activated_user:
        signals.user_activated.send(sender=self.__class__,
                                    user=activated_user,
                                    request=request)

In PyCharm, I placed a breakpoint on that last line where send() is called. Execution pauses at that breakpoint when a user is activated, and proceeds from there with no error messages. It's as if the listener isn't even there.

Upvotes: 1

Views: 1018

Answers (2)

codingcoding
codingcoding

Reputation: 751

The answer given by alecxe is the accepted answer.

I am only adding this answer to help newbies like me understand what happened here, and to provide an alternate solution of changing the listener without changing the package code.

Thanks a million to @alecxe for that answer. That was the key that finally helped me wrap mind around the process. The docs did not help me much in this case, and it seems that many others have difficulty with signals as well.

The key is that sender in the send() tuple must match sender in your connect() tuple.

In this case, there was no problem with the sender. The problem was that my listener was listening for the right signal, but for the wrong sender. Changing the send() tuple worked, but I would rather fix my broken listener than to modify the sender in the registration package. The challenge there was, with my inexperience, I didn't know how sender=self.__class__ would appear when output. Using PyCharm, with a breakpoint, I was able to get that answer.

I'll detail how I did, just in case there is someone out there who could benefit from it.

Using alexce's answer, my signal listener was working, so I was able to place a breakpoint inside create_user_profile(). That pauses the program while the signal is still in memory. At that point, the signal can be seen in the variables list in the debugger.

I added 'check_signal' to the Signal args in signals.py:

user_activated = Signal(providing_args=["user", "request", "check_signal",])

...and then self.__class__ was added back into the send() call:

signals.user_activated.send(sender=activated_user.__class__,
                            user=activated_user,
                            request=request,
                            check_signal=self.__class__)

When debugging, the value of check_signal was <registration.backends.default.views.ActivationView>.

So the solution for fixing my listener without modifying the sender was first to restore the original code in registration/signals.py:

user_activated = Signal(providing_args=["user", "request"])

...and restore the code in registration/default/views.py:

signals.user_activated.send(sender=self.__class__,
                            user=activated_user,
                            request=request)

...and finally, to fix the listener in models.py:

from registration.backends.default.views import ActivationView
from registration.signals import user_activated
from mysite.signals import create_user_profile

user_activated.connect(create_user_profile, sender=ActivationView)

There may be a way to do this without having to import ActivationView, but I'm not sure. It does work this way. For anyone using django-registration-redux with the simple (no email) configuration, the only differences would be from registration.backends.simple.views import RegistrationView and the listener would be user_registered.connect(create_user_profile, sender=RegistrationView).

And just in case anyone is curious, here is the code from mysite/signals.py:

def create_user_profile(sender, user, **kwargs):
    """
    When user is activated, create the UserProfile. Prevents dead profiles
    from registered users who never activate.
    """
    from mysite.models import UserProfile

    UserProfile(user=user).save()

I spent days of madness and desperation acquiring the logic behind these seven little lines of code. I hope this can save someone else from such stress.

Upvotes: 1

alecxe
alecxe

Reputation: 473863

It might be that the sender should be the User class:

signals.user_activated.send(sender=activated_user.__class__,
                            user=activated_user,
                            request=request)

Upvotes: 1

Related Questions