Unit testing in Django with flexible url patterns - django

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.

Related

Query parameters in request on Django 3.0.3

I'm putting together an API and need to add query parameters to the URI like https://www.example.com/api/endpoint?search=term&limit=10.
My first question is, in Django 3.0, I'd need to use re-path to accomplish parsing out the various parameters, correct?
Secondly, the question is about convention. It seems like two of the three APIs I've been working with a lot lately us a convention like:
/api/endpoint?paramater1=abc&parameter2=xyz
Another uses something like:
/api/endpoint?$parameter1=abc&parameter2=abc
Looking at some past Django question related to this topic, I see stuff like:
/api/endpoint/?parameter1=abc&parameter2=xyz
Another post I read was suggesting that parameters should be separated with ;.
I guess I'm just curious what the "correct" convention should be either in terms of Django or general concensus.
Lastly, it seems to me what I'm trying to accomplish should be a GET request. The front-end sends the user defined parameters (section and startingPage) to the back-end where a PDF is generated matching those parameters. When it is generated, it sends it back to the FE. The PDFs are much too large to generate client-side. GET would be the correct method in the case, correct?
Well, I elected to go with the first convetion:
/api/endpoint?paramater1=abc&parameter2=xyz
Simply because the majority of APIs I work with use this convention.
For my files:
# urls.py
from django.urls import path
from .views import GeneratePDFView
app_name = 'Results'
urlpatterns = [
path('/endpoint',
GeneratePDFView.as_view(), name='generate_pdf')
]
# view.py
from django.conf import settings
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.status import HTTP_200_OK, HTTP_400_BAD_REQUEST
from rest_framework.views import APIView
class GeneratePDFView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request, *args, **kwargs):
if len(request.query_params) == 2:
data['id'] = {'id': request.user.id}
data['starting_page'] = request.query_params.get('parameter1')
data['data_view'] = request.query_params.get('parameter2')
serializer = GeneratePDFSerializer(data=data)
...

Django Rest Framework Custom Endpoints

