How to trigger allauth.account.signals.user_signed_up signal using pytest? - django

I have a OneToOneField between my UserTrust model and django.contrib.auth.models.User that I would like to create whenever a new User registers. I thought on creating the UserTrust instance using the user_signed_up signal.
I have the following code in my AppConfig
def ready(self):
# importing model classes
from .models import UserTrust
#receiver(user_signed_up)
def create_usertrust(sender, **kwargs):
u = kwargs['user']
UserTrust.objects.create(user=u)
... and this is in my pytest test
#pytest.fixture
def test_password():
return 'strong-test-pass'
#pytest.fixture
def create_user(db, django_user_model, test_password):
def make_user(**kwargs):
kwargs['password'] = test_password
if 'username' not in kwargs:
kwargs['username'] = str(uuid.uuid4())
return django_user_model.objects.create_user(**kwargs)
return make_user
#pytest.mark.django_db
def test_my_user(create_user):
user = create_user()
assert user.usertrust.available == 1000
Still, the test fails with
django.contrib.auth.models.User.usertrust.RelatedObjectDoesNotExist: User has no usertrust.
What did I miss?

The problem with you creating the user via the django_user_model is that it doesn't actually pass through the allauth code that actually sends that signal. You've got two options:
Use a client (since I'm assuming you're using pytest-django) and fill out a registration via the allauth provided link for registering new users. That way, a signal is sent, and you can assert attributes and model instances like that.
You can simply ignore the signal and unittest the function itself. That's it. You put your trust in that single registration view not changing at all and put your trust in the package that the API will not change. I can still recommend this option, but you've been warned.
You can send the signal yourself. Not recommended in case allauth's API changes, but you could just import the signal from allauth and send it like this: user_signed_up.send(sender=self.__class__, toppings=toppings, size=size) where user_signed_up is the signal. Ref the docs: https://docs.djangoproject.com/en/dev/topics/signals/#sending-signals
Again, definitely recommend the first one in case of API changes. I can also recommend the second option just because allauth is pretty reputable and you know what going to happen without too huge of an package change, but you never know.

Related

Creating user profile after they sign up

I'm using Django Allauth. Users can either sign up using Google, Twitter, Facebook or they can sign up using their email address. Once signed up, their details will be stored in the User table. There's also another model I have called Profile that contains user information like bio, avatar, etc. I'd like to create a Profile for the user when they sign up. I looked at Allauth signals and found the user_signed_up signal to be appropriate. Here's how I wrote the code in my handlers.py file:
#receiver(user_signed_up)
def create_profile(request, user):
profile = Profile(avatar='img/blah/blah.jpg', bio='Example text', gender='M', dob='2018-01-01',
country='US', user=user)
profile.save()
I added random stuff just so I can see if it's being created or not, but for some reason when the user signs up their profile is not being created. What am I doing wrong?
You have to be sure you're importing the handlers.py module somehow in order to signal handler gets registered, you can write
import handlers
anywhere in your code, but the recommended place is in the ready method of your app config class.
References:
https://chriskief.com/2014/02/28/django-1-7-signals-appconfig/
https://docs.djangoproject.com/en/2.1/ref/applications/
You need to write signal when user model instance save signal will work there you need to write one condition this instance is new created or old modify base on this condition you can create profile
#receiver(post_save, sender=User)
def user_updated(sender, created=False, **kwargs):
user = kwargs.get('instance', None)
if user and created:
##create profile object here

How to safely access request object in Django models

