With django-socialregistration what should I do to get the first name from Facebook at the moment of the Facebook Connect?
I tried to put these lines in django-socialregistration/views.py:
graph = request.facebook.graph
fb_profile = graph.get_object("me")
user.first_name = fb_profile['first_name']
user.save()
in the method post(self, request) after user = profile.authenticate() but I get this error when I try to connect:
int() argument must be a string or a number, not 'AnonymousUser'
Why? The error occurs at the first line graph = request.facebook.graph
The code of the django-socialregistration view:
class Setup(SocialRegistration, View):
"""
Setup view to create new Django users from third party APIs.
"""
template_name = 'socialregistration/setup.html'
def get_form(self):
"""
Return the form to be used. The return form is controlled
with ``SOCIALREGISTRATION_SETUP_FORM``.
"""
return self.import_attribute(FORM_CLASS)
def get_username_function(self):
"""
Return a function that can generate a username. The function
is controlled with ``SOCIALREGISTRATION_GENERATE_USERNAME_FUNCTION``.
"""
return self.import_attribute(USERNAME_FUNCTION)
def get_initial_data(self, request, user, profile, client):
"""
Return initial data for the setup form. The function can be
controlled with ``SOCIALREGISTRATION_INITIAL_DATA_FUNCTION``.
:param request: The current request object
:param user: The unsaved user object
:param profile: The unsaved profile object
:param client: The API client
"""
if INITAL_DATA_FUNCTION:
func = self.import_attribute(INITAL_DATA_FUNCTION)
return func(request, user, profile, client)
return {}
def generate_username_and_redirect(self, request, user, profile, client):
"""
Generate a username and then redirect the user to the correct place.
This method is called when ``SOCIALREGISTRATION_GENERATE_USERNAME``
is set.
:param request: The current request object
:param user: The unsaved user object
:param profile: The unsaved profile object
:param client: The API client
"""
func = self.get_username_function()
user.username = func(user, profile, client)
user.save()
profile.user = user
profile.save()
user = profile.authenticate()
self.send_connect_signal(request, user, profile, client)
self.login(request, user)
self.send_login_signal(request, user, profile, client)
self.delete_session_data(request)
return HttpResponseRedirect(self.get_next(request))
def get(self, request):
"""
When signing a new user up - either display a setup form, or
generate the username automatically.
"""
# I want some validation here, hacked up in the generic callback
try:
urlfrom = request.session['urlfrom']
match = resolve(urlfrom)
username, code = match.args
checkcode, referrer, ticket = utils.register_validate(username, code)
except:
return http.HttpResponseServerError()
# validation end
try:
user, profile, client = self.get_session_data(request)
except KeyError:
return self.render_to_response(dict(
error=_("Social profile is missing from your session.")))
if GENERATE_USERNAME:
return self.generate_username_and_redirect(request, user, profile, client)
form = self.get_form()(initial=self.get_initial_data(request, user, profile, client))
return self.render_to_response(dict(form=form))
def post(self, request):
"""
Save the user and profile, login and send the right signals.
"""
try:
user, profile, client = self.get_session_data(request)
except KeyError:
return self.render_to_response(dict(
error=_("A social profile is missing from your session.")))
form = self.get_form()(request.POST, request.FILES,
initial=self.get_initial_data(request, user, profile, client))
if not form.is_valid():
return self.render_to_response(dict(form=form))
user, profile = form.save(request, user, profile, client)
# validation count up referrals, tickets, etc.
try:
urlfrom = request.session['urlfrom']
match = resolve(urlfrom)
username, code = match.args
checkcode, referrer, ticket = utils.register_validate(username, code)
except:
return http.HttpResponseServerError()
utils.register_accounting(checkcode, referrer, ticket, user)
user = profile.authenticate()
self.send_connect_signal(request, user, profile, client)
self.login(request, user)
self.send_login_signal(request, user, profile, client)
self.delete_session_data(request)
# added by me
graph = request.facebook.graph
fb_profile = graph.get_object("me")
user.first_name = fb_profile['first_name']
user.save()
#
return HttpResponseRedirect(self.get_next(request))
let me take a stab at it. From where your code is it appears right above it you're killing the session self.delete_session_data(request). This might take away the session key or auth token. Try your code above that line.
It seems like you may have an AnonymousUser object in request.user when you access request.facebook.graph. Check to see if your user is_authenticated (more docs on AnonymousUser):
request.user.is_authenticated()
Another thing to play with is pdb from inside the dev server (manage.py runserver). The easiest way to use it is by using this line of code to put in a break point just before your code is bombing out:
import pdb; pdb.set_trace()
That will give you a prompt where you can look around at variables in context.
Related
I'm trying to add google auth service to my site, it is working correctly in localhost, but in the server, I've got an error when a new user tries to sign up although the old users can connect their account to google account without any problems
get() returned more than one User -- it returned 2!
there is no duplicated either in the user model or social account
model.
before I added social auth users could only log in or sign in with their email addresses, so now my AUTHENTICATION_BACKENDS is like that:
AUTHENTICATION_BACKENDS = (
'user_profile.backends.EmailBackend',
'allauth.account.auth_backends.AuthenticationBackend',)
and my EmailBackend file in like this:
class EmailBackend(ModelBackend):
def authenticate(self, username=None, password=None, **kwargs):
UserModel = get_user_model()
try:
user = UserModel.objects.get(email=username.lower())
except UserModel.DoesNotExist:
return None
else:
if user.check_password(password):
return user
return None
class MySocialAccountAdapter(DefaultSocialAccountAdapter):
def pre_social_login(self, request, sociallogin):
user = sociallogin.user
if user.id:
return
try:
customer = User.objects.get(email=user.email) # if user exists, connect the account to the existing account and login
sociallogin.state['process'] = 'connect'
perform_login(request, customer, 'none')
except User.DoesNotExist:
pass
and in my settings.py:
SOCIALACCOUNT_ADAPTER = 'user_profile.backends.MySocialAccountAdapter'
thanks a lot, it has been a disaster
I am running an example django app from this library, here is the whole code.
I would like to add the login part of this app, i want to add more fields to the login view but i really don't understand how to do that, because the app does not have it's own view, but it's just calling the module's own login view. But what if i would like to use this library for my own project? Would i be forced to use their login view? How can i edit it?
Here is the login view that the example is calling to handle authentication:
core.py
#class_view_decorator(sensitive_post_parameters())
#class_view_decorator(never_cache)
class LoginView(IdempotentSessionWizardView):
"""
View for handling the login process, including OTP verification.
The login process is composed like a wizard. The first step asks for the
user's credentials. If the credentials are correct, the wizard proceeds to
the OTP verification step. If the user has a default OTP device configured,
that device is asked to generate a token (send sms / call phone) and the
user is asked to provide the generated token. The backup devices are also
listed, allowing the user to select a backup device for verification.
"""
template_name = 'two_factor/core/login.html'
form_list = (
('auth', AuthenticationForm),
('token', AuthenticationTokenForm),
('backup', BackupTokenForm),
)
idempotent_dict = {
'token': False,
'backup': False,
}
def has_token_step(self):
return default_device(self.get_user())
def has_backup_step(self):
return default_device(self.get_user()) and \
'token' not in self.storage.validated_step_data
condition_dict = {
'token': has_token_step,
'backup': has_backup_step,
}
redirect_field_name = REDIRECT_FIELD_NAME
def __init__(self, **kwargs):
super(LoginView, self).__init__(**kwargs)
self.user_cache = None
self.device_cache = None
def post(self, *args, **kwargs):
"""
The user can select a particular device to challenge, being the backup
devices added to the account.
"""
# Generating a challenge doesn't require to validate the form.
if 'challenge_device' in self.request.POST:
return self.render_goto_step('token')
return super(LoginView, self).post(*args, **kwargs)
def done(self, form_list, **kwargs):
"""
Login the user and redirect to the desired page.
"""
login(self.request, self.get_user())
redirect_to = self.request.POST.get(
self.redirect_field_name,
self.request.GET.get(self.redirect_field_name, '')
)
if not is_safe_url(url=redirect_to, allowed_hosts=[self.request.get_host()]):
redirect_to = resolve_url(settings.LOGIN_REDIRECT_URL)
device = getattr(self.get_user(), 'otp_device', None)
if device:
signals.user_verified.send(sender=__name__, request=self.request,
user=self.get_user(), device=device)
return redirect(redirect_to)
def get_form_kwargs(self, step=None):
"""
AuthenticationTokenForm requires the user kwarg.
"""
if step == 'auth':
return {
'request': self.request
}
if step in ('token', 'backup'):
return {
'user': self.get_user(),
'initial_device': self.get_device(step),
}
return {}
def get_device(self, step=None):
"""
Returns the OTP device selected by the user, or his default device.
"""
if not self.device_cache:
challenge_device_id = self.request.POST.get('challenge_device', None)
if challenge_device_id:
for device in backup_phones(self.get_user()):
if device.persistent_id == challenge_device_id:
self.device_cache = device
break
if step == 'backup':
try:
self.device_cache = self.get_user().staticdevice_set.get(name='backup')
except StaticDevice.DoesNotExist:
pass
if not self.device_cache:
self.device_cache = default_device(self.get_user())
return self.device_cache
def render(self, form=None, **kwargs):
"""
If the user selected a device, ask the device to generate a challenge;
either making a phone call or sending a text message.
"""
if self.steps.current == 'token':
self.get_device().generate_challenge()
return super(LoginView, self).render(form, **kwargs)
def get_user(self):
"""
Returns the user authenticated by the AuthenticationForm. Returns False
if not a valid user; see also issue #65.
"""
if not self.user_cache:
form_obj = self.get_form(step='auth',
data=self.storage.get_step_data('auth'))
self.user_cache = form_obj.is_valid() and form_obj.user_cache
return self.user_cache
def get_context_data(self, form, **kwargs):
"""
Adds user's default and backup OTP devices to the context.
"""
context = super(LoginView, self).get_context_data(form, **kwargs)
if self.steps.current == 'token':
context['device'] = self.get_device()
context['other_devices'] = [
phone for phone in backup_phones(self.get_user())
if phone != self.get_device()]
try:
context['backup_tokens'] = self.get_user().staticdevice_set\
.get(name='backup').token_set.count()
except StaticDevice.DoesNotExist:
context['backup_tokens'] = 0
if getattr(settings, 'LOGOUT_REDIRECT_URL', None):
context['cancel_url'] = resolve_url(settings.LOGOUT_REDIRECT_URL)
elif getattr(settings, 'LOGOUT_URL', None):
warnings.warn(
"LOGOUT_URL has been replaced by LOGOUT_REDIRECT_URL, please "
"review the URL and update your settings.",
DeprecationWarning)
context['cancel_url'] = resolve_url(settings.LOGOUT_URL)
return context
I have used this package in one of my projects and many complicated scenarios can come up with it. There can be a lot of ways you can customize this view. As you mentioned, you need extra fields in login form, here is one method you can use if you just want extra fields in login form.
Step 1 Create your own login form with extra fields
You can create your own login form, inherit from django's builtin one or inherit from the form they are using for login. Add extra fields in it.
class YourLoginForm(AuthenticationForm):
pass
# your extra fields and functionality here
Step 2 Inherit from login view from package and use your form
You have to create a login view inherited from package's builtin login view and add your login form along with other ones like this
from TWO_FACTOR_AUTU import LoginView
class YourLoginView(LoginView):
form_list = (
('auth', YourLoginForm),
('token', AuthenticationTokenForm),
('backup', BackupTokenForm),
)
Use this view with appropriate routing for handling authentication.
Hope this helps
If I move specific page after login, I lose my authentication in django.
And I move another page, I get my authentication again without login.
def user(request, user_id):
"""
Display a user
:param request: request
:param user_id: user id
:return: render
"""
_user = get_object_or_404(User, id=user_id)
_groups = _user.groups
return render(
request,
'archive/user/user.html',
{
'user': _user,
'groups': _groups.exclude(privacy='CLOSED'),
}
)
This is view code with problem.
If having class based views you should use LoginRequiredMixins.
This will check if the user is authenticated to access the page or not.
If authenticated, then the page gets displayed otherwise you can redirect to the login page again.
More about Login Required Mixing
Example :
from django.contrib.auth.mixins import LoginRequiredMixin
class home(LoginRequiredMixin,View):
login_url = "/"
def get(self,request):
user = request.user
s = Person.objects.get(pk=user.id)
return render(request,'chat/home.html',locals())
in the above example, login_url is where you provide the link of redirection if the user is not authenticated. Here I have redirected to the root i.e., the login page.
You can define your views like this.
For function-based views, you can use a login_required decorator.
You should also use Django sessions for security.
You can use this way to login too if you want, works like a charm :
class Login(View):
template_name = ['your_app_name/login.html', 'your_app_name/home.html']
def get(self, request, *args, **kwargs):
form = UsersForm()
return render(request, self.template_name[0],{"form":form,})
def post(self, request, *args, **kwargs):
username = request.POST['username']
password = request.POST['password']
user = authenticate(username=username, password=password)
if user is not None:
login(request, user)
#if login is succesfull it takes you to home page:
return render_to_response(self.template_name[1],RequestContext(request))
else:
#if is not, takes you to login page again
return HttpResponseRedirect(reverse('cost_control_app:login'))
I am using the OAUTH API to verify access to the users google calendar. They are not logging into my system using OAUTH, just accepting my site accessing their data.
The problem is, if the user is logged out of their google account and after they hit verify, it force logs them out of their session and I have no way of linking them back up.
This DOES work if they are already logged into the google account in their browser session and hit accept, they will be redirected to the right page.
I replicate the error when the cache, cookies are clear and they need to relogin into their google account to verify.
I've tried storing the session ID, etc, but the request parameter is not containing the same request data as the initial view, so there is a conflict in the data I am trying to retrieve.
The user is logged in using the standard Django libraries for the credentials model.
CODE
FLOW = flow_from_clientsecrets(
CLIENT_SECRETS,
scope='https://www.googleapis.com/auth/calendar.readonly',
redirect_uri='http://127.0.0.1:8000/oauth2callback')
'''''''''''''''''''''''''''''''''''''''''''''''''''
Main function dealing with auth verification
'''''''''''''''''''''''''''''''''''''''''''''''''''
def index(request):
current_user = User.objects.get(username=request.user.username)
storage = Storage(CredentialsModel, 'id', current_user, 'credential')
credential = storage.get()
if credential is None or credential.invalid == True:
FLOW.params['state'] = xsrfutil.generate_token(settings.SECRET_KEY,
request.user.id)
authorize_url = FLOW.step1_get_authorize_url()
return redirect(authorize_url)
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''
User then calls the data function once authenticated
''''''''''''''''''''''''''''''''''''''
def auth_return(request):
print("THE CURRENTLY REQUESTED USER IN THIS SESSION REQUEST IS %s"%(request.user.username))
credential = FLOW.step2_exchange(request.REQUEST)
try:
current_user = User.objects.get(id=request.user.id)
except:
return HttpResponseRedirect("/login")
storage = Storage(CredentialsModel, 'id', current_user, 'credential')
storage.put(credential)
return HttpResponseRedirect("/get_cal")
Ok, so this was a bit more involved than I thought.
I fixed this by adding a state parameter of the currently logged in username.
logged_in_username = request.user.username
user_data = {'var_test' : logged_in_username}
pass_param = urllib.urlencode(user_data)
FLOW.params['state']=pass_param
authorize_url = FLOW.step1_get_authorize_url()
This gave me the ability to query the user from the DB via the User model in Django contrib. I parsed out the state var from the URL:
#Get the currently logged in username
user_variable_data = str(FLOW.params['state'])
#get rid of the var_test= preprended text data for parsing reasons
user_variable_data = user_variable_data[9:]
#Get that user from the database in the form of a user object
current_user = User.objects.get(username=user_variable_data)
and then built a custom backend authentication file to auth the user without a password to maintain the request like nothing weird ever even happened.
user = authenticate(username=user_variable_data)
login(request, user)
print("AUTHENTICATED")
Appended this to settings file
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
'procrastinate.auth_backend.PasswordlessAuthBackend',
)
Custom Backend File
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth.models import User
class PasswordlessAuthBackend(ModelBackend):
"""Log in to Django without providing a password.
"""
def authenticate(self, username=None):
try:
return User.objects.get(username=username)
except User.DoesNotExist:
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
So I have integrated django-allauth in my app, and now users have the ability to log in via instagram. Let's say I have a model called UserProfile and it has a field
user_avatar = models.ImageField(upload_to='profile_images', blank=True, default=None)
And with that I have a signal that creates a user profile as soon as a new user registers:
def create_user_profile(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
post_save.connect(create_user_profile, sender=User)
So usually when the user registers the user_avatar is blank since the default is set as None, now I want to add in the signal(if that's the correct way of doing it), to check if the user created his account via signing in using instagram, to go and fetch his profile picture and use it in the user_avatar. I think it's possible https://instagram.com/developer/endpoints/users/, but since I am a complete noob in python and django I don't know how to exactly do it.
So I found this signal from the django-allauth docs allauth.socialaccount.signals.pre_social_login(request, social_login) so this states that I can check that the user has signed up using a social account, but how would I use it with my create_user_profile function? The steps that I thought of is to first create the profile which I did and then to check whether the user signed up using a social account or not, if they did then the user_avatar which use their instagram profile picture and if not it would stay as none.
And as a plus I know that I can fetch the users social account profile picture in a template using {{user.socialaccount_set.all.0.get_avatar_url}}, but I don't want to do it via templates rather than doing it via Models which is the best way.
This might look really stupid but I gave it a go and tried to come up with something (this is what a newbie thinks would work, I thought this on top of my head, as I have no idea if this how signals work)
def create_user_profile(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
def pre_social_login(request, social_login):
user_logged_social = social_login.account.user
if user_logged_social:
UserProfile.objects.get(user_avatar=user_logged_social.profile_picture)
else:
pass
post_save.connect(create_user_profile, sender=User)
UPDATE
Got it working with the help of #bellum! Thank you!
Here is the code that I used:
models.py
class UserProfile(models.Model):
user = models.OneToOneField(User, related_name="profile")
user_avatar = models.ImageField(upload_to='profile_images'
blank=True,
default=None)
def __unicode__(self):
return self.user.username
class Meta:
verbose_name_plural = "User Profiles"
def create_user_profile(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
post_save.connect(create_user_profile, sender=User)
3utils.py
def download_file_from_url(url):
# Stream the image from the url
try:
request = requests.get(url, stream=True)
except requests.exceptions.RequestException as e:
# TODO: log error here
return None
if request.status_code != requests.codes.ok:
# TODO: log error here
return None
# Create a temporary file
lf = tempfile.NamedTemporaryFile()
# Read the streamed image in sections
for block in request.iter_content(1024 * 8):
# If no more file then stop
if not block:
break
# Write image block to temporary file
lf.write(block)
return files.File(lf)
class SocialAccountAdapter(DefaultSocialAccountAdapter):
def save_user(self, request, sociallogin, form=None):
user = super(SocialAccountAdapter, self).save_user(request, sociallogin, form)
url = sociallogin.account.get_avatar_url()
avatar = download_file_from_url(url)
if avatar:
profile = user.profile # access your profile from user by correct name
profile.user_avatar.save('avatar%d.jpg' % user.pk, avatar)
return user
settings.py
SOCIALACCOUNT_ADAPTER = 'main.s3utils.SocialAccountAdapter'
The signal for creating the profile on sign up in my models was left the same, just added an SocialAccountAdapter!
I have done the same task for Facebook provider. allauth gives possibility to achive this in another way. I think you don't need to get avatar every time user logins in your system. If yes then you can override class like this:
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
class SocialAccountAdapter(DefaultSocialAccountAdapter):
def save_user(self, request, sociallogin, form=None):
user = super(SocialAccountAdapter, self).save_user(request, sociallogin, form)
url = sociallogin.account.get_avatar_url()
avatar = download_file_from_url(url) # here you should download file from provided url, the code is below
if avatar:
profile = user.user_profile # access your profile from user by correct name
profile.user_avatar.save('avatar%d.jpg' % user.pk, avatar)
return user
You should add this line to your config: SOCIALACCOUNT_ADAPTER = 'path-to-your-adapter.SocialAccountAdapter'.
As result this code will be called only during new socialaccount registration process, fetch avatar url, download it and save in your User model.
import requests
import tempfile
from django.core import files
def download_file_from_url(url):
# Stream the image from the url
try:
request = requests.get(url, stream=True)
except requests.exceptions.RequestException as e:
# TODO: log error here
return None
if request.status_code != requests.codes.ok:
# TODO: log error here
return None
# Create a temporary file
lf = tempfile.NamedTemporaryFile()
# Read the streamed image in sections
for block in request.iter_content(1024 * 8):
# If no more file then stop
if not block:
break
# Write image block to temporary file
lf.write(block)
return files.File(lf)