Flask Testing: Test App Request? - python

While doing the Miguel Grinberg's Flask Web Development, I got stuck while testing the gravatar code,
def test_gravatar(self):
u = User(email='john#example.com', password='cat')
with self.app.test_request_context('/'):
gravatar = u.gravatar()
gravatar_256 = u.gravatar(size=256)
gravatar_pg = u.gravatar(rating='pg')
gravatar_retro = u.gravatar(default='retro')
with self.app.test_request_context('/', base_url='https://example.com'):
gravatar_ssl = u.gravatar()
self.assertTrue('http://www.gravatar.com/avatar/' +
'd4c74594d841139328695756648b6bd6'in gravatar)
self.assertTrue('s=256' in gravatar_256)
self.assertTrue('r=pg' in gravatar_pg)
self.assertTrue('d=retro' in gravatar_retro)
self.assertTrue('https://secure.gravatar.com/avatar/' +
'd4c74594d841139328695756648b6bd6' in gravatar_ssl)
What does app.test_request_context() do and how is it different from app_context()?
Why do we even need to call with self.app.test_request_context('/')? Also, what changes can we do to shift the call to app.test_request_context() in SetUp()?

There's plenty of reading to do on the subject, so start with the documentation: app_context, test_request_context, and you can always double-check the code: app_context and test_request_context. In addition, here's an article discussion Flask's contexts.
That's a lot of links, so for a break-down:
We can see that app_context creates a new application context, while test_request_context creates a new request context. Application contexts are created in two situations: manually with app_context and when a request context is created, which, in turn, is created with test_request_context or at the beginning of the request.
So when a request comes into your application, a RequestContext is created. The creation of this object creates an application context.
Why test_request_context? You need that context to access the application when working outside of a context created by a request, like proxies that you probably recognize, like current_app, request, g, and session. Going down into the code, when you create a RequestContext with test_request_context instead of request_context, you're getting a EnvironBuilder object.

Check out tbicr 's answer here.
Specifically, this snippet of code
gravatar = u.gravatar()
gravatar_256 = u.gravatar(size=256)
gravatar_pg = u.gravatar(rating='pg')
gravatar_retro = u.gravatar(default='retro')
requires request context since it needs to access 'request' variable.
The definition of gravatar method in User Model needs 'request' variable.
def gravatar(self, size=100, default='identicon', rating='g'):
if request.is_secure: # here
url = 'https://secure.gravatar.com/avatar'
else:
url = 'http://www.gravatar.com/avatar'
hash = self.avatar_hash or hashlib.md5(self.email.encode('utf-8')).hexdigest()
return '{url}/{hash}?s={size}&d={default}&r={rating}'.format(url=url, hash=hash, size=size, default=default, rating=rating)

Related

405 method not allowed, Django + ngrok, only on my local machine

This is a legacy project that I'm working with other guys in my current job.
and is doing a very strange behavior that I cannot understand.
It's returning 405 http response status, which does not make sense, because this view already accepts POST requests
I would share a couple of snippets, I just detected that happens just in the comment that I would mark.
this is the view file, that actually accepts both methods GET and POST
#csrf_exempt
#load_checkout
#validate_cart
#validate_is_shipping_required
#require_http_methods(["GET", "POST"])
def one_step_view(request, checkout):
"""Display the entire checkout in one step."""
this is the decorator that modifies the response, and returns 405.
def load_checkout(view):
"""Decorate view with checkout session and cart for each request.
Any views decorated by this will change their signature from
`func(request)` to `func(request, checkout, cart)`."""
#wraps(view)
#get_or_empty_db_cart(Cart.objects.for_display())
def func(request, cart):
try:
session_data = request.session[STORAGE_SESSION_KEY]
except KeyError:
session_data = ''
tracking_code = analytics.get_client_id(request)
checkout = Checkout.from_storage(
session_data, cart, request.user, tracking_code)
response = view(request, checkout, cart) # in this response it returns 405.
if checkout.modified:
request.session[STORAGE_SESSION_KEY] = checkout.for_storage()
return response
return func
Any idea or clue when I can start to find out the problem?.
for the record: I didn't code this, this was working a couple of days ago, and its just happening in my local environment, on stage and production, and even the local of others developers are working just fine. I have all the requirements and the dependencies, and are updated.
BTW I'm using ngrok for tunneling
--
if your front-end use different HOST && PORT
you need to add CROS in Django app

