How do I transform a python dictionary to an HttpRequest appropriately? - python

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)

Related

Django RequestFactory doesn't store a User

I'm testing whether an API User can POST to a view if the User can pass the IsAuthenticated permission. Upon running the test I get the assertion error: AssertionError: 401 != 201.
When permission_classes = (permissions.IsAuthenticated,) is commented out and the test is ran again, I'm finding that request.user is turning out to be an AnonymousUser. Yet, I have a User created and attached to the request as shown below.
I'm not sure what is causing this, and looking to understand how I can pass a User instance to the request.
Note - I'm trying to do this in the Django testing API rather than use Django REST Framework.
tests.py
class TestUserPreferencesResource(TestCase):
'''Verify that a User is capable of setting preferences for their profile'''
#classmethod
def setUpTestData(cls):
cls.user = User.objects.create_user('Mock', password="secret")
cls.user_prefs = json.dumps({
"age": ['Baby', 'Adult'],
"gender": ['Male'],
"size": ["Medium", "Large"]
})
cls.factory = RequestFactory()
cls.credentials = b64encode(b"Mock:secret").decode("ascii")
def test_user_preferences_settings(self):
request = self.factory.post(
reverse("pref-settings"),
data=self.user_prefs,
content_type="application/json",
headers = {
"Authorization": f"Basic {self.credentials}"
},
)
request.user = self.user
print(request.user)
response = UserPrefView.as_view()(request)
self.assertEqual(response.status_code, 201)
views.py
class UserPrefView(
CreateModelMixin,
UpdateModelMixin,
GenericAPIView):
queryset = UserPref.objects.all()
permission_classes = (permissions.IsAuthenticated,)
serilaizer_class = serializers.UserPreferenceSerializer
def post(self, request, format=None):
import pdb; pdb.set_trace()
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save(user=request.user)
return Response(serializer.data)

Django Rest API Djoser Refresh and Access Token Problem

I am trying to get access and refresh tokens when the user registers using the /auth/users/ endpoint.
I have already extended the serializer and it is showing all custom fields. It registers the user successfully and returns the result as follows:
{
"mobile": "12345",
"driving_id": "478161839",
"full_name": "John Doe",
}
This is where I would want to have an access and refresh token. I read that djoser uses django simple jwt library to provide access and refresh tokens. This is the function to create the tokens manually which I am able to do but the only thing I am not understanding is where to return the data with other details.
from rest_framework_simplejwt.tokens import RefreshToken
def get_tokens_for_user(user):
refresh = RefreshToken.for_user(user)
return {
'refresh': str(refresh),
'access': str(refresh.access_token),
}
I solved it but I do not know if it is the best way or not. I imported the userview set from djoser.views and then overrode the create method. I fetch the user and then create JWT tokens for it, add it to the response dict and return it.
from rest_framework import status
from djoser.views import UserViewSet
from djoser import signals
from djoser.compat import get_user_email
from rest_framework_simplejwt.tokens import RefreshToken
class CustomRegistrationView(UserViewSet):
def perform_create(self, serializer):
user = serializer.save()
signals.user_registered.send(
sender=self.__class__, user=user, request=self.request
)
context = {"user": user}
to = [get_user_email(user)]
if settings.SEND_ACTIVATION_EMAIL:
settings.EMAIL.activation(self.request, context).send(to)
elif settings.SEND_CONFIRMATION_EMAIL:
settings.EMAIL.confirmation(self.request, context).send(to)
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
response_data = serializer.data
user = User.objects.get(username = response_data['username'])
refresh = RefreshToken.for_user(user)
response_data['refresh'] = str(refresh)
response_data['access'] = str(refresh.access_token)
return Response(response_data, status=status.HTTP_201_CREATED, headers=headers)

access data from request body django

I am using django v2.2.4 and need to access request body data.
Here's my code:
#api_view(['POST'])
#renderer_classes((JSONRenderer,))
def index(request):
if request.method == 'POST':
results= []
data = JSONParser().parse(request)
serializer = ScrapeSerializer(data=data)
if serializer.is_valid():
url = request.data.url
#url = request.POST.get('url')
But I get this error:
RawPostDataException at /scrape/
You cannot access body after reading from request's data stream
Here's the request body:
{
"url": "xyz.com"
}
How can I access the request body?
I have found this SO post that related to this issue, Exception: You cannot access body after reading from request's data stream
Anyway use request.data instead of request.body in DRF
#api_view(['POST'])
#renderer_classes((JSONRenderer,))
def index(request):
if request.method == 'POST':
results = []
serializer = ScrapeSerializer(data=request.data)
if serializer.is_valid():
url = request.data["url"]
The request.data returns the parsed content of the request body and it will be dict like object, hence the dot operation (request.data.url) won't works here.
To access the request body of a POST request, you could do this by url = request.POST.get("url")

Django testing, change request

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='...'
)

What's the proper way to test token-based auth using APIRequestFactory?

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 ...

Categories

Resources