I'm pretty new to this, so I will show you the error first and then display my thought process.
I am attempting to use a thunk (Http request in redux) to make a post request to my personal rest api in django. (so it can run cURL commands and use a authorization code to gain an access token and get user data from an API)
when I make this request, the network returns a 201 status to stay it worked, while my console logs the dispatch as rejected, and gives the error, "message: "Unexpected end of JSON input". In this message, payload is undefined, and my payload is living in meta under arg.
After much reserach, it looks like I either need to JSON.strigify something on my front end, OR that django is receiving a null object and not knowing what to do with it. I believe it is the latter, and not sure how to proceed. Here is my code:
FRONTEND:
slice.py
import {createAsyncThunk} from '#reduxjs/toolkit'
export const postRequest= createAsyncThunk('slice/postRequest',
async postData => {
const response=
await client.post('url', {slice:postData})
return response.slice
}
)
component.py
import {useDispatch} from 'react-redux'
import {postRequest} from './slice'
const Component = () => {
const dispatch=useDispatch()
const authCode= 'samplecode'
const data={authCode:`${authCode}`, userData:{}}
dispatch(postRequest(JSON.stringify(data))
return(null)
}
export default Component
BACKEND:
models.py
from django.db import models
class UserData(models.Model):
authCode=models.CharField(max_length=100, default='')
userData= models.JSONField
serializers.py
from rest_framework import serializers
from .models import *
class UserDataSerializer(serializers.ModelSerializer):
class meta:
model=UserData
fields= ('authCode', 'userData')
views.py
from .models import *
from .serializers import *
from django.views.decorators.csrf import csrf_excempt
from rest_framework.decorators import api.view
from rest_framework.resposne import Response
from rest_framework import status
#api_view(['POST'])
#csrf_excempt
define restapi(request)
if request.method == "POST":
serializer = UserDataSerializer(data = request.data
if serializer.is_valid():
serializer.save
return Response(status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_Request
I also tried it where it would return a status of 204, and without JSON.stringify.
I noticed that when I made a successful post request to a json server, it would make the request with the payload in args, and if it succeeded, the payload would get moved to payload, but it is my guess that my current error is happening because the payload is blank at the start of the thunk dispatch. Really I am not sure though
I solved this problem, it was stupid.
I was using a homemade client and I am not experienced enough to do that. I just got axios and used that as the client and it worked fine. I also simplified the request in the component to not have JSON.stringify and not have the backticks.
Related
Recently I have been trying to add an api using flask-restx. I am having some toruble even getting a helloworld working.
I have everything in blueprints so it only make sense to place the api in its own blueprint also. Below is my api_routes.py
from flask_login import current_user, login_user, logout_user, login_required
from flask_restx import Resource, Api
from flask import Blueprint
import flask
from . import db
api_bp = Blueprint('api_bp', __name__, url_prefix='/api')
api = Api(api_bp, version='1.0', title='Scribe API',
description='An API for interfacing with Scribe',
)
#api_bp.route("/hello")
class HelloWorld(Resource):
def get(self):
return "Hello"
api.add_resource(HelloWorld, '/hello')
However if you go to the swagger ui and send a get like so:
curl -X GET "http://127.0.0.1:5000/api/hello" -H "accept: application/json"
The response is 500:
TypeError: The view function did not return a valid response. The return type must be a string, dict, tuple, Response instance, or WSGI callable, but it was a HelloWorld.
So for some odd reason I am returning the class and not the actual return string of "Hello". Does anyone have an idea of why this is and how to fix this?
I decided to move on shortly after posting this, it turns out namespaces are mandatory, you can fix my above code with the code below to create a namespace. This will get the basic helloworld working.
hello_ns = api.namespace('', description='Hello World Operations')
#hello_ns.route("/hello")
class HelloWorld(Resource):
def get(self):
return "Hello"
i want to send body data using get request in the django drf test case APITestCase
for example
data ={'hi':'bye'}
self.client.get('media_list/', {'body': data})
and in the views i can able get the body using below code
request.data.get('hi', None)
but it is not working using {'body': data} my test method but it is working fine in the postman raw type.
what is tried is(not working)
self.client.get('media_list/', data=data)
I faced this issue while writing unit test. You can solve it with this workaround.
Problem: When you request with client.get method, the method put your data to url. You can see link in the below.
https://github.com/encode/django-rest-framework/blob/master/rest_framework/test.py#L194
Solution: You should use client.generic() directly. Don't use client.get() https://github.com/encode/django-rest-framework/blob/master/rest_framework/test.py#L203
Example
import json
from rest_framework.test import APIClient
from rest_framework.test import APITestCase
class TestClass(APITestCase):
def setUp(self):
# Setup run before every test method.
pass
def test_send_json_in_body(self):
data ={'hi' : 'bye'}
resp = self.client.generic(method="GET", path="/return/data/", data=json.dumps(data), content_type='application/json')
request._request
<WSGIRequest: GET '/return/data/'>
request.data
{'hi': 'bye'}
It works :)
data` returns request body and for get api's you need to get query params
Try bellow code:
data = {'hi':'bye'}
self.client.get('media_list/', data)
request.query_params.get('hi', None)
How to write custom authentication of user, that connects to chat over ws:// protocol? This user is on the other side of Django app, he is the mobile user, connecting websocket via ws:// from mobile app. I tried to test websocket with chrome extenshion, it couldn't connect to my websocket. I think it was because of authentication.
In Django channels docs it says:
If you have a custom authentication scheme, you can write a custom middleware to parse the details and put a user object (or whatever other object you need) into your scope.Middleware is written as a callable that takes an ASGI application and wraps it to return another ASGI application. Most authentication can just be done on the scope, so all you need to do is override the initial constructor that takes a scope, rather than the event-running coroutine.
Here’s a simple example of a middleware that just takes a user ID out of the query string and uses that:
The same principles can be applied to authenticate over non-HTTP protocols; for example, you might want to use someone’s chat username from a chat protocol to turn it into a user.
from django.db import close_old_connections
class QueryAuthMiddleware:
def __init__(self, inner):
# Store the ASGI application we were passed
self.inner = inner
def __call__(self, scope):
# Look up user from query string (you should also do things like
# check it's a valid user ID, or if scope["user"] is already populated)
user = User.objects.get(id=int(scope["query_string"]))
close_old_connections()
# Return the inner application directly and let it run everything else
return self.inner(dict(scope, user=user))
What query do I need to do? I don't know anything about that user, in fact its anonymous.
Help me, please.
In this example code, you probably have to open the websocket connection with:
ws://SERVER:PORT/PATH?1
Everything after the ? is the query string. In your example code, your query_string has to be a user id, so for example 1.
You could change the code to use different query strings. For example you could use:
from urllib.parse import parse_qs
from django.db import close_old_connections
class QueryAuthMiddleware:
def __init__(self, inner):
# Store the ASGI application we were passed
self.inner = inner
def __call__(self, scope):
# Look up user from query string (you should also do things like
# check it's a valid user ID, or if scope["user"] is already populated)
query_string = parse_qs(self.scope['query_string'])
if b'user_id' in query_string:
user = User.objects.get(id=int(query_string[b'user_id'][0]))
close_old_connections()
else:
user = AnonymousUser
# Return the inner application directly and let it run everything else
return self.inner(dict(scope, user=user))
Now, you can use this uri:
ws://SERVER:PORT/PATH?user_id=1
You sill have to make sure, that the user with the ID exists in the database. You also have to write actual auth code. Every user can connect to this application with a arbitrary user id. There is no password or auth-token required.
I had the same issue as well, did research a bit came across solution with the code snippet below:
let's say you have User class defined. You want authenticate user through query send query while establishing connection with ws.
I will pass channels installation and configuration let's say you have successfully installed channels and configured.
QueryAuthMiddleware class as shown below:
from channels.auth import AuthMiddlewareStack
from django.conf import LazySettings
from urllib import parse
from rest_socket.models import User
from urllib.parse import parse_qs
from urllib.parse import unquote, urlparse
from channels.auth import AuthMiddlewareStack
from django.contrib.auth.models import AnonymousUser
class QueryAuthMiddleware:
"""
QueryAuthMiddleware authorization
"""
def __init__(self, inner):
self.inner = inner
def __call__(self, scope):
query_string = parse_qs(scope['query_string']) #Used for query string token url auth
headers = dict(scope['headers']) #Used for headers token url auth
print("query", query_string)
if b'user' in query_string:
try:
user = query_string[b'user'][0].decode()
print("user", user)
existing = User.objects.filter(id=user).last()
print(existing)
if existing:
print("existinguser")
scope['user'] = existing
else:
scope["user"] ="no user found"
except User.DoesNotExist:
pass
return self.inner(scope)
QueryAuthMiddlewareStack = lambda inner: QueryAuthMiddleware(AuthMiddlewareStack(inner))
Your routing.py should be like this:
from channels.security.websocket import AllowedHostsOriginValidator
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.security.websocket import OriginValidator
from django.urls import path
import your_app.routing
from project.utils.auth import QueryAuthMiddlewareStack
application = ProtocolTypeRouter({
# (http->django views is added by default)
'websocket': QueryAuthMiddlewareStack(
URLRouter(
your_app.routing.websocket_urlpatterns
)
),
})
Routing inside application:
websocket_urlpatterns = [
path("tasks/", consumers.Task)
]
and your ws connection to django-channels:
<script>
var notification;
if (location.protocol === 'https:') {
notification = new WebSocket('wss://' + "window.location.host" + "/tasks"+ "/?user=id");
console.log("with htpps")
}
else {
notification = new WebSocket('ws://' + window.location.host + "/tasks"+ "/?userr=id");
console.log("htpp")
}
notification.onopen = function open() {
console.log('notification connection created for NotificationWebsocket.');
};
notification.onmessage = function message(event) {
var data = JSON.parse(event.data);
console.log("Socket response from NotificationWebsocket => ", data);
};
if (notification.readyState === WebSocket.OPEN) {
notification.onopen();
}
</script>
I want to allow the django users to use a key in the api urls for authentication.
I do not have OAUTH set up yet so I guess the key could be a sesion key or a digest key.
I'm having 2 problems.
I've tried sending this request:
http://192.166.166.11:8000/api?task=search&term=115&csrf_token=s69WAIZqlCTur1XZQr72QhCc7fzqaRtM
First of all, I've tried using the csrf_token but it does not work.
It takes me to the login page.
Secondly, I do not know how to retrieve csrf_token of other users (the admin is trying to get their csrf_tokens).
My attempt:
x = User.objects.get(username='someone')
x.get_session_auth_hash()
gives me the user's authentication hash but it is a different value.
Can someone please guide me get these two problems sorted out?
You are creating a token-based authentication. You already mentioned OAUTH as one option, and I strongly recommend using one of the existing implementations like django-oauth-toolkit. However, you can also create your own quick solution to create a token-based authentication.
Disclaimer: This is for demo purposes only. Do not copy it in any existing project. It will make your application vulnerable.
First, we create an additional model handling the authentication tokens:
/auth_tokens/models.py
from django.db import models
from django.conf import settings
import string, random
def random_string(length = 64, chars=string.ascii_uppercase + string.ascii_lowercase + string.digits):
return ''.join(random.choice(chars) for x in range(length))
class AuthToken(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
token = models.CharField(max_length=64, default=random_string)
/auth_tokens/middleware.py
from auth_tokens.models import AuthToken
class AuthTokenMiddleware:
def process_request(self, request):
token = request.GET.get('auth', None)
if not token:
return
token = AuthToken.objects.get(token=token)
request.user = token.user
return request
Including the middleware into your settings.MIDDLEWARE_CLASSES should enable you to add ?token=<token> to your URL to login your users.
I ended up using token authentication:
http://www.django-rest-framework.org/api-guide/authentication/
so I'd like to share the workflow.
First, you need to do the set up. In settings.py, modify INSTALLED_APPS and add REST_FRAMEWORK as in documentation.
Then you need to run python manage.py syncdb because it needs to add some tables.
Then, you need to add some urls to urls.py to route the api.
You can create and retrieve tokens using this code:
from rest_framework.authtoken.models import Token
token = Token.objects.create(user=User.objects.get(username='john'))
print token.key
Lastly, you'll have to modify your view which depends on whether you're using a function based or class based view.
Here is a function based view I used:
from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.decorators import authentication_classes, permission_classes
from rest_framework.decorators import api_view
#api_view(['GET', 'POST'])
#authentication_classes((TokenAuthentication,))
#permission_classes((IsAuthenticated,))
#login_required
def mybooks(request):
entries = Book.objects.all()
return render(request, 'mybooks.html', {'entries': entries})
Lastly, to test it out:
import requests
token = '243124c52f7583e320d043c4395bd99f63344035'
headers = {'Authorization' : 'Token {}'.format(token)}
page = requests.post('http://127.0.0.1:8000/mybooks/', headers=headers)
print page.content
Note that in my case I do not need define plain serialization since I have an advanced custom serialization and that is not the topic here.
Django doesn't provide API Keys out of the box.
Use API providers such as Tastypie to have this feature
I have been writing tests for one of my django applications and have been looking to get around this problem for quite some time now. I have a view that sends messages using django.contrib.messages for different cases. The view looks something like the following.
from django.contrib import messages
from django.shortcuts import redirect
import custom_messages
def some_view(request):
""" This is a sample view for testing purposes.
"""
some_condition = models.SomeModel.objects.get_or_none(
condition=some_condition)
if some_condition:
messages.success(request, custom_message.SUCCESS)
else:
messages.error(request, custom_message.ERROR)
redirect(some_other_view)
Now, while testing this view client.get's response does not contain the context dictionary that contains the messages as this view uses a redirect. For views that render templates we can get access to the messages list using messages = response.context.get('messages'). How can we get access messages for a view that redirects?
Use the follow=True option in the client.get() call, and the client will follow the redirect. You can then test that the message is in the context of the view you redirected to.
def test_some_view(self):
# use follow=True to follow redirect
response = self.client.get('/some-url/', follow=True)
# don't really need to check status code because assertRedirects will check it
self.assertEqual(response.status_code, 200)
self.assertRedirects(response, '/some-other-url/')
# get message from context and check that expected text is there
message = list(response.context.get('messages'))[0]
self.assertEqual(message.tags, "success")
self.assertTrue("success text" in message.message)
You can use get_messages() with response.wsgi_request like this (tested in Django 1.10):
from django.contrib.messages import get_messages
...
def test_view(self):
response = self.client.get('/some-url/') # you don't need follow=True
self.assertRedirects(response, '/some-other-url/')
# each element is an instance of django.contrib.messages.storage.base.Message
all_messages = [msg for msg in get_messages(response.wsgi_request)]
# here's how you test the first message
self.assertEqual(all_messages[0].tags, "success")
self.assertEqual(all_messages[0].message, "you have done well")
If your views are redirecting and you use follow=true in your request to the test client the above doesn't work. I ended up writing a helper function to get the first (and in my case, only) message sent with the response.
#classmethod
def getmessage(cls, response):
"""Helper method to return message from response """
for c in response.context:
message = [m for m in c.get('messages')][0]
if message:
return message
You include this within your test class and use it like this:
message = self.getmessage(response)
Where response is what you get back from a get or post to a Client.
This is a little fragile but hopefully it saves someone else some time.
I had the same problem when using a 3rd party app.
If you want to get the messages from a view that returns an HttpResponseRedict (from which you can't access the context) from within another view, you can use get_messages(request)
from django.contrib.messages import get_messages
storage = get_messages(request)
for message in storage:
do_something_with_the_message(message)
This clears the message storage though, so if you want to access the messages from a template later on, add:
storage.used = False
Alternative method mocking messages (doesn't need to follow redirect):
from mock import ANY, patch
from django.contrib import messages
#patch('myapp.views.messages.add_message')
def test_some_view(self, mock_add_message):
r = self.client.get('/some-url/')
mock_add_message.assert_called_once_with(ANY, messages.ERROR, 'Expected message.') # or assert_called_with, assert_has_calls...