Django Rest Framework gives 302 in Unit tests when force_login() on detail view?

I'm using Django Rest Framework to serve an API. I've got a couple tests which work great. To do a post the user needs to be logged in and I also do some checks for the detail view for a logged in user. I do this as follows:
class DeviceTestCase(APITestCase):
USERNAME = "username"
EMAIL = 'a#b.com'
PASSWORD = "password"
def setUp(self):
self.sa_group, _ = Group.objects.get_or_create(name=settings.KEYCLOAK_SA_WRITE_PERMISSION_NAME)
self.authorized_user = User.objects.create_user(self.USERNAME, self.EMAIL, self.PASSWORD)
self.sa_group.user_set.add(self.authorized_user)
def test_post(self):
device = DeviceFactory.build()
url = reverse('device-list')
self.client.force_login(self.authorized_user)
response = self.client.post(url, data={'some': 'test', 'data': 'here'}, format='json')
self.client.logout()
self.assertEqual(status.HTTP_201_CREATED, response.status_code)
# And some more tests here
def test_detail_logged_in(self):
device = DeviceFactory.create()
url = reverse('device-detail', kwargs={'pk': device.pk})
self.client.force_login(self.authorized_user)
response = self.client.get(url)
self.client.logout()
self.assertEqual(status.HTTP_200_OK, response.status_code, 'Wrong response code for {}'.format(url))
# And some more tests here
The first test works great. It posts the new record and all checks pass. The second test fails though. It gives an error saying
AssertionError: 200 != 302 : Wrong response code for /sa/devices/1/
It turns out the list view redirects the user to the login screen. Why does the first test log the user in perfectly, but does the second test redirect the user to the login screen? Am I missing something?
Here is the view:
class APIAuthGroup(InAuthGroup):
"""
A permission to allow all GETS, but only allow a POST if a user is logged in,
and is a member of the slimme apparaten role inside keycloak.
"""
allowed_group_names = [settings.KEYCLOAK_SA_WRITE_PERMISSION_NAME]
def has_permission(self, request, view):
return request.method in SAFE_METHODS \
or super(APIAuthGroup, self).has_permission(request, view)
class DevicesViewSet(DatapuntViewSetWritable):
"""
A view that will return the devices and makes it possible to post new ones
"""
queryset = Device.objects.all().order_by('id')
serializer_class = DeviceSerializer
serializer_detail_class = DeviceSerializer
http_method_names = ['post', 'list', 'get']
permission_classes = [APIAuthGroup]
Here is why you are getting this error.
Dependent Libraries
I did some searching by Class Names to find which libraries you were using so that I can re-create the problem on my machine. The library causing the problem is the one called keycloak_idc. This library installs another library mozilla_django_oidc which would turn out to be the reason you are getting this.
Why This Library Is Causing The Problem
Inside the README file of this library, it gives you instructions on how to set it up. These are found in this file. Inside these instructions, it instructed you to add the AUTHENTICATION_BACKENDS
AUTHENTICATION_BACKENDS = [
'keycloak_oidc.auth.OIDCAuthenticationBackend',
...
]
When you add this authentication backend, all your requests pass through a Middleware defined inside the SessionRefresh class defined inside mozilla_django_oidc/middleware.py. Inside this class, the method process_request() is always called.
The first thing this method does is call the is_refreshable_url() method which always returns False if the request method was POST. Otherwise (when the request method is GET), it will return True.
Now the body of this if condition was as follows.
if not self.is_refreshable_url(request):
LOGGER.debug('request is not refreshable')
return
# lots of stuff in here
return HttpResponseRedirect(redirect_url)
Since this is a middleware, if the request was POST and the return was None, Django would just proceed with actually doing your request. However when the request is GET and the line return HttpResponseRedirect(redirect_url) is triggered instead, Django will not even proceed with calling your view and will return the 302 response immediately.
The Solution
After a couple of hours debugging this, I do not the exact logic behind this middleware or what exactly are you trying to do to provide a concrete solution since this all started based off guess-work but a naive fix can be that you remove the AUTHENTICATION_BACKENDS from your settings file. While I feel that this is not acceptable, maybe you can try using another library that accomplishes what you're trying to do or find an alternative way to do it. Also, maybe you can contact the author and see what they think.
So i guess you have tested this and you get still the same result:
class APIAuthGroup(InAuthGroup):
def has_permission(self, request, view):
return True
Why do you use DeviceFactory.build() in the first test and DeviceFactory.create() in the second?
Maybe a merge of the two can help you:
def test_get(self):
device = DeviceFactory.build()
url = reverse('device-list')
response = self.client.get(url)
self.assertEqual(status.HTTP_200_OK, response.status_code)
Is this a problem with the setUp() method? From what I see, you may be setting self.authorize_user to a user that was already created on the first test.
Instead, I would create the user on each test, making sure that the user doesn't exist already, like so:
user_exists = User.objects.filter(username=self.USERNAME, email=self.EMAIL).exists()
if not user_exists:
self.authorize_user = User.objects.create_user....
That would explain why your first test did pass, why your second didn't, and why #anupam-chaplot's answer didn't reproduce the error.
Your reasoning and code looks ok.
However you are not giving the full code, there must be error you are not seeing.
Suspicious fact
It isn't be default 302 when you are not logged in.
(#login_required, etc redirects but your code doesn't have it)
Your APIAuthGroup permission does allow GET requests for non-logged-in user ( return request.method in SAFE_METHODS), and you are using GET requests (self.client.get(url))
So it means you are not hitting the endpoint that you think you are hitting (your get request is not hitting the DevicesViewSet method)
Or it could be the case you have some global permission / redirect related setting in your settings.py which could be DRF related..
eg :
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
]
}
Guess
url = reverse('device-detail', kwargs={'pk': device.pk})
might not point to the url you are thinking..
maybe there's another url (/sa/devices/1/) that overrides the viewset's url. (You might have a django view based url)
And I didn't address why you are getting redirected after force_login.
If it's indeed login related redirect, all I can think of is self.authorized_user.refresh_from_db() or refreshing the request ..
I guess some loggin related property (such as session, or request.user) might point to old instance .. (I have no evidence or fact this can happen, but just a hunch) and you better off not logging out/in for every test case)
You should make a seperate settings file for testing and add to the test command --settings=project_name.test_settings, that's how I was told to do.

