Send confirmation email when changing the email in Django - django

I'm currently using django-registration, and it is working well (with some tricks). When the user registers, he has to check his/her mail and click on the activation link. That's fine, but...
What if the user changes the email? I would like to send him/her an email in order to confirm that he is the owner of the email address...
Is there an application, snippet, or something that would save me the time of writing it by myself?

I've faced the same issue recently. And I didn't like the idea of having another app/plugin for just that.
You can achieve that by, listening to User model's singles(pre_save, post_save) and using RegistrationProfile:
signals.py:
from django.contrib.sites.models import Site, RequestSite
from django.contrib.auth.models import User
from django.db.models.signals import post_save, pre_save
from django.dispatch import receiver
from registration.models import RegistrationProfile
# Check if email change
#receiver(pre_save,sender=User)
def pre_check_email(sender, instance, **kw):
if instance.id:
_old_email = instance._old_email = sender.objects.get(id=instance.id).email
if _old_email != instance.email:
instance.is_active = False
#receiver(post_save,sender=User)
def post_check_email(sender, instance, created, **kw):
if not created:
_old_email = getattr(instance, '_old_email', None)
if instance.email != _old_email:
# remove registration profile
try:
old_profile = RegistrationProfile.objects.get(user=instance)
old_profile.delete()
except:
pass
# create registration profile
new_profile = RegistrationProfile.objects.create_profile(instance)
# send activation email
if Site._meta.installed:
site = Site.objects.get_current()
else:
site = RequestSite(request)
new_profile.send_activation_email(site)
So whenever a User's email is changed, the user will be deactivated and an activation email will be send to the user.

Related

In django, how to sign in using Oauth, if signed up using CustomForm

My Django app has an option for login/register using CustomForm (inherited from the UserCreationForm) as well as Outh. Now the problem is if a user has already signed up using the CustomForm and if the next time he tries to log in using google Oauth then instead of logging in, google Oauth is redirecting to some other signup form (not created by me) which looks like:
But as the user is already registered, if he enters the same username/email here then it displays says username taken. So how can I resolve this issue? I mean I want the user to be able to login using Oauth even if he has signed up using the CustomForm, how can I implement that? Or even if I ask the user to fill this form to be able to use OAuth login, the problem is that his email/username are already present in the db, so he won't be able to sign up using this form.
Edit:
If that's difficult to implement then instead how can I just show a message when the user tries to login using oauth after signing up with the CustomForm, something like "You signed up using username rather than google account", rather than taking him to the socialaccount signup form?
My register function in views.py:
def register(request):
if request.method == 'POST':
form = CustomForm(request.POST or None)
if form.is_valid():
form.save()
return redirect('login')
else:
return redirect('register')
else:
return render(request, 'accounts/register.html')
forms.py looks something like this:
class CustomForm(UserCreationForm):
email = forms.EmailField()
class Meta:
model = User
fields = ("username", "email")
You can use pre_social_login signal
from allauth.exceptions import ImmediateHttpResponse
from allauth.socialaccount.signals import pre_social_login
from allauth.account.utils import perform_login
from allauth.utils import get_user_model
from django.dispatch import receiver
from django.shortcuts import redirect
from django.conf import settings
#receiver(pre_social_login)
def link_to_local_user(sender, request, sociallogin, **kwargs):
email_address = sociallogin.account.extra_data['email']
User = get_user_model()
users = User.objects.filter(email=email_address)
if users:
perform_login(request, users[0], email_verification=settings.EMAIL_VERIFICATION)
raise ImmediateHttpResponse(redirect(settings.LOGIN_REDIRECT_URL))
See https://github.com/pennersr/django-allauth/issues/215

How to prevent multiple login in Django

