Django + Tastypie - user_logged_in signal doesn't work - django

I have a Tastypie ModelResource defining various endpoints which work as expected.
I've now configured this ModelResource to have BasicAuthentication:
class Meta:
authentication = BasicAuthentication()
I've defined a couple of test users through the Django Admin Interface.
As per the Django 1.7 Documentation, I've created a signals.py in which I register a couple of test signals:
from django.core.signals import request_finished
from django.contrib.auth.signals import user_logged_in
from django.dispatch import receiver
#receiver(user_logged_in)
def on_login(sender, request, user, **kwargs):
print('******* LOGIN DETECTED *********')
#receiver(request_finished)
def on_request_finished(sender, **kwargs):
print('******* REQUEST FINISHED *******')
This is loaded successfully by my AppConfig in apps.py:
from django.apps import AppConfig
class MyAppConfig(AppConfig):
name = 'myapp'
verbose_name = 'verbose description of myapp'
def ready(self):
import myapp.signals
I use the Requests library to successfully communicate with my API, providing basic authentication credentials for one of my test users:
auth = HTTPBasicAuth(username, getpass('Enter password: '))
response = requests.get(self.url(endpoint), auth=self.auth, params = params)
The REQUEST FINISHED print shows in the Django server's output, but LOGIN DETECTED does not.
Do we have to manually fire a login signal when using Tastypie, or use some other inbuilt/custom Authentication class besides BasicAuthentication? In other words, is it expected that the user_logged_in signal wouldn't fire automatically?
Any info would be greatly appreciated.

Having inspected the Tastypie source code, it ties into the Django auth backend by calling the authenticate method, thus doesn't trigger the usual login cycle of which authenticate is one component. Consequently the login method is never called, and thus the user_logged_in signal never fires.
I ended up providing the signal myself by extending BasicAuthentication and overriding is_authenticated like so:
class MyBasicAuthentication(BasicAuthentication):
def is_authenticated(self, request, **kwargs):
orig_user = request.user
has_authenticated = super(MyBasicAuthentication, self).is_authenticated(request, **kwargs)
if has_authenticated:
was_authenticated = orig_user == request.user
if not was_authenticated:
user_logged_in.send(sender=self.__class__, request=request, user=request.user)
return has_authenticated

Related

django-allauth adapter redirects

I am leveraging django-allauth to provide Google authentication for my property management application. Here is the registration workflow I'm looking for:
A new manager goes to a registration page with a "Signup With Google" button.
They click the button and sign into Google.
On the call back from Google they are presented with a form for additional info.
When they submit this form their user account and manager profile is created and they are redirected to their company's homepage.
I have attempted to handle this "redirect to company's homepage" through adapters. The problem I am having is that the "get_login_redirect_url" function is executed after the initial Google sign-in, but before the signup form where I collect their work schedule, so I get a DoesNotExist on the adapter redirect because it was called before the managerprofile was created.
What is the proper way to do these redirects?
settings.py
ACCOUNT_SIGNUP_FORM_CLASS = 'managers.signup.ManagerProfileSignupForm'
SOCIALACCOUNT_AUTO_SIGNUP = False
SOCIALACCOUNT_ADAPTER = 'managers.adapter.ManagerSocialAccountAdapter'
ACCOUNT_ADAPTER = 'managers.adapter.ManagerAccountAdapter'
adapters.py
class ManagerSocialAccountAdapter(DefaultSocialAccountAdapter):
def get_connect_redirect_url(self, request, socialaccount):
return reverse('company_details', args=(request.user.managerprofile.company.pk,))
class ManagerAccountAdapter(DefaultAccountAdapter):
def get_login_redirect_url(self, request):
return reverse('company_details', args=(request.user.managerprofile.company.pk,))
Set the LOGIN_REDIRECT_URL on the settings.py of your application:
I have this value, to redirect to the home page:
LOGIN_REDIRECT_URL = '/'
you need to define a decorator which consists of a function which runs before the account is created. Here take a look
#imports necessary for decorator call
from allauth.exceptions import ImmediateHttpResponse
from allauth.socialaccount.signals import pre_social_login
from allauth.account.utils import perform_login
from django.dispatch import receiver
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
from allauth.socialaccount.models import SocialLogin
# defining class to run through authentication
class SocialAccountAdapter(DefaultSocialAccountAdapter):
def pre_social_login(self, request, sociallogin):
pass
# reciever defining function to hold the account before making it registered
#receiver(pre_social_login)
def link_to_local_user(sender, request, sociallogin, **kwargs):
socialemail = sociallogin.user.email
socialuname = socialemail.split('#')[0]
sociallogin.user.username = socialuname+str(sociallogin.user.pk)
if User.objects.filter(email=sociallogin.user.email).exists():
user = User.objects.get(email=sociallogin.user.email)
if user:
perform_login(request, user, email_verification='optional')
raise ImmediateHttpResponse(redirect('homePage'))
else:
SocialLogin.save(sociallogin, request, connect=False)
user = User.objects.get(email=sociallogin.user.email)
perform_login(request, user, email_verification='optional')
raise ImmediateHttpResponse(redirect('homePage'))
This is assuming you have created a signal over User model instance creation which directly creates a profile model instance also mapping the user. If not, below the model for ManagerProfile, use this:
def create_profile(sender, instance, created, **kwargs):
if created:
<ManagerProfileModel>.objects.create(userID=instance)
post_save.connect(create_profile, sender=<UserModelWhereMainAccountIsCreated>)

