How to queue requests in Django? - python

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.

Related

What are common technique, to ensure code logic in TemplateView's get_context_data being executed once?

Currently, I have the following payment button.
PAY NOW
When user click on the link, here's what happens behind the scene.
Get token input from user.
Payment gateway processes the received token, and return success/fail result.
Display success/fail result to user.
What I wish is, when user click on refresh button in browser, step 1 & step 2 will be skipped.
We don't want user makes duplicated payment.
But, only displayed previous gateway success/fail result.
Here's the TemplateView code.
class SubscribeView(TemplateView):
template_name = 'subscribe.html'
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super(SubscribeView, self).get_context_data(**kwargs)
# Step 1: Get token input from user
#
payload_nonce = self.request.GET.get('payload_nonce')
# Step 2: Payment gateway processes the
# received token, and return success/fail result.
...
...
##############################
# Submit it to payment gateway
##############################
...
...
# Step 3: Display success/fail result to user.
#
context['is_success'] = result.is_success
context['message'] = result.message
return context
May I know, what are common technique, to ensure code logic in TemplateView's get_context_data being executed once?
The real problem here is that you are updating state in an operation that should be idempotent.
The proper solution is to use a dedicated view only accepting POST requests (which mean you need an HTML form instead of a link) that will handle steps 1 & 2 and then redirect to your template view. You will of course have to store the result (and the associated token) somewhere so you can 1. avoid resending a payment twice for the same token and 2. retrieve the results associated with the token in the template view's get_context_data method.
NB : you can also, of course, handle both the GET and POST requests in the same view, but then a TemplateView might not be the best choice (actually class-based-views are seldom the best choice unless you need inheritance - function based views are usually way much simpler).
Thanks to bruno desthuilliers. This is how to code being refactor.
from django.views.generic import TemplateView
from django.views.generic import View
class SubscribeView(View):
def post(self, request):
# Step 1: Get token input from user
#
payload_nonce = self.request.POST.get("payload_nonce")
# Step 2: Payment gateway processes the
# received token, and return success/fail result.
...
...
##############################
# Submit it to payment gateway
##############################
...
...
# Redirect to SubscribeDoneView, for page rendering purpose.
return redirect(reverse('subscribe_done') + query_string)
class SubscribeDoneView(TemplateView):
template_name = 'subscribe_done.html'
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super(SubscribeDoneView, self).get_context_data(**kwargs)
# Step 3: Display success/fail result to user.
#
is_success = (self.request.GET.get('is_success') == 'True')
message = self.request.GET.get('message')
context['is_success'] = is_success
if is_success is False and message is not None:
context['message'] = message
return context

Can we get the request object data in signal

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')

django-allauth: Check whether user signed up using a social account

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)

Get first name from Facebook with django-socialregistration

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.

Python - Inheritance - Facebook application

I have a question about how the code run in inheritance in Python. It might look like a dummy question somehow, but I a new to Python.
This a code snippet from some Facebook application I am working on:
class BaseHandler(webapp.RequestHandler):
facebook = None
user = None
def initialize(self, request, response):
"""General initialization for every request"""
super(BaseHandler, self).initialize(request, response)
try:
self.init_facebook()
except Exception, ex:
self.log_exception(ex)
raise
def init_facebook(self):
"""Sets up the request specific Facebook and user instance"""
facebook = Facebook()
user = None
# Initially Facebook request comes in as a POST with a signed_request
if u'signed_request' in self.request.POST:
facebook.load_signed_request(self.request.get('signed_request'))
# We reset the method to GET because a request from Facebook with a
# signed_request uses POST for security reasons, despite it
# actually being a GET. In a web application this causes loss of request.POST data.
self.request.method = u'GET'
self.set_cookie(
'u', facebook.user_cookie, datetime.timedelta(minutes=1440))
elif 'u' in self.request.cookies:
facebook.load_signed_request(self.request.cookies.get('u'))
# Try to load or create a user object
if facebook.user_id:
user = User.get_by_key_name(facebook.user_id)
if user:
# Update stored access_token
if facebook.access_token and \
facebook.access_token != user.access_token:
user.access_token = facebook.access_token
user.put()
# Refresh data if we failed in doing so after a realtime ping.
if user.dirty:
user.refresh_data()
# Restore stored access_token if necessary
if not facebook.access_token:
facebook.access_token = user.access_token
if not user and facebook.access_token:
me = facebook.api(u'/me', {u'fields': _USER_FIELDS})
try:
friends = [user[u'id'] for user in me[u'friends'][u'data']]
user = User(key_name=facebook.user_id,
user_id=facebook.user_id, friends=friends,
access_token=facebook.access_token, name=me[u'name'],
email=me.get(u'email'), picture=me[u'picture'])
user.put()
except KeyError, ex:
pass # Ignore if can't get the minimum fields.
self.facebook = facebook
self.user = user
This is another class that inherits from BaseHandler
class RecentRunsHandler(BaseHandler):
"""Show recent runs for the user and friends"""
def get(self):
if self.user:
friends = {}
for friend in select_random(
User.get_by_key_name(self.user.friends), 30):
friends[friend.user_id] = friend
self.render(u'runs',
friends=friends,
user_recent_runs=Run.find_by_user_ids(
[self.user.user_id], limit=5),
friends_runs=Run.find_by_user_ids(friends.keys()),
)
else:
self.render(u'welcome')
Does the initialize function in the BaseHandler get called when the RecentRunsHandler is called?
I am asking this because if after the user "allow" the application (and the user data is saved in the database) ... The application still redirects him to the welcoming page where the Facebook-login button exists.
To make it more clear, the application can't know that the user has authorized him before.
Probably not. Perhaps you should def initialize to def __init__? Python objects get instantiated through the __init__ method, not through any initialize() method. Since you don't seem to have any explicit calls to initialize(), it will probably not be called.

Categories