In the Django testing documentation they promise that you can "Test that the correct view is executed for a given URL."
However I didn't find any possibility how to test which view was executed. I would expect that in the Response class but there's nothing about the executed view.
Thanks in advance.
You can extract the view function name thusly
from django.test.client import Client
c = Client()
response = c.get('/')
from django.core.urlresolvers import resolve
resolve(response.request["PATH_INFO"])[0].func_name
Dave's answer involves a HTTP request each time you're testing a url, which can be slow. If you just want to know what view a url resolves to, you can do that without using Client:
>>> from django.core.urlresolvers import get_resolver
>>> from myapp.views import func_to_test
>>> resolver = get_resolver(None)
>>> match = resolver.resolve('/some/path/')
>>> if match.func is func_to_test:
>>> print "correct function for resolution!"
Ryan Wilcox's post on route testing goes into more detail and provides techniques for making it even easier to test them.
Related
This is my first post on Stackoverflow and I'm new to Django, I hope you'll understand.
I want to use Django to provide a portal with authentication, which will have to consume an Keystone/Openstack API, to create/delete Projects, grant/remove rights.
Openstack provides a RestFul API, on which I have to authenticate (I provide credentials, and receive back a token).
I have 2 possibilities to access this API:
Using python client: python-keystoneclient
Using directly the restfulAPI
Nevermind the option 1 or 2, I'm able to login and interact with the API, I do this in the view.
My problem is, each time I change the page/view, I have to authenticate again. I don't know how to use/share the "session or client object" in other views.
>>> from keystoneauth1.identity import v3
>>> from keystoneauth1 import session
>>> from keystoneclient.v3 import client
>>> auth = v3.Password(auth_url='https://my.keystone.com:5000/v3',
... user_id='myuserid',
... password='mypassword',
... project_id='myprojectid')
>>> sess = session.Session(auth=auth)
>>> keystone = client.Client(session=sess, include_metadata=True)
I tried to pass the object as a session variable with request.session and request.session.get, but the object is not serializable. I serialized it, but I can't use it on the other view.
Maybe I shouldn't access the API in the view?
I'm sure I'm not the first in this usecase, regardless of the remote API. But I googled a lot without finding a proper way. Maybe I don't search with the right words
Thanks for your help.
I did it like this and it works well:
in apps.py:
from django.apps import AppConfig
from keystoneauth1.identity import v3
from keystoneauth1 import session
from keystoneclient.v3 import client
class MyAppConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'mySharedVar'
auth = v3.Password(auth_url='https://my.keystone.com:5000/v3', user_id='myuserid', password='mypassword',project_id='myprojectid')
ses1 = session.Session(auth=auth)
in my views, I can now access the "share variable" with:
from keystoneauth1.identity import v3
from keystoneauth1 import session
from keystoneclient.v3 import client
#login_required
def list_project(request):
sharedVar=apps.get_app_config('mySharedVar')
keystone = client.Client(session=sharedVar.ses1, include_metadata=True)
pl = keystone.projects.list()
context = {
"title": "Project List",
"projects": pl.data
}
return render(request, "myapp/list_project.html",context=context)
I hope this can help someone.
So essentially I want to make a request to a ViewSet without requiring to authenticate, does Django APIRequestFactory make this possible? This works:
from django.test import TestCase
from .views.SomeViewSet
from rest_framework.test import APIRequestFactory
class ViewSetsTest(TestCase):
...
...
...
def test_db_instance_viewset(self):
api_request = APIRequestFactory.patch("", HTTP_AUTHORIZATION="Bearer xyz123ppouu")
detail_view = SomeViewSet.as_view({'patch': 'update'})
response = detail_view(api_request)
self.assertEqual(response.status_code, 200)
But the issue is, the bearer token is something that is generated 'somewhere far' every 24 hours. Hence, I want to skip authentication.
Thank you in advance.
You need to use forcing authentication
I use django as a backend. I have big project and there are many views(ViewSets from django-rest-framework, views and functions). And I use React as a front and and how can I get function or class which will be called from the url. For example I have the url:
api/v2/users/322/send_letters/1232/
from this url I want to know which class or function will be called.
I think you're looking for resolve() that can be used for resolving URL paths to the corresponding view functions.
Be careful when using resolve(path) the function raises a Resolver404 if the URL does not resolve (Doesn't exist in your all URLs patterns)
>>> from django.core.urlresolvers import resolve
>>> path = 'api/v2/users/322/send_letters/1232/'
>>> match = resolve(path)
>>> match.url_name
>>> 'url_name'
>>> match.view_name
>>> match.func # func, that you are looking for
match.view_name will return the name of the view that matches the URL, including the namespace if there is one.
I was using the following test to check whether page resolves to correct template:
from django.test import TestCase
class HomePageTest(TestCase):
def test_landing_page_returns_correct_html(self):
response = self.client.get('/')
self.assertIn(member='Go to the', container=response.content.decode())
def test_uses_test_home_template(self):
response = self.client.get('/test/')
self.assertTemplateUsed(response=response,
template_name='myapp/home.html')
I used many variations of self.client.get('/test/') or self.client.get('/test/dashboard/') etc. in many many tests. All of which are in my myapp.urlpatterns.
Then one day I decided to get rid of /test/. Or simply change the URL pattern. All of the tests failed because, well, I hardcoded the URLs.
I would like to use flexible URL's in my tests. I assume it involves something like:
from myapp.urls import urlpatterns as myapp_urls
and using myapp_urls throughout the tests.
I have two questions:
How can I implement it?
Will there be any (negative) side effects of this approach that I don't foresee?
You can use reverse(), for example if you've got something like this in your urls.py:
from news import views
urlpatterns = [
url(r'^archive/$', views.archive, name='news-archive')
]
you can write your test like this:
from django.test import TestCase
from django.urls import reverse
class NewsArchivePageTest(TestCase):
def test_news_archive_page_does_something(self):
response = self.client.get(reverse('news-archive'))
# do some stuff with the response
Read more about reverse() in the documentation. As for negative side effects I don't think there are any.
I just learnt that with Rails is possible to simulate HTTP requests in the console with few lines of code.
Check out: http://37signals.com/svn/posts/3176-three-quick-rails-console-tips (section "Dive into your app").
Is there a similar way to do that with Django? Would be handy.
You can use RequestFactory, which allows
inserting a user into the request
inserting an uploaded file into the request
sending specific parameters to the view
and does not require the additional dependency of using requests.
Note that you have to specify both the URL and the view class, so it takes an extra line of code than using requests.
from django.test import RequestFactory
request_factory = RequestFactory()
my_url = '/my_full/url/here' # Replace with your URL -- or use reverse
my_request = request_factory.get(my_url)
response = MyClasBasedView.as_view()(my_request) # Replace with your view
response.render()
print(response)
To set the user of the request, do something like my_request.user = User.objects.get(id=123) before getting the response.
To send parameters to a class-based view, do something like response = MyClasBasedView.as_view()(my_request, parameter_1, parameter_2)
Extended Example
Here's an example of using RequestFactory with these things in combination
HTTP POST (to url url, functional view view, and a data dictionary post_data)
uploading a single file (path file_path, name file_name, and form field value file_key)
assigning a user to the request (user)
passing on kwargs dictionary from the url (url_kwargs)
SimpleUploadedFile helps format the file in a way that is valid for forms.
from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import RequestFactory
request = RequestFactory().post(url, post_data)
with open(file_path, 'rb') as file_ptr:
request.FILES[file_key] = SimpleUploadedFile(file_name, file_ptr.read())
file_ptr.seek(0) # resets the file pointer after the read
if user:
request.user = user
response = view(request, **url_kwargs)
Using RequestFactory from a Python shell
RequestFactory names your server "testserver" by default, which can cause a problem if you're not using it inside test code. You'll see an error like:
DisallowedHost: Invalid HTTP_HOST header: 'testserver'. You may need to add 'testserver' to ALLOWED_HOSTS.
This workaround from #boatcoder's comment shows how to override the default server name to "localhost":
request_factory = RequestFactory(**{"SERVER_NAME": "localhost", "wsgi.url_scheme":"https"}).
How I simulate requests from the python command line is:
Use the excellent requests library
Use the django reverse function
A simple way of simulating requests is:
>>> from django.urls import reverse
>>> import requests
>>> r = requests.get(reverse('app.views.your_view'))
>>> r.text
(prints output)
>>> r.status_code
200
Update: be sure to launch the django shell (via manage.py shell), not a classic python shell.
Update 2: For Django <1.10, change the first line to
from django.core.urlresolvers import reverse
(See tldr; down)
Its an old question,
but just adding an answer, in case someone maybe interested.
Though this might not be the best(or lets say Django) way of doing things.
but you can try doing this way.
Inside your django shell
>>> import requests
>>> r = requests.get('your_full_url_here')
Explanation:
I omitted the reverse(),
explanation being, since reverse() more or less,
finds the url associated to a views.py function,
you may omit the reverse() if you wish to, and put the whole url instead.
For example, if you have a friends app in your django project,
and you want to see the list_all() (in views.py) function in the friends app,
then you may do this.
TLDR;
>>> import requests
>>> url = 'http://localhost:8000/friends/list_all'
>>> r = requests.get(url)