How to generate Django request object with ASGIHandler class programmatically - django

I have a Django project in which I have a function called some_func that uses request inside it.
from fastapi import Depends
from fastapi.security import HTTPBasicCredentials
def foo(credentials: HTTPBasicCredentials = Depends(security),):
# Need to generate a request object
user = some_func()
def some_func(request):
x = request.GET.get('my_param')
Unfortunately, I have no chance to edit the some_func function and the only solution is to generate an empty Django request object. I looked into the ASGIHandler class, method create_request, but I couldn't figure out how to set scope and body_file params.

Related

Having the view function – how do I get the URL of a Blueprint route in Flask?

The cache system I'm using (Flask-Cache) uses URLs to calculate cache keys. From a different place in the code I get passed a view function, and then need to check if the view has been cached.
Firstly, the current iteration of Flask-Cache needs the request to calculate the cache key, but I have made a patch that fixes the deficiency for simple cases, by getting the URL with url_for(view_function.__name__, **kwargs).
The problem is that now I've started using Flask Blueprints, and simply using view_function.__name__ in url_for will not find the blueprint route (raises BuildError).
Simple Flask app
#app.route('/user/<user_id>')
def user_page(user_id):
return 'Hello user %s' % user_id
def check_cache(view_function, **kwargs):
url = url_for(view_function.__name__, **kwargs)
... use URL to check cache ...
check_cache(user_page, user_id='123')
Blueprint Flask app
from flask import Blueprint
bp = Blueprint('user_blueprint', __name__)
#bp.route('/user/<user_id>')
def user_page(user_id):
return 'Hello user %s' % user_id
def check_cache(view_function, **kwargs):
url = url_for(view_function.__name__, **kwargs) # WON'T WORK WITH BLUEPRINTS
... use URL to check cache ...
check_cache(user_page, user_id='123')
How do I get the URL for the route/view function, when it's defined via a Blueprint?
Or alternatively, how do I make cache keys that will work when using blueprints? (And can be calculated via a decorator on a view function, and doesn't clash when two or more blueprints have view functions with the same name)
Blueprints, by default, prepend the endpoint name with the blueprint name. In this way, the url_for string:
url = url_for('user_page')
Becomes:
url = url_for('user_blueprint.user_page')
Now, you are creating your url_for strings with the function name, so you can either switch to explicitly passing the endpoint strings as above, or use:
url = url_for('user_blueprint.' + view_function.__name__)
I don't know of a programmatic way to automatically find the blueprint name from the view_function.
Try with app.view_functions
def check_cache(view_function, **kwargs):
flipped_view_functions_dict = dict((v, k) for k, v in app.view_functions.iteritems())
view_name = flipped_view_functions_dict[view_function]
url = url_for(view_name, **kwargs)
... use URL to check cache ...

Django Rest Framework testing save POST request data

I'm writing some tests for my Django Rest Framework and trying to keep them as simple as possible. Before, I was creating objects using factory boy in order to have saved objects available for GET requests.
Why are my POST requests in the tests not creating an actual object in my test database? Everything works fine using the actual API, but I can't get the POST in the tests to save the object to make it available for GET requests. Is there something I'm missing?
from rest_framework import status
from rest_framework.test import APITestCase
# from .factories import InterestFactory
class APITestMixin(object):
"""
mixin to perform the default API Test functionality
"""
api_root = '/v1/'
model_url = ''
data = {}
def get_endpoint(self):
"""
return the API endpoint
"""
url = self.api_root + self.model_url
return url
def test_create_object(self):
"""
create a new object
"""
response = self.client.post(self.get_endpoint(), self.data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(response.data, self.data)
# this passes the test and the response says the object was created
def test_get_objects(self):
"""
get a list of objects
"""
response = self.client.get(self.get_endpoint())
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, self.data)
# this test fails and says the response is empty [] with no objects
class InterestTests(APITestCase, APITestMixin):
def setUp(self):
self.model_url = 'interests/'
self.data = {
'id': 1,
'name': 'hiking',
}
# self.interest = InterestFactory.create(name='travel')
"""
if I create the object with factory boy, the object is
there. But I don't want to have to do this - I want to use
the data that was created in the POST request
"""
You can see the couple lines of commented out code which are the object that I need to create through factory boy because the object does not get created and saved (although the create test does pass and say the object is created).
I didn't post any of the model, serializer or viewsets code because the actual API works, this is a question specific to the test.
First of all, Django TestCase (APITestCase's base class) encloses the test code in a database transaction that is rolled back at the end of the test (refer). That's why test_get_objects cannot see objects which created in test_create_object
Then, from (Django Testing Docs)
Having tests altering each others data, or having tests that depend on another test altering data are inherently fragile.
The first reason came into my mind is that you cannot rely on the execution order of tests. For now, the order within a TestCase seems to be alphabetical. test_create_object just happened to be executed before test_get_objects. If you change the method name to test_z_create_object, test_get_objects will go first. So better to make each test independent
Solution for your case, if you anyway don't want database reset after each test, use APISimpleTestCase
More recommended, group tests. E.g., rename test_create_object, test_get_objects to subtest_create_object, subtest_get_objects. Then create another test method to invoke the two tests as needed

Passing request to custom Django template loader

I want to write custom template loader for my Django app which looks for a specific folder based on a key that is part of the request.
Let me get into more details to be clear. Assume that I will be getting a key on every request(which I populate using a middleware).
Example: request.key could be 'india' or 'usa' or 'uk'.
I want my template loader to look for the template "templates/<key>/<template.html>". So when I say {% include "home.html" %}, I want the template loader to load "templates/india/home.html" or "templates/usa/home.html" or "templates/uk/home.html" based on the request.
Is there a way to pass the request object to a custom template loader?
I've been searching for the same solution and, after a couple days of searching, decided to use threading.local(). Simply make the request object global for the duration of the HTTP request processing! Commence rotten tomato throwing from the gallery.
Let me explain:
As of Django 1.8 (according to the development version docs) the "dirs" argument for all template finding functions will be deprecated. (ref)
This means that there are no arguments passed into a custom template loader other than the template name being requested and the list of template directories. If you want to access paramters in the request URL (or even the session information) you'll have to "reach out" into some other storage mechanism.
import threading
_local = threading.local()
class CustomMiddleware:
def process_request(self, request):
_local.request = request
def load_template_source(template_name, template_dirs=None):
if _local.request:
# Get the request URL and work your magic here!
pass
In my case it wasn't the request object (directly) I was after but rather what site (I'm developing a SaaS solution) the template should be rendered for.
To find the template to render Django uses the get_template method which only gets the template_name and optional dirs argument. So you cannot really pass the request there.
However, if you customize your render_to_response function to pass along a dirs argument you should be able to do it.
For example (assuming you are using a RequestContext as most people would):
from django import shortcuts
from django.conf import settings
def render_to_response(template_name, dictionary=None, context_instance=None, content_type=None, dirs):
assert context_instance, 'This method requires a `RequestContext` instance to function'
if not dirs:
dirs = []
dirs.append(os.path.join(settings.BASE_TEMPLATE_DIR, context_instance['request'].key)
return shortcuts.render_to_response(template_name, dictionary, context_instance, content_type, dirs)

Can i access the response context of a view tested without the test client?

I have a function which i call from a unittest. From setting some debug traces i know the function worked like a charm and has all the values correctly prepared for return.
This is what my testcode looks like (see where my ipdb.set_trace() is ):
#override_settings(REGISTRATION_OPEN=True)
def test_confirm_account(self):
""" view that let's a user confirm account creation and username
when loggin in with social_auth """
request = self.factory.get('')
request.user = AnonymousUser()
request.session={}
request.session.update({self.pipename:{'backend':'facebook',
'kwargs':{'username':'Chuck Norris','response':{'id':1}}}})
# this is the function of which i need the context:
response = confirm_account(request)
self.assertEqual(response.context['keytotest'],'valuetotest')
From what i know from this part of the Django docs, i would be able to access response.context when i have used the testing client. But when i try to access response.context like i did it, i get this:
AttributeError: 'HttpResponse' object has no attribute 'context'
Is there a way to get the special HttpResponse object of the client, without using the client?
The RequestFactory does not touch the Django middleware, and as such, you will not generate a context (i.e. no ContextManager middleware).
If you want to test the context, you should use the test client. You can still manipulate the construction of the request in the test client either using mock or simply saving your session ahead of time in the test, such as:
from django.test import Client
c = Client()
session = c.session
session['backend'] = 'facebook'
session['kwargs'] = {'username':'Chuck Norris','response':{'id':1}}
session.save()
Now when you load the view with the test client, you'll be using the session as you set it, and when you use response = c.get('/yourURL/'), you can then reference the response context using response.context as desired.
The "response.context" is incorrect for new django versions but you can use response.context_data to get the same context that passed to TemplateResponse.
Though this is an old post, I suppose this tip can be of help. You can look into using TemplateResponse (or SimpleTemplateResponse) which can be substituted for render or render_to_response.
The Django docs has more on this
Yes, you can. You have to patch render.
I'm using pytest-django
class Test:
def context(self, call_args):
args, kwargs = call_args
request_mock, template, context = args
return context
#patch('myapplication.views.render')
def test_(self, mock_render, rf):
request = rf.get('fake-url')
view(request)
context = self.context(mock_render.call_args)
keytotest = 'crch'
assert keytotest == context['keytotest']
context (sic!) can be found in Response class. As you see it says it's HTTPResponse you get back from the view function. This happened because you've called it directly. Call this function via test client and it will be okay.
response = client.get('/fobarbaz/')
response.context

Why is my app using the default method vs the method I imported?

I have a context processor where I'm trying to override get_and_delete_messages
from forum.user_messages import get_and_delete_messages
def user_messages (request):
"""
Returns session messages for the current session.
"""
messages = request.user.get_and_delete_messages()
return { 'user_messages': messages }
It's not picking up a user message that I can see in the debugging session:
ipdb> request.session['messages']
["only site Admins can use that feature."]
The app seems to be calling the get_and_delete_messages from the User model # django.contrib.auth.models:
Instead of the method I imported.
How do I get the correct model called?
When you import get_and_delete_messages, you are importing a function with that name. request.user.get_and_delete_messages is still bound to the same function implementation that it was bound to before.