I have recently inherited an API built with Django and DRF. I need to add some endpoints to the API but have never worked with Django or DRF before so I am trying to come up to speed as quickly as possible.
I am wondering how to do custom endpoints that don't just translate data too/from the backend database. A for instance might be an endpoint that reads data from the DB then compiles a report and returns it to the caller in JSON. But I suppose that right now the simplest method would be one that when the endpoint is hit just prints 'Hello World' to the log and returns a blank page.
I apologize if this seems basic. I've been reading through the docs and so far all I can see is stuff about serializers when what I really need is to be able to call a custom block of code.
Thanks.
if you want your REST endpoint to have all: GET, POST, PUT, DELETE etc. functionality then you have to register a route in your urls.py:
urls.py:
from rest_framework import routers
from django.urls import path, include
from . import views
router = routers.DefaultRouter()
router.register(r'hello', views.HelloWorldViewSet)
urlpatterns = [
# Wire up our API using automatic URL routing.
# rest_framework api routing
path('api/', include(router.urls)),
# This requires login for put/update while allowing get (read-only) for everyone.
path('api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]
now the url: /hello/ points to the HelloWorldViewSet.
in your views.py add the HelloWorldViewSet that will inherits from the rest_framework.viewsets.ViewSet class. You can override the ViewSet default class behavior by defining the following "actions": list(), create(), retrieve(), update(), partial_update(), destroy(). For displaying "hello world" on GET you only need to override list():
so in your views.py:
from rest_framework import viewsets
from rest_framework.response import Response
class HelloWorldViewSet(viewsets.ViewSet):
def list(self, response):
return Response('Hello World')
So, in your more advanced list() function you have to interact with the database, to retrieve the data you want, process it and create the report as a json serializable dictionary and return it as a Response object.
If you don't want to override the standard list action, you could instead add a new action to the HelloWorldViewSet let's call it report:
so in your views.py:
from rest_framework import viewsets
from rest_framework.response import Response
from rest_framework.decorators import action
class HelloWorldViewSet(viewsets.ViewSet):
#action(detail=False)
def report(self, request, **kwargs):
return Response('Hello World')
I hope this is what you were looking for.
Note that you don't need django-rest-framework if you are not interested in POST, PUT, PATCH, DELETE, etc... you can simply add a path to your urls.py that points to a Django view function that returns a Django JsonResponse object containing your report.
One good option for you would be DRF actions. Docs here
The action allows you to choose a relevant view for what you wanna do, so you can just pop things into the API you inherited. No additional setup needed, they show up next to the regular routes.

Django: Avoid HTTP API calls while testing from django views

I'm writing the tests for django views, Some of the views are making the external HTTP requests. While running the tests i dont want to execute these HTTP requests. Since during tests , data is being used is dummy and these HTTP requests will not behave as expected.
What could be the possible options for this ?
You could override settings in your tests and then check for that setting in your view. Here are the docs to override settings.
from django.conf import settings
if not settings.TEST_API:
# api call here
Then your test would look something like this
from django.test import TestCase, override_settings
class LoginTestCase(TestCase):
#override_settings(TEST_API=True)
def test_api_func(self):
# Do test here
Since it would be fairly messy to have those all over the place I would recommend creating a mixin that would look something like this.
class SensitiveAPIMixin(object):
def api_request(self, url, *args, **kwargs):
from django.conf import settings
if not settings.TEST_API:
request = api_call(url)
# Do api request in here
return request
Then, through the power of multiple inheritence, your views that you need to make a request to this api call you could do something similar to this.
class View(generic.ListView, SensitiveAPIMixin):
def get(self, request, *args, **kwargs):
data = self.api_request('http://example.com/api1')
This is where mocking comes in. In your tests, you can use libraries to patch the parts of the code you are testing to return the results you expect for the test, bypassing what that code actually does.
You can read a good blog post about mocking in Python here.
If you are on Python 3.3 or later, the mock library is included in Python. If not, you can download it from PyPI.
The exact details of how to mock the calls you're making will depend on what exactly your view code looks like.
Ben is right on, but here's some psuedo-ish code that might help. The patch here assumes you're using requests, but change the path as necessary to mock out what you need.
from unittest import mock
from django.test import TestCase
from django.core.urlresolvers import reverse
class MyTestCase(TestCase):
#mock.patch('requests.post') # this is all you need to stop the API call
def test_my_view_that_posts_to_an_api(self, mock_get):
response = self.client.get(reverse('my-view-name'))
self.assertEqual('my-value', response.data['my-key'])
# other assertions as necessary

In which order to sort my middleware path match requirements?

I created a middleware which allows me to use a list of dictionaries to specify some access rules for any of my views. Each of these dictionaries looks like this:
REQUIREMENTS=(
{'viewname':'addtag',
'permissions':'can_add_tags'},
{'regex':re.compile(r'^somestart'),
'user_check':lambda request:request.user.username=='sam'}
)
In my middleware i then try to find out which of those requirements match the current request. To do so, i filter the complete REQUIREMENTS, and in the filter function i use this code to check if the path matches:
def process_request(self,request):
def path_matches(self,req):
path_matches = False
if (req.has_key('url') and req['url'] == request.path ) or\
(req.has_key('regex') and req['regex'].search(request.path)) or\
(req.has_key('viewname') and resolve(request.path).url_name==req['viewname']):
path_matches=True
return path_matches
requirements = filter(path_matches,REQUIREMENTS)
# now use the returned requirements to determine if a user
# matches the requirement and
My question now is: in which order should i use the checks? It's pretty clear that the check for the url is the fastest, so this has to be first. But then the question is if the regex search or django's url resolve function should follow first.
As i do not have any performance issues right now, this is more of an academic question. And if someone would have an all better solution to solve this, that would be even better.
edit:
To react to the given answers: what i'm trying to do is to create a possibility to restrict views of several external apps in a single file. So decorators are not an option, as long as i do not want to do something like this:
from ext_app1 import view1,view2
from ext_app2 import view3
#permission_required('can_do_stuff')
def view1_ext(*args,**kwargs):
return view1(args,kwargs)
which would lead to rewriting the url specifications each time i change permissions. I want to avoid that. Additionally my solution allows for a user_check function to do a check on a user like this:
def check_user(user):
if len(Item.objects.get(creator=user,datetime=today)) > 3:
return False
return True
That would be a simple way to, ie., restrict how many items a user can upload each day. (Ok, that would be possible with user_passes_test as well).
One more thing is that i sometimes want to check for a permission only if the request is a POST, or if the request contains a certain key:value pair (like 'action':'delete' should require the permission, while 'action':'change' should be allowed for anyone). This could be done with a custom decorator as well, but as soon as i would need a new check, i would need a new decorator.
If you're using Django's built in user authentication and permissions system (django.contrib.auth) then you should consider using the views decorators it provides instead of a middleware. These give you several advantages:
Code that changes together is in the same place, i.e. it's more likely that you'll need to change permissions for a specific view when you are changing the view's code than when you are changing other permissions.
You don't have to write much of the code yourself, which makes your project smaller and easier to maintain.
For simple situations you can use the login_required and permission_required decorators, and for a more complex condition the user_passes_test decorator allows you to check if the user passes any condition you care to specify.
The code looks something like this for a view function (example is lifted from the documentation):
from django.contrib.auth.decorators import permission_required
#permission_required('polls.can_vote')
def my_view(request):
...
If you're using class-based views then it looks a little different (again, this example is lifted from the documentation):
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.views.generic import TemplateView
class ProtectedView(TemplateView):
template_name = 'secret.html'
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(ProtectedView, self).dispatch(*args, **kwargs)
If you have a good reason not to use Django's permissions system, then you can still adopt a similar approach. The code for the django.contrib.auth decorators could easily be used as the basis of your own decorators.
You might be looking for user_passes_test decorator.
You can decorate the views you need instead of using a middleware.
The code will looks like this:
from django.contrib.auth.decorators import user_passes_test
#user_passes_test(lambda user: user.has_perm('model.can_add_tags') \
and user.username == 'sam')
def my_view(request):
...

Django UrlResolver, adding urls at runtime for testing

I'm looking to do some tests and I'm not really familiar with the URLResolver quite yet but I'd like to solve this issue quickly.
In a TestCase, I'd like to add a URL to the resolver so that I can then use Client.get('/url/') and keep it separate from urls.py.
Since Django 1.8 using of django.test.TestCase.urls is deprecated. You can use django.test.utils.override_settings instead:
from django.test import TestCase
from django.test.utils import override_settings
urlpatterns = [
# custom urlconf
]
#override_settings(ROOT_URLCONF=__name__)
class MyTestCase(TestCase):
pass
override_settings can be applied either to a whole class or to a particular method.
https://docs.djangoproject.com/en/2.1/topics/testing/tools/#urlconf-configuration
In your test:
class TestMyViews(TestCase):
urls = 'myapp.test_urls'
This will use myapp/test_urls.py as the ROOT_URLCONF.
I know this was asked a while ago, but I thought I'd answer it again to offer something more complete and up-to-date.
You have two options to solve this, one is to provide your own urls file, as suggested by SystemParadox's answer:
class MyTestCase(TestCase):
urls = 'my_app.test_urls'
The other is to monkey patch your urls. This is NOT the recommended way to deal with overriding urls but you might get into a situation where you still need it. To do this for a single test case without affecting the rest you should do it in your setUp() method and then cleanup in your tearDown() method.
import my_app.urls
from django.conf.urls import patterns
class MyTestCase(TestCase):
urls = 'my_app.urls'
def setUp(self):
super(MyTestCase, self).setUp()
self.original_urls = my_app.urls.urlpatterns
my_app.urls.urlpatterns += patterns(
'',
(r'^my/test/url/pattern$', my_view),
)
def tearDown(self):
super(MyTestCase, self).tearDown()
my_app.urls.urlpatterns = self.original_urls
Please note that this will not work if you omit the urls class attribute. This is because the urls will otherwise be cached and your monkey patching will not take effect if you run your test together with other test cases.
Couldn't get it running with the answers above. Not even with the override_settings.
Found a solution which works for me. My usecase was to write some integration tests where I want to test put/post methods where I needed the urls from my app.
The main clue here is to use the set_urlconf function of django.urls instead of overwriting it in the class or using override_settings.
from django.test import TestCase
from django.urls import reverse, set_urlconf
class MyTests(TestCase):
#classmethod
def setUpClass(cls):
super().setUpClass()
set_urlconf('yourapp.urls') # yourapp is the folder where you define your root urlconf.
def test_url_resolving_with_app_urlconf(self):
response = self.client.put(
path=reverse('namespace:to:your:view-name'), data=test_data
)