Redirect User to another url with django-allauth log in signal

I am using Django-allauth for my login/signup related stuff, so when a user signs up(first time) into my site, I am redirecting him to /thanks/ page by defining below setting in settings.py file
LOGIN_REDIRECT_URL = '/thanks/'
But when the user tried to log in for the next time(if already registered) I should redirect him to '/dashboard/' URL
So tried to alter that with Django-allauth signals like below which is not working at all
#receiver(allauth.account.signals.user_logged_in)
def registered_user_login(sender, **kwargs):
instance = User.objects.get_by_natural_key(kwargs['user'])
print instance.last_login==instance.date_joined,"??????????????????????????????"
if not instance.last_login==instance.date_joined:
return HttpResponseRedirect(reverse('dashboard'))
So can anyone please let me know how to redirect a user to /dashboard/ for the normal login, am I doing anything wrong in the above signal code?
Edit
After some modification according to the below answer by pennersr, my AccountAdapter class looks like below
from allauth.account.adapter import DefaultAccountAdapter
# from django.contrib.auth.models import User
class AccountAdapter(DefaultAccountAdapter):
def get_login_redirect_url(self, request):
if request.user.last_login == request.user.date_joined:
return '/registration/success/'
else:
return '/dashboard/'
But still, it is redirecting the user to /dashboard/, my logic in determining the first time user is wrong?
In general, you should not try to put such logic in a signal handler. What if there are multiple handlers that want to steer in different directions?
Instead, do this:
# settings.py:
ACCOUNT_ADAPTER = 'project.users.allauth.AccountAdapter'
# project/users/allauth.py:
class AccountAdapter(DefaultAccountAdapter):
def get_login_redirect_url(self, request):
return '/some/url/'
The two datetimes last_login and date_joined will always be different, although it might only be a few milliseconds. This snippet works:
# settings.py:
ACCOUNT_ADAPTER = 'yourapp.adapter.AccountAdapter'
# yourapp/adapter.py:
from allauth.account.adapter import DefaultAccountAdapter
from django.conf import settings
from django.shortcuts import resolve_url
from datetime import datetime, timedelta
class AccountAdapter(DefaultAccountAdapter):
def get_login_redirect_url(self, request):
threshold = 90 #seconds
assert request.user.is_authenticated()
if (request.user.last_login - request.user.date_joined).seconds < threshold:
url = '/registration/success'
else:
url = settings.LOGIN_REDIRECT_URL
return resolve_url(url)
One important remark to pennersr answer: AVOID using files named allauth.py as it will confuse Django and lead to import errors.
the answer here is very simple, you do not need any signals or overriding the DefaultAccountAdapter
in settings.py just add a signup redirect_url
ACCOUNT_SIGNUP_REDIRECT_URL = "/thanks/"
LOGIN_REDIRECT_URL = "/dashboard/"
You can simply define those two other signals using user_logged_in signal as base. A good place to put it is on a signals.py inside a accounts app, in case you have one, or in you core app. Just remember to import signals.py in you __init__.py.
from django.dispatch import receiver, Signal
pre_user_first_login = Signal(providing_args=['request', 'user'])
post_user_first_login = Signal(providing_args=['request', 'user'])
#receiver(user_logged_in)
def handle_user_login(sender, user, request, **kwargs):
first_login = user.last_login is None
if first_login:
pre_user_first_login.send(sender, user=user, request=request)
print 'user_logged_in'
if first_login:
post_user_first_login.send(sender, user=user, request=request)
#receiver(pre_user_first_login)
def handle_pre_user_first_login(sender, user, request, **kwargs):
print 'pre_user_first_login'
#receiver(post_user_first_login)
def handle_post_user_first_login(sender, user, request, **kwargs):
print 'post_user_first_login'

