I have a middleware from secretballot
class SecretBallotMiddleware(object):
def process_request(self, request):
request.secretballot_token = self.generate_token(request)
def generate_token(self, request):
raise NotImplementedError
class SecretBallotIpMiddleware(SecretBallotMiddleware):
def generate_token(self, request):
return request.META['REMOTE_ADDR']
class SecretBallotIpUseragentMiddleware(SecretBallotMiddleware):
def generate_token(self, request):
s = ''.join((request.META['REMOTE_ADDR'], request.META.get('HTTP_USER_AGENT', '')))
return md5(s.encode('utf8')).hexdigest()
and I use this in my view (e.g. 'different_view'):
token = request.secretballot_token
How can I change this token form request in my tests?
class BasicTest(TestCase):
def test_one(self):
self.client.request['secretballot_token']='asd' #??
response = self.client.post('/different_view/')
And I want to send post in this test to /different_view/ but with my own, changed token.
If you're looking to the test the view without running through the middleware, you can use RequestFactory to generate a request and pass it directly into your view.
def test_one(self):
# create a request
request = RequestFactory().post('/different_view')
request.secretballot_token = 'asd'
# function based view
response = different_view(request)
# class based view
response = DifferentView.as_view()(request)
If you need to test the middleware along with the view, you should pass HTTP headers in your tests instead
def test_one(self):
# pass http headers
response = self.client.post(path='/different_view'/,
REMOTE_ADDR='12.34.56.78',
HTTP_USER_AGENT='...'
)
Related
Is it possible to write a class based (not function) custom error page handler?
for example this:
def handler404(request, *args, **argv):
response = render_to_response('404.html', {},
context_instance=RequestContext(request))
response.status_code = 404
return response
but with class
I have Django1.9 middleware class:
class MyMiddleware(object):
def process_request(self, request):
token = self._get_or_create_token(request)
#request.context['token'] = token
The issue is:
- I would like to put token to some sort of context to pass it through the application flow.
- I avoid putting it into request session, because it result in extra database reading/writing.
Could you suggest me some solution?
You can add any attribute to the HttpRequest, so you can implement this with:
class MyMiddleware(object):
def process_request(self, request):
token = self._get_or_create_token(request)
request.token = token
or if you really want some sort of context dictionary:
class MyMiddleware(object):
def process_request(self, request):
token = self._get_or_create_token(request)
if not hasattr(request, 'context'):
request.context = {}
request.context['token'] = token
I have Django1.9 middleware class.
As is documented, django-1.9 is not supported anymore since April 2017, therefore I strongly advice to update.
I have a builtins.dict dictionary that looks like this:
request = {"data": {"var": "hello", "content": "jello"}}
I am writing a test for an API endpoint in django. I am calling the post method of that class and sending this request to it, and I am accessing the data in the request using request.data. But of course that won't work, because the request that goes into my post function is actually a django.http.HttpRequest, not a builtins.dict. How do I make it so that my request argument is a HttpRequest and not a dict so my function can consume it right?
Edit:
Okay, so I have a function that looks like this:
class EmailView(APIView):
"""
Serializes POST data for sending emails.
"""
authentication_classes = [authentication.TokenAuthentication]
permission_classes = [permissions.IsAuthenticated]
def post(self, request):
"""
Sends an email using SES
:param request: POST request
:return: drf http response
"""
serializer = EmailSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
args = serializer.data
send_mail(args.get("subject"), args.get("message"), "info#mycomp.io", args.get("to"), fail_silently=False)
return Response('Email sent.', status=status.HTTP_200_OK)
I want to test this function. So, I've written something that looks like this:
class EmailTests(APITestCase):
def setup(self):
self.user = UserProfile.objects.create_user(
'testuser', email='testuser#test.com', password='testpass')
self.user.save()
def init_request(self):
request = {"data": {"sender": "info#mycomp.io", "to": ["test#gmail.com", "testt#gmail.com"],
"subject": "Subject", "message": "Hello"}}
return request
def test_incorrect_email(self):
request = self.init_request()
reponse = EmailView.post(EmailView, request)
print(reponse)
But, of course, this isn't working because the request i'm sending through my to my post in the test isn't an actual Request from rest_framework.request. Guess I'm saying I have no idea what the right way to write a test for this is... any ideas?
Edit:
My new tests. The EmailView class has not changed.
class EmailTests(APITestCase):
def setup(self):
self.user = UserProfile.objects.create_user(
'testuser', email='testuser#test.com', password='testpass')
self.user.save()
def init_request(self):
request = {"sender": "info#mycomp.io", "to": ["test#gmail.com", "testt#gmail.com"],
"subject": "Subject", "message": "Hello"}
return request
def test_incorrect_email(self):
request = self.init_request()
factory = APIRequestFactory()
request = factory.post('/v1/send_email/', request)
view = EmailView()
reponse = view.post(request)
print(reponse)
Use the Django REST Framework RequestFactory, eg:
from rest_framework.test import APIRequestFactory
view = EmailView.as_view()
factory = APIRequestFactory()
request = factory.post('/url/to/the/endpoint', {"var": "hello", "content": "jello"})
reponse = view(request)
I have a generic class based view:
class ProjectDetails(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
generics.GenericAPIView):
queryset = Project.objects.all()
# Rest of definition
And in my urls.py, I have:
urlpatterns = [
url(r'^(?P<pk>[0-9]+)/$', views.ProjectDetails.as_view())
]
When the API is called with a non-existent id, it returns HTTP 404 response with the content:
{
"detail": "Not found."
}
Is it possible to modify this response?
I need to customize error message for this view only.
This solution affect all views:
Surely you can supply your custom exception handler: Custom exception handling
from rest_framework.views import exception_handler
from rest_framework import status
def custom_exception_handler(exc, context):
# Call REST framework's default exception handler first,
# to get the standard error response.
response = exception_handler(exc, context)
# Now add the HTTP status code to the response.
if response.status_code == status.HTTP_404_NOT_FOUND:
response.data['custom_field'] = 'some_custom_value'
return response
Sure you can skip default rest_framework.views.exception_handler and make it completely raw.
Note: remember to mention your handler in django.conf.settings.REST_FRAMEWORK['EXCEPTION_HANDLER']
Solution for specific view:
from rest_framework.response import Response
# rest of the imports
class ProjectDetails(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
generics.GenericAPIView):
queryset = Project.objects.all()
def handle_exception(self, exc):
if isinstance(exc, Http404):
return Response({'data': 'your custom response'},
status=status.HTTP_404_NOT_FOUND)
return super(ProjectDetails, self).handle_exception(exc)
It's possible by overriding specific methods like update, retrieve as:
from django.http import Http404
from rest_framework.response import Response
class ProjectDetails(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
generics.GenericAPIView):
queryset = Project.objects.all()
def retrieve(self, request, *args, **kwargs):
try:
return super().retrieve(request, *args, **kwargs)
except Http404:
return Response(data={"cusom": "message"})
def update(self, request, *args, **kwargs):
try:
return super().update(request, *args, **kwargs)
except Http404:
return Response(data={"cusom": "message"})
The query to my endpoint works fine (as long as I pass it a valid token), it returns the json representation of my response data.
The code in the service api that calls my endpoint, passing an auth token in the header:
headers = {'content-type': 'application/json',
'Authorization': 'Token {}'.format(myToken)}
url = 'http://localhost:8000/my_endpoint/'
r = session.get(url=url, params=params, headers=headers)
In views.py, I have a method decorator that wraps the dispatch method on the view (viewsets.ReadOnlyModelViewSet):
def login_required(f):
def check_login_and_call(request, *args, **kwargs):
authentication = request.META.get('HTTP_AUTHORIZATION', b'')
if isinstance(authentication, str):
authentication = authentication.encode(HTTP_HEADER_ENCODING)
key = authentication.split()
if not key or len(key) != 2:
raise PermissionDenied('Authentication failed.')
user, token = authenticate_credentials(key[1])
return f(request, *args, **kwargs)
return check_login_and_call
I'm trying to write a test to authenticate the request using a token:
from rest_framework.authtoken.models import Token
from rest_framework.test import APIRequestFactory
from rest_framework.test import APITestCase
from rest_framework.test import force_authenticate
class EndpointViewTest(APITestCase):
def setUp(self):
self.factory = APIRequestFactory()
self.user = User.objects.create_user(
username='user#foo.com', email='user#foo.com', password='top_secret')
self.token = Token.objects.create(user=self.user)
self.token.save()
def test_token_auth(self):
request = self.factory.get('/my_endpoint')
force_authenticate(request, token=self.token.key)
view = views.EndpointViewSet.as_view({'get': 'list'})
response = view(request)
self.assertEqual(response.status_code, 200)
json_response = json.loads(response.render().content)['results']
For some reason, I cannot get the request to properly pass the token for this test. Using force_authenticate doesn't seem to change the header that I'm using for validating the token. The current output is raising "PermissionDenied: Authentication failed." because the token isn't being set on the request.
Is there a proper way to set this in the request header in my test or to refactor the way I'm using it in the first place?
I found a way to get the test to pass, but please post if you have a better idea of how to handle any of this.
request = self.factory.get('/my_endpoint', HTTP_AUTHORIZATION='Token {}'.format(self.token))
force_authenticate(request, user=self.user)
After changing the above two lines of the test, it seems to authenticate based on the token properly.
I wanted to test the authentication function itself, so forcing authentication wans't an option.
One way to properly pass the token is to use APIClient, which you already have imported.
client = APIClient()
client.credentials(HTTP_AUTHORIZATION='Token ' + self.token.key)
response = client.get('/api/vehicles/')
That sets your given token into the request header and lets the back end decide if it's valid or not.
Sorry for digging this old thread up, but if someone is using APIClient() to do their tests you can do the following:
from rest_framework.test import APITestCase
from rest_framework.test import APIClient
from rest_framework.authtoken.models import Token
from django.contrib.auth.models import User
class VehicleCreationTests(APITestCase):
def setUp(self):
self.client = APIClient()
self.user = User.objects.create_superuser('admin', 'admin#admin.com', 'admin123')
self.token = Token.objects.create(user=self.user)
def testcase(self):
self.client.force_login(user=self.user)
response = self.client.post('/api/vehicles/', data=vehicle_data, format='json', HTTP_AUTHORIZATION=self.token)
self.assertEqual(response.status_code, 201)
Really good resource that I've used to come up with this is django-rest-framework-jwt tests
The simpler way to force_authentication using a built-in method from APITestCase is:
class Test(APITestCase):
def setUp(self):
user1 = User.objects.create_user(username='foo')
self.client.force_authenticate(user=user1) # self.client is from APITestCase
... the rest of your tests ...