Singleton object behaviour in Django

Consider I want to create a singleton object for each user accessing a particular feature in the website. This object is not a Model.
class NavApi:
__instance = None
#staticmethod
def get_instance():
""" Static access method. """
if NavApi.__instance is None:
Client()
return NavApi.__instance
In views file
#csrf_exempt
def get_folder_tree(request):
if request.method == "POST":
nav_api = NavAPI.get_instance()
folders = nav_api.listing_folders(request.POST.get('id'))
return render(request, "folder_tree.html", {'folders': folders, 'page1': False})
#csrf_exempt
def get_prev_folder_tree(request):
if request.method == "POST":
nav_api = NavAPI.get_instance()
page1,folders = nav_api.listing_prev_folder_tree()
return render(request, "folder_tree.html", {'folders': folders,'page1':page1})
The reason for using Singleton is that the class object has few members which defines the state of the folder/contents/current folder_id etc. And it should not be created for every view. It needed to be reused
But when I tried to run it as public, using ngrok and shared the linked to my friend, and testing the navigation features,
We ended up using the same singleton object. When I was browsing contents of Folder A, and he was browsing contents of Folder B, we ended up recieving contents of the same folder(either A or B).
How to overcome this?
Python is not PHP... In a typical production setup, your app will be served by many long-running processes, so you can NOT expect to reliably use process memory as a way to persist global state (a singleton IS global state) from request to request - you can have the same user having two consecutive requests served by different processes and a same process serving requests for two different users.
If you want to maintain per-user state between requests, you have to use some shared storage, the very obvious solution here being sessions.
Can I get any reference to a resource which deals a similar case?
Using sessions ? That's fully documented
Oh and yes: if all you're doing is to navigate thru folders, you are badly misusing http verbs:
The POST method is used to request that the origin server accept the entity enclosed in the request as a new subordinate of the resource identified by the Request-URI in the Request-Line.
https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
Here you are just reading resources, so you want to use GET requests.

