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)
Related
I manage a physical locker with Django (DRF). Users fill out a form, authenticate via link sent to their e-mail, authorize via a pin displayed on the locker.
My view should handle three cases:
If user authenticates and authorizes successfully, pin displayed on
the locker is replaced with a generic message and the locker opens.
(Already implemented)
If the user fails to authorize within 3 minutes, locker pin is replaced with a generic message.
If a new authorization request is made by user Foo, while authorization for user Bar is still incomplete, stack the request in
a queue and wait for case 1. or case 2. to complete.
How can I:
Implement a request queue, such that the pin displayed on the locker does not get overridden/replaced when a new request comes in?
How can I wait 3 minutes for authorization to be completed before processing the next request?
View as is, in case it is useful:
if request.method == 'POST':
form = ConfirmationForm(request.POST)
if form.is_valid():
if pin == form.cleaned_data['pin']:
open_bay(jwt_token=jwt[1], pin=pin)
display_generic_message(jwt_token=jwt[1])
lock_bay(jwt_token=jwt[1], pin=pin)
return render(request, 'static/pages/request-success.html')
else:
pass
else:
form = ConfirmationForm()
return render(request, 'static/pages/confirmation.html', {'form': form})
in your model which u save the authorization u need to add a field of date_created or date_requested and must be a datetime field . then on each request you can check if 3 minutes has passed from date_requested which you saved .
you also need an is_authorized field to check if your user is authorized or not.
we assume that you are getting the user from email you sent .
user = get_object_or_404(User,email=kwargs['email'])
if user.date_request + timedelta(minutes=3) > datetime.datetime.now():
"do your authorzing stuff ..."
else:
return HttpResponse("you need to wait 3 minutes to request again")
You would need to store when the last authorization started, clear that timestamp if the user puts in the pin.
# models.py
class LockerUserQueue(models.Model):
user = models.ForeignKey(get_user_model())
locker = models.ForeignKey("yourapp.Locker")
created_at = models.DateTimeField(index=True, auto_now_add=True)
class Locker(models.Model):
last_authorization = models.DateTimeField(null=True, blank=True)
user_queue = models.ManyToManyField(through=LockerUserQueue)
class BadPin(Exception):
pass
def enqueue_user(self, user):
self.user_queue.add(user)
def process_authorization(self, user):
# do authorization for user
def process_pin(self, user, pin):
self.last_authorization = None
if validate_pin(user, pin):
# pin OK logic
else:
raise self.BadPin
# views.py
def authorize_user(request, locker_id):
locker = get_object_or_404(pk=locker_id)
locker.enqueue_user(request.user)
locker.save()
return render(request, "authorization_started.html")
def open_bay_with_pin(request, locker_id):
locker = get_object_or_404(pk=locker_id)
pin = get_pin_from_request(request)
try:
# No matter if the pin is correct, the locker.last_authorization is cleared
locker.process_pin(user, pin)
except locker.BadPin:
return render(request, "bad_pin.html")
finally:
locker.save()
return render(request, "good_pin.html")
# management/commands/process_queue.py
# you would run this by
# $ python manage.py process_queue
class Command(BaseCommand):
#transaction.atomic
def process_queue(self):
# you probably want to put the 3 min delay in your settings.py
# so you don't end up with a magic value here
for locker in LockerUserQueue.objects.filter(
Q(locker__last_authorization__isnull=True)|Q(locker__last_authorization__gt=now() - timedelta(minutes=3))
).order_by("created_at").values_list("locker", flat=True).distinct():
locker.process_authorization()
def handle(self):
while True:
self.process_queue()
# you don't want to keep querying the DB when there's nothing in the queue
sleep(5)
The code above shows how you would do it using the database as the queue, in a low volume use case this should be just fine. If the volume is high, I would store the locker last_authorization in faster storage, like redis, the queue can be maintained in redis as well. But the logic behind would be the same.
This question is about saving Facebook Profile pictures in the Django model automatically, using https://github.com/PhilipGarnero/django-rest-framework-social-oauth2 library.
Edit:
There are 2 ways to solve this question: Save the URL of the image in CharField() or Save the image itself using ImageField(). Both solutions will do.
The above library allows me to create and authenticate users using bearer tokens. I have the created the profile model:
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='userprofile')
photo = models.FileField(blank=True) # OR
######################################
url = 'facebook.com{user id}/picture/'
photo = models.CharField(default=url)
#receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
#receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
instance.userprofile.save()
Which automatically creates user profiles for each user. Now, I would like to add the following code to save the photo from Facebook. Facebook API requires user id to get this picture.
photo = 'https://facebook/{user-id}/picture/'
UserProfile.objects.create(user=instance, photo=photo)
The above is not working because
1) I can't figure out where to get the user id from.
2) The image can't be stored like that, I need to convert it to bytes or some other method.
There is a VERY simple solution for that. Use the python-social-auth pipelines.
The way this thing work is just like middleware, you can add in your settings page to the SOCIAL_AUTH_PIPELINE section a function that will run every time the user is authenticated using the social_django.
An example:
In your settings page, add the following:
SOCIAL_AUTH_PIPELINE = (
'social.pipeline.social_auth.social_details',
'social.pipeline.social_auth.social_uid',
'social.pipeline.social_auth.auth_allowed',
'social.pipeline.social_auth.social_user',
'social.pipeline.user.get_username',
'social.pipeline.user.create_user',
'social.pipeline.social_auth.associate_user',
'social.pipeline.social_auth.load_extra_data',
'social.pipeline.user.user_details',
'home.pipeline.save_profile',
)
Look at home.pipeline.save_profile, this is a new pipeline in home.pipeline file. (Change it into your own user module folder)
In there (home.pipeline) add the following:
from .models import UserProfile
def save_profile(backend, user, response, *args, **kwargs):
if backend.name == "facebook":
UserProfile.objects.create(
user=user,
photo_url=response['user']['picture']
)
This is an example. You need to change it for get/update in case the user already logged in.
Also, try and play with the response argument, there might be different data you can use there.
One last thing, make sure you add the picture attribute into your settings:
SOCIAL_AUTH_FACEBOOK_PROFILE_EXTRA_PARAMS = {
'fields': 'id, name, email, picture'
}
http://python-social-auth.readthedocs.io/en/latest/backends/facebook.html
https://godjango.com/122-custom-python-social-auth-pipeline/
https://github.com/python-social-auth/social-app-django
The above answers may not work (it did not work for me) as the facebook profile URL does not work anymore without accesstoken. The following answer worked for me.
def save_profile(backend, user, response, is_new=False, *args, **kwargs):
if is_new and backend.name == "facebook":
# The main part is how to get the profile picture URL and then do what you need to do
Profile.objects.filter(owner=user).update(
imageUrl='https://graph.facebook.com/{0}/picture/?type=large&access_token={1}'.format(response['id'],response['access_token']))
add to the pipeline in setting.py,
SOCIAL_AUTH_PIPELINE+ = ('<full_path>.save_profile')
I am registering the data in user model and also want to save the profile data same time like first_name and last_name in profile model.
So I have used django signals to store the profile information and send the mail to user.
But we are unable to get the first_name and last_name in signal file:
#---------------------------- Create profile at the time of registration --------------------------#
def register_profile(sender, **kwargs):
if kwargs.get('created'):
user = kwargs.get('instance')
request = kwargs.get("request")
if user.id is not None and user._disable_signals is not True:
profile = Profile(user=user)
if user.status is not 1:
#------------------- Send the registration mail to user and it have confirmation link ----------#
salt = hashlib.sha1(str(random.random())).hexdigest()[:5]
activation_key = hashlib.sha1(salt+user.email).hexdigest()
key_expires = datetime.datetime.today() + datetime.timedelta(2)
#print user
profile.activation_key = activation_key
profile.key_expires = key_expires
#--------------------- End -------------------------------------------------------------#
profile.save()
if user.status is not 1:
user = model_to_dict(user)
BaseSendMail.delay(user,type='account_confirmation',key = activation_key)
return
post_save.connect(register_profile, sender=User, dispatch_uid='register_profile')
#-------------------------- End ---------------------------------------------------------------------#
In above code I am unable to get first_name and last_name data which is sent at the time of registration.Also I would like to mention that first_name and last_name fields belong to profile model.
No, and you shouldn't try. Signals could be executed from anywhere: a management script, a Celery task, all sorts of places that might not have a request.
You could store the data on the User instance temporarily, as you do with the _disable_signals attribute. However I suspect that this is not really best done in a signal; since you're saving the result of a form submission, and it depends on the data in that form, you should really do that in the view or the form itself.
I did this and it worked.
Not sure about it's impact on performance etc.
some_file.py:
data = {}
middleware.py:
class MyMiddleware(object):
def process_request(self):
from path.to.some_file import data
data['request'] = self.request
signals / model_method / manager / template tag / any where else:
from path.to.some_file import data
request = data.get('request')
I have an Image model that has a user field foreignkey from django user
class Image(models.Model):
image = models.ImageField()
user = models.ForeignKey(User)
#more specific model fields
I want media files to be served only to users that are logged in and uploaded the media. So I used X-SendFile header in apache using the following view
#owns_media
def media_xsendfile(request, path):
print "inside media_xsendfile view"
print os.path.join(settings.MEDIA_ROOT,path)
response = HttpResponse()
response['Content-Type']=''
response['X-Sendfile']= smart_str(os.path.join(settings.MEDIA_ROOT, path))
return response
The owns_media is a decorator that checks if the user logged in is the user that has uploaded the picture and permitts the view to run or raise a PermissionDenied exception. This is the decorator
def owns_media(view):
"""Decorator to check if users has permission to access media"""
def wrapper(request, *args, **kw):
path = kw['path']
user = request.user
image = Image.objects.get(image=path)
image_user = image.user
if user==image_user:
return view(request, *args, **kw)
else:
raise PermissionDenied
return wrapper
But it won't work. I loggedn in to a user got the picture, then logged out and tried to access it and it did serve the picture while it shouldn't. Am I doing something wrong?
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.