Django How to prevent multiple users login using the same credentials

I am developing an Django application using django auth module and would like to prevent multiple login using the same user name and password.
It should prevent multiple logins on different machines using the same user name and password. How do I achieve this in Django?
We have to keep following things in mind:
If user close the browser without logging out
If the session times out
You may try this, it logs out the first user and logs in the second. Add middleware.py in your app directory (same level as models, views etc) and add this code. Useful when the same person is using more than one device. Make sure you add this to your middleware classes: 'myapp.middleware.UserRestrict',
class UserRestrict(object):
def process_request(self, request):
"""
Checks if different session exists for user and deletes it.
"""
if request.user.is_authenticated():
cache = get_cache('default')
cache_timeout = 86400
cache_key = "user_pk_%s_restrict" % request.user.pk
cache_value = cache.get(cache_key)
if cache_value is not None:
if request.session.session_key != cache_value:
engine = import_module(settings.SESSION_ENGINE)
session = engine.SessionStore(session_key=cache_value)
session.delete()
cache.set(cache_key, request.session.session_key,
cache_timeout)
else:
cache.set(cache_key, request.session.session_key, cache_timeout)
Out of the box, Django doesn't provide you with a way to prevent concurrent sessions for the same user account, and that isn't a trivial thing to do. However, here's another question with some suggestions about how you might make this happen: How can I detect multiple logins into a Django web application from different locations?
i solve the problem with a new model, a custom decorator and custom login page
1) i created a additional model for users eg:
class SessionKey(models.Model):
user = models.OneToOneField(User,primary_key=True)
key = models.CharField(max_length=255)
2) i created custom decorator to check session key is equal or not last key.
i changed the original source code django decorators
from functools import wraps
from django.conf import settings
from django.utils.decorators import available_attrs
from django.contrib.auth.decorators import login_required
from django.shortcuts import resolve_url
from users.models import SessionKey #my additional model
def unique_login_required(view_func):
#wraps(view_func, assigned=available_attrs(view_func))
def _wrapped_view(request, *args, **kwargs):
r = False
...
#check session key is equal to last one
...
if r:
return view_func(request, *args, **kwargs)
else:
from django.contrib.auth.views import redirect_to_login
path = request.build_absolute_uri()
resolved_login_url = resolve_url(settings.LOGIN_URL)
return redirect_to_login(path,resolved_login_url)
return _wrapped_view
3) in custom login page, i updated the session key. last login always updates the stored session key.
finally, in the views, i call my decorator
from users.decorators import unique_login_required
#unique_login_required
def index(request):
...

Getting remote ip and model field name in signals

How to get remote ip and fields name in signals ?
I have tried using def ModelChangeLogger(sender, request, **Kwargs): but it throws error message takes only one argument. The code:
signals.py
def ModelChangeLogger(sender, **Kwargs):
if str(sender._meta) == str(models.DBLogEntry._meta):
return
log_time = datetime.datetime.now()
log_table_name = sender._meta.object_name
log_instance = Kwargs['instance']
log_ip = '0.0.0.0' **####### Remote ip #########**
log_change_type = ''
if 'created' in Kwargs:
log_change_type = Kwargs['created'] and 'Creation' or 'Updating'
else:
log_change_type = 'Deleting'
models.DBLogEntry.objects.create(
log_time=log_time,
log_table_name=log_table_name,
log_instance=log_instance,
log_change_type=log_change_type
)
__init__.py
from django.db.models.signals import post_save
from django.db.models.signals import pre_save
from django.db.models.signals import post_delete
from myapp.tracker.signals import ModelChangeLogger
pre_save.connect(ModelChangeLogger)
post_save.connect(ModelChangeLogger)
post_delete.connect(ModelChangeLogger)
How to get remote_add and field names from there?
It is not recommendable to have a signal handler expecting a request object due to the fact that a model save might be triggered without having a request coming from a web browser (eg. if you simply do it through the shell) or call model.save() somewhere else in your code!
You could either make your own custom signal that gets send from the views that modifiy your model or call the logging method yourself in your views.

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.