What I am trying to do:
I am trying to access request object in my django models so that I can get the currently logged in user with request.user.
What I have tried:
I found a hack on this site. But someone in the comments pointed out not to do it when in production.
I also tried to override model's __init__ method just like mentioned in this post. But I got an AttributeError: 'RelatedManager' object has no attribute 'request'
Models.py:
class TestManager(models.Manager):
def user_test(self):
return self.filter(user=self.request.user, viewed=False)
class Test(models.Model):
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
super(Test, self).__init__(*args, **kwargs)
user = models.ForeignKey(User, related_name='test')
viewed = models.BooleanField(default=False)
objects = TestManager()
I trying to access request object in my Django models so that I can get the currently logged in user with request.user.
Well a problem is that models are not per se used in the context of a request. One for example frequently defines custom commands to do bookkeeping, or one can define an API where for example the user is not present. The idea of the Django approach is that models should not be request-aware. Models define the "business logic" layer: the models define entities and how they interact. By not respecting these layers, one makes the application vulnerable for a lot of problems.
The blog you refer to aims to create what they call a global state (which is a severe anti-patten): you save the request in the middleware when the view makes a call, such that you can then fetch that object in the model layer. There are some problems with this approach: first of all, like already said, not all use cases are views, and thus not all use cases pass through the middleware. It is thus possible that the attribute does not exist when fetching it.
Furthermore it is not guaranteed that the request object is indeed the request object of the view. It is for example possible that we use the model layer with a command that thus does not pass through the middleware, in which case we should use the previous view request (so potentially with a different user). If the server processes multiple requests concurrently, it is also possible that a view will see a request that arrived a few nanoseconds later, and thus again take the wrong user. It is also possible that the authentication middleware is conditional, and thus that not all requests have a user attribute. In short there are more than enough scenario's where this can fail, and the results can be severe: people seeing, editing, or deleting data that they do not "own" (have no permission to view, edit, or delete).
You thus will need to pass the request, or user object to the user_test method. For example with:
from django.http import HttpRequest
class TestManager(models.Manager):
def user_test(self, request_or_user):
if isinstance(request_or_user, HttpRequest):
return self.filter(user=request_or_user.user, viewed=False)
else:
return self.filter(user=request_or_user, viewed=False)
one thus has to pass the request object from the view to the function. Even this is not really pure. A real pure approach would only accept a user object:
class TestManager(models.Manager):
def user_test(self, user):
return self.filter(user=user, viewed=False)
So in a view one can use this as:
def some_view(request):
some_tests = Test.objects.user_test(request.user)
# ...
# return Http response
For example if we want to render a template with this queryset, we can pass it like:
def some_view(request):
some_tests = Test.objects.user_test(request.user)
# ...
return render(request, 'my_template.html', {'some_tests': some_tests})

Django - Extra context in django-registration activation email

I'm using django-registration for a project of mine.
I'd like to add some extra contextual data to the template used for email activation.
Looking into the register view source, I cannot figure out how to do it.
Any idea ?
From what I remember, you need to write your own registration backend object (easier then is sounds) as well as your own profile model that inherits from RegistrationProfile and make the backend use your custom RegistrationProfile instead (This model is where the email templates are rendered and there is no way to extend the context, so they need to be overwritten)
A simple solution is to rewrite the send_activation_email
So instead of
registration_profile.send_activation_email(site)
I wrote this in my Users model
def send_activation_email(self, registration_profile):
ctx_dict = {
'activation_key': registration_profile.activation_key,
'expiration_days': settings.ACCOUNT_ACTIVATION_DAYS,
'OTHER_CONTEXT': 'your own context'
}
subject = render_to_string('registration/activation_email_subject.txt',
ctx_dict)
subject = ''.join(subject.splitlines())
message = render_to_string('registration/activation_email.txt',
ctx_dict)
self.email_user(subject, message, settings.DEFAULT_FROM_EMAIL)
And I call it like this
user.send_activation_email(registration_profile)
I don't get what it is your problem but the parameter is just in the code you link (the last one):
def register(request, backend, success_url=None, form_class=None,
disallowed_url='registration_disallowed',
template_name='registration/registration_form.html',
extra_context=None)
That means you can do it from wherever you are calling the method. Let's say your urls.py:
from registration.views import register
(...)
url(r'/registration/^$', register(extra_context={'value-1':'foo', 'value-2':'boo'})), name='registration_access')
That's in urls.py, where usually people ask more, but, of course, it could be from any other file you are calling the method.

How to get notified when a user changes password or requests a password reset?