I'm writing a User system that cannot login at the same time.
If the account in login state in somewhere, and someone login the same account in other position. The latter one will be logged in. And the previous will be logged out.
I'm using a model with oneToOneField associated to the User model, And save session ids of this user.
The code is like below.
from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
from .myModels import JSONField
class Profile(models.Model):
user = models.OneToOneField(User, models.CASCADE)
sessionids = JSONField(null=True)
#receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
The JSONField is a field that using textField to store JSON string.
When a user login, I go to get all session ids of this user and delete all the session ids. Then I add current session id to the Profile. By doing this, I can logout in the previous position. the code is like below.
def login(request):
if request.method == "POST":
if request.user.is_authenticated:
return HttpResponse("the user session is authenticated.")
username = request.POST.get('username', '')
password = request.POST.get('password', '')
user = auth.authenticate(username=username, password=password)
if user is not None and user.is_active:
auth.login(request, user)
#remove cur user all sessions
sessionidsToDelete = request.user.profile.sessionids
if sessionidsToDelete != None:
sessions = Session.objects.filter(session_key__in=sessionidsToDelete)
for session in sessions:
session.delete()
#add cur user sessions
sessionidsToStore = user.profile.sessionids
print("sessionidsToStore = ")
print(sessionidsToStore)
print("sessionidsToDelete = ")
print(sessionidsToDelete)
if sessionidsToStore== None:
sessionidsToStore = []
else:
sessionidsToStore = list(set(sessionidsToStore) - set(sessionidsToDelete))
print("sessionidsToStore = ")
print(sessionidsToStore)
sessionidsToStore.append(request.session.session_key)
user.profile.sessionids = json.dumps(sessionidsToStore)
user.profile.save()
rotate_token(request)
return HttpResponse("login sucessful")
elif user.is_active == False:
userNotActivedHttpresponse = HttpResponse()
userNotActivedHttpresponse.status_code = 605
userNotActivedHttpresponse.reason_phrase = "This user not active"
return userNotActivedHttpresponse
else:
return HttpResponse("Please Input the correct username and password")
else:
return HttpResponseBadRequest("Please use POST to login")
But I think something will happen. When there two people want to login the same account at the same time.
For example, there are two people know the same account.
They login at the same time. It may be happen that B append B's session id to Profile after A remove all other session ids. In this situation, A and B will still in login state, and won't be logout. How could I prevent this problem?
I think you make things very complicated, by storing data in UserProfiles, etc. and then have signals, you introduce a lot of levels, and at each level, things can go wrong.
We basically need two things here: a table that maps Users to their corresponding settings. We can implement this with a UserSession model:
# models.py
from django.conf import settings
from django.db import models
from django.contrib.sessions.models import Session
class UserSession(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
session = models.OneToOneField(Session, on_delete=models.CASCADE)
So the UserSession object makes a link between User and Sessions. Now we can implement a login hook: a signal that is triggered in case a user logs in. In that case we perform two things:
we delete all Sessions (and corresponding UserSessions) of the User that are active; and
we create a new Session and corresponding UserSession that we can remove later. Like:
from django.contrib.auth import user_logged_in
from django.dispatch.dispatcher import receiver
#receiver(user_logged_in)
def remove_other_sessions(sender, user, request, **kwargs):
# remove other sessions
Session.objects.filter(usersession__user=user).delete()
# save current session
request.session.save()
# create a link from the user to the current session (for later removal)
UserSession.objects.get_or_create(
user=user,
session_id=request.session.session_key
)
Update: I wrapped this into a small reusable appĀ [GitHub] that can be installed through pip.
Since you want the user to have only one session at a time, you can call logout before you call login
...
if user is not None and user.is_active:
auth.logout(request)
auth.login(request, user)
Logout documentation: https://docs.djangoproject.com/en/3.0/topics/auth/default/#django.contrib.auth.logout

Compare values while saving upon a model in django?

this is my model
class Profile(models.Model):
activate = models.BooleanField(default=False)
Now i want to do is , whenever some one from admin panel makes it true , an email will be sent to this particular user whose account is activated.
But i want to sent mail only when the value becomes true from false. if the value is already true i dont want to send any mail .
tried this thing with post save , but it sends email after every save action on Profile Model
Here the code, that will do it (used pre_save signal):
from django.db.models.signals import pre_save
from django.dispatch import receiver
#receiver(pre_save, sender=Profile)
def profile_changed(sender, instance, *args, **kwargs):
if instance.activate:
if not instance.pk:
print "Send email to user here"
else:
activate_was = sender._default_manager.filter(pk=instance.pk)\
.values("activate").get()["activate"]
if activate_was != instance.activate:
print "Send email to user here"

django-registration auto create UserProfile

I'm using django-registration and I'm trying to connect to its signals to automatically create a UserProfile.
Signal definition:
from django.dispatch import Signal
# A new user has registered.
user_registered = Signal(providing_args=["user", "request"])
Signal send by django-registration:
def register(self, request, **kwargs):
"""
Create and immediately log in a new user.
"""
username, email, password = kwargs['username'], kwargs['email'], kwargs['password1']
User.objects.create_user(username, email, password)
# authenticate() always has to be called before login(), and
# will return the user we just created.
new_user = authenticate(username=username, password=password)
login(request, new_user)
signals.user_registered.send(sender=self.__class__,
user=new_user,
request=request)
return new_user
My signal connect:
from registration.signals import *
from core.models import UserProfile
from django.contrib.auth.models import User
def createUserProfile(sender, instance, **kwargs):
UserProfile.objects.get_or_create(user=instance)
user_registered.connect(createUserProfile, sender=User)
Needless to say no UserProfile is being created. What am I missing here?
Thanks a lot!
EDIT: I moved my connect() and its corresponding method to a model.py and still no luck.
New code:
from django.db import models
from django.contrib import auth
from django.contrib.auth import login
from core.forms import AuthForm
from registration.signals import *
from django.contrib.auth.models import User
# Create your models here.
class UserProfile(models.Model) :
user = models.ForeignKey(User, unique=True)
def __unicode__(self):
return self.user.username
def createUserProfile(sender, instance, **kwargs):
print "creating profile"
UserProfile.objects.get_or_create(user=instance)
user_registered.connect(createUserProfile, sender=User)
I'm using Pycharm to debug, and in the very beginning my breakpoint on user_registered.connect() is hit. So I assume that connect() is being registered correctly. However, I still don't see createUserProfile being run. Anything else I'm missing?
Thanks!
ANSWER: Doh. My connect and receiver code was wrong. Correct code:
def createUserProfile(sender, user, request, **kwargs):
UserProfile.objects.get_or_create(user=user)
user_registered.connect(createUserProfile)
Realized it after I read signals.py in django-registration
You need to register (connect) your signal in a module which is imported on server startup. Your file where user_registered.connect(createUserProfile, sender=User)lives is mot likely not imported on startup. From the django docs:
You can put signal handling and
registration code anywhere you like.
However, you'll need to make sure that
the module it's in gets imported early
on so that the signal handling gets
registered before any signals need to
be sent. This makes your app's
models.py a good place to put
registration of signal handlers.
http://docs.djangoproject.com/en/dev/topics/signals/#connecting-receiver-functions
So models.py of your custom app would be a good place (or any other module which is definitely imported on server startup).
Torsten is right: the alternative way is to use decorators as stated in documentation:
from registration.signals import user_registered
# ...
#receiver(user_registered)
def your_function_name_here(sender, user, request, **kwargs):
# your code here
pass
I like this way because it's compact and readable.

Django - send email on model change

I want to send an email when a specific field is changed in a model. Is it possible? Here is what I am looking for. I have a profile model that includes a BooleanField that when the administrator selects to be true I want to send user an email. I know I could put it in a "def save(self):" but, that fires off an email anytime the model is changed and the field is true. Is there a way to have it only email if the field was changed from False to True?
save method is a perfectly good place for what you want to do:
def save(self):
if self.id:
old_foo = Foo.objects.get(pk=self.id)
if old_foo.YourBooleanField == False and self.YourBooleanField == True:
send_email()
super(Foo, self).save()
You can use django-model-changes to do this without an additional database lookup:
from django.db import models
from django.dispatch import receiver
from django_model_changes import ChangesMixin
class MyModel(ChangesMixin, models.Model):
flag = models.BooleanField()
#receiver(pre_save, sender=MyModel)
def send_email_if_flag_enabled(sender, instance, **kwargs):
if instance.previous_instance().flag == False and instance.flag == True:
# send email
Something like this could help and only sends an email when change from false to true
#models.py
from django.contrib.auth.models import User
from django.db.models import signals
from django.db import models
from django.dispatch import receiver
from django.db.models.signals import pre_save
from django.conf import settings
from django.core.mail import send_mail
#signal used for is_active=False to is_active=True
#receiver(pre_save, sender=User, dispatch_uid='active')
def active(sender, instance, **kwargs):
if instance.is_active and User.objects.filter(pk=instance.pk, is_active=False).exists():
subject = 'Active account'
mesagge = '%s your account is now active' %(instance.username)
from_email = settings.EMAIL_HOST_USER
send_mail(subject, mesagge, from_email, [instance.email], fail_silently=False)
Use hook a function with your models post_save using django signals (http://docs.djangoproject.com/en/dev/ref/signals/#django.db.models.signals.post_save)
In that function use standard django mailing: http://docs.djangoproject.com/en/dev/topics/email/