Require new login for each page view

I am currently using code found here:
http://flask.pocoo.org/snippets/8/
And I decorate my function accordingly to have the admin authenticate when requesting a specific admin page. However, instead of requiring the admin to keep authenticating each time they admin page, I noticed that it somehow keeps track of the session and no longer requires authentication after successfully authenticating once. Is there some way to force flask to re-authenticate every time an admin requests the given decorated admin page?
Using the included snippet, there is no good way to force a user to log in every time they request the given page.
This is because that snippet is using HTTP Basic Auth and there is no good way to ask the browser to stop sending that header.
What you are looking for can be done with a custom decorator. You can use the sample below. Note that your case will be different, but you can use this as a guide.
from web import app, has_role
#app.route("/admin/my_page")
#login_required
#has_role(role="admin")
def admin_my_page():
//do stuff
Then, in your project init, or an include file you can add the following:
def has_role(role=None):
def _initial_decorator(view_func):
def _decorator(*args, **kwargs):
response = view_func(*args, **kwargs)
if g.user.user_level != role:
from flask import redirect, url_for
return redirect(url_for("no_access"))
return response
return wraps(view_func)(_decorator)
return _initial_decorator
This should at lease give you an idea of how to create a custom decorator, and then check for role permissions. You can expand this to however you need. You can put engine logic, or other checks to fit your project.

How do I pass template values when doing a redirect?

I use the blobstoreuploadhandler and hence must return a self.redirect but I need to pass values to my template. How can I do it? If I can't use template values then I suppose I can use session variables and I've included the beaker session library but I can't understand how to access the session variables in django template. Any idea how I should do it?
I use default builtin django with google app engine and I can access session variables with a request handler but I don't understand how to do it in templates:
class Sessiontest(webapp.RequestHandler):
def get(self):
# Get the session object from the environ
self.session = self.request.environ['beaker.session']
# Check to see if a value is in the session
if 'counter' in self.session:
counter = self.session['counter'] + 1
self.session['counter'] = counter
else:
self.session['counter'] = 1
counter = 1
self.session.save()
self.response.out.write('counter: %d' % counter)
Thanks
Update/edit: My problem is almost exactly like this Accessing session variable in Django template with Google App Engine (Webapp) - Python but with the library beaker instead of gaeutilities
Update: Here's some of the code. we see that using HTTP GET to pass the values won't be very good since there's an anti-spam test that should hide the values:
def post(self, view):
message = ''
challenge = self.request.get('recaptcha_challenge_field').encode('utf-8')
response = self.request.get('recaptcha_response_field').encode('utf-8')
remoteip = os.environ['REMOTE_ADDR']
cResponse = captcha.submit(
challenge,
response,
CAPTCHA_PRV_KEY,
remoteip)
if cResponse.is_valid:
isHuman=True
else:#failed anti-spam test and can try again
isHuman=False
#Reprint the form
import util
template_values = {'isHuman':isHuman,'user' : users.get_current_user(),}
template_values.update(dict(current_user=self.current_user, facebook_app_id=FACEBOOK_APP_ID))
template_values.update(dict(capture=captcha.displayhtml(public_key = CAPTCHA_PUB_KEY, use_ssl = False, error = None)))
path = os.path.join(os.path.dirname(__file__), 'market', 'market_insert.html')
self.redirect("/ai") # Here the values aren't passed on and I must make a redirect
If you are doing a redirect you might have to redirect with the variables that you wish to keep in the GET string. So you redirect from
/myview/
to
/myview2/?variable1=value
However, I think you should really look to see why you are doing redirects. I tend to do them after a POST to a form, and if the user needs to be logged on, I redirect to a login screen with
/authentication/login/?next=/view/they/wanted/to/see
Otherwise you could keep things in cookies but its not the best way to proceed.
How about letting your class inherit from multiple classes, both requesthandler class and blobstoreuploadhandler, in that way you can both render your template with values with the functions in the requesthandler, and use the functions in blobstoreuploadhandler?
A class definition with multiple base classes looks as follows:
class DerivedClassName(Base1, Base2, Base3):
<statement-1>
...
<statement-N>

Categories

Resources