For a password change I am using auth_views.password_change and for the password reset auth_views.password_reset.
How can I be notified when a user successfully changes their password? I do not need to know old nor new password. Just that the event took place, and for which user.
Similarly, I would like to get notified when someone requested a password reset and also when they successfully completed the reset procedure.
Can i do the above with signals or some simple patching? Or do I need to write my own views to do this?
Create a decorator:
def notify_admins(func):
def wrapper(request, *args, **kwargs):
# send email to admins
return func(request, *args, **kwargs)
return wrapper
Then, just add wrap it around the appropriate views in your urls.py:
urlpatterns = patterns('',
...
(r'^password_change/done/$', notify_admins(auth_views.password_change_done)),
(r'^password_reset/done/$', notify_admins(auth_views.password_reset_done)),
(r'^reset/done/$', notify_admins(auth_views.password_reset_complete)),
...
)
Keep in mind that sending email directly from a view, or in this case a decorator, will tie up the request. Instead of sending the email there directly, it would be better to create a custom signal and a handler that will fire off a thread to actually send the email. Then, in the decorator, you simply send the signal.
You could write a custom password_change_form that you pass to password_change. This form would extend django's PasswordChangeForm overriding its save method to first notify you of the change and then call it's parent PasswordChangeForms save method.
Docs on password_change view:
https://docs.djangoproject.com/en/dev/topics/auth/#django.contrib.auth.views.password_change
Docs on ChangeForm:
https://docs.djangoproject.com/en/dev/topics/auth/#django.contrib.auth.forms.PasswordChangeForm
Code for PasswordChangeForm:
https://code.djangoproject.com/browser/django/trunk/django/contrib/auth/forms.py
Beginning in Django 1.9, you can define your define your own password validators. You could even simply re-define an existing one, if you like. When you do, add a method:
from django.contrib.auth.password_validation import MinimumLengthValidator
class MyPasswordValidator(MinimumLengthValidator):
def password_changed(self, password, user):
# put your password changed logic here
Be sure to include your new class in your settings as follows:
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'my_package.password_validators.MyPasswordValidator',
'OPTIONS': {
'min_length': 8,
}
},
...
]
Now, each time a password is changed by the user, your class MyPasswordValidator will be notified. In my experience, this is the best way to do this because:
When using signals to capture these events, you will also capture events where the system re-encoded an existing password due to a change in hashing parameters, in most cases, you would not want to capture these events and there's no obvious way to prevent it with signals.
You could simple add a function-call in the save() method of all your password-handling forms, but this becomes difficult when you want to do the same with the built in admin change password form and will not help you if password changes are made programmatically outside of a form.
I will caution you to be aware that the password parameter in password_changed() is in fact the user's raw password. Take care when handling this and absolutely never store this anywhere unencrypted/unhashed.
If you are already using the auth_views.password_change built in view, then it would be easy to notify yourself once they are redirected after a successful change:
https://docs.djangoproject.com/en/dev/topics/auth/#django.contrib.auth.views.password_change
password_change(request[, template_name, post_change_redirect, password_change_form])
If you set the post_change_redirect url to redirect to one of your own views, then you simply take whatever action you want in that view to send a notification (email, database updates, etc).
You could even, in your redirect view, just do your notification and then return password_change_done(request[, template_name])
You could also capture the signal and check if the password has changed. Just keep in mind that this code will run every time a user changes.
#receiver(pre_save, sender=User)
def record_password_change(sender, **kwargs):
user = kwargs.get('instance', None)
if user:
new_password = user.password
try:
old_password = User.objects.get(pk=user.pk).password
except User.DoesNotExist:
old_password = None
if new_password != old_password:
# do what you need here

Creating a custom signal for when a user activates his account

I am trying to create a custom signal for when the field auth_user.is_active becomes 1. I looked at Django's docs on signals, but was having trouble understanding how to implement custom signals.
When a user account becomes active, I want to execute the following function:
def new_user(sender, **kwargs)
profile = User.objects.get(id=user_id).get_profile()
return RecentActivity(content_object=profile, event_type=1, timestamp=datetime.datetime.now())
How would I do this. And also, what is the advantage of using signals over just doing the database insert directly? Thank you.
Here is what I did:
# in models.py
#receiver(pre_save, sender=User, dispatch_uid='get_active_user_once')
def new_user_activation_handler(sender, instance, **kwargs):
if instance.is_active and User.objects.filter(pk=instance.pk, is_active=False).exists():
profile = User.objects.get(pk=instance.pk).get_profile()
RecentActivity.objects.create(content_object=profile, event_type=1, timestamp=datetime.datetime.now())
If you want to do something when the field is changing, you can use the approach suggested by Josh, which is essentially to override the __init__ method.
Signals are generally used to communicate between apps. For example auth app sends user_logged_in signal. So if you want to do something when user is logging in, you just handle this signal, no need to patch the app.