I am creating a project with Django Rest Framework and I ran into some problems and im not able to fix them.
So, I have a URL for post method and when I post there using postman, I get an error:
{
"detail": "JSON parse error - Expecting property name enclosed in double quotes: line 1 column 2 (char 1)"
}
This is the data im sending:
{username:"REQUAB", password:"REQUAB", emailId:"requab#gmail.com"}
And just to check if i had some problem in the serializer or the model, i did a normal get request and i got correct output.
my models.py:
from django.db import models
# Create your models here.
class User(models.Model):
emailId = models.EmailField(max_length=50)
username = models.CharField(max_length=20)
password = models.CharField(max_length=50)
recipes = models.IntegerField(default=0)
def __str__(self):
return self.username
my serializers.py:
from rest_framework import serializers
from .models import User
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'username', 'emailId', 'password', 'recipes']
my urls.py:
from django.urls import path
from .views import UsersList, UsersDetail
urlpatterns = [
path('', UsersList.as_view()),
]
my views.py:
from django.http import Http404
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from users.models import User
from .serializers import UserSerializer
# Create your views here.
class UsersList(APIView):
def get(self, request, format=None):
users = User.objects.all()
serializer = UserSerializer(users, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
def post(self, request):
print(request.data)
serializer = UserSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Pls help me with this (Thanks, in advance).
The error is pretty clear, try to enclose the property names in double quotes or use JSON.stringify when sending it (using JS).
It allow me to pass in json format data to post for creating record after i make def create so that json format like [{data:data,data:data}] can post in. How should i do so that i can also make put request with multiple object in one request or using post method to update?
Below is views.py.
from django.shortcuts import render
from .models import ListForm
# Create your views here.
from rest_framework import viewsets
from .serializers import ListFormSerializer
from rest_framework import filters
import django_filters.rest_framework
from rest_framework.response import Response
from rest_framework import status, viewsets
class ListFormViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows users to be viewed or edited.
"""
queryset = ListForm.objects.all().order_by('group')
serializer_class = ListFormSerializer
filter_backends = (django_filters.rest_framework.DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter,)
filterset_fields = ['group','key_description']
search_fields = ['group']
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data, many=isinstance(request.data,list))
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
# return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
Maybe you need something like this:
from rest_framework import viewsets
from rest_framework.response import Response
from rest_framework.decorators import action
import json
from .models import Post
from .serializers import PostSerializer
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.none()
serializer_class = serializers.PostSerializer
#action(detail=False, methods=['post'])
def update_this(self, request):
try:
data = json.loads(request.data)
except:
try:
data = dict(request.data)
except:
data = request.data
# You can do everything you want here with data
return Response({'code': 0, 'desc': 'OK', 'more_data': {}})
I am having a problem setting up authentication in django rest. Everytime I try to create a user using a post request to this url http://127.0.0.1:8000/user/create/ I get this error: AssertionError: Expected a Response, HttpResponse or HttpStreamingResponse to be returned from the view, but received a <class 'NoneType'>
[09/Apr/2021 14:41:43] "POST /user/create/ HTTP/1.1" 500 82190. I have tried may solutions here with no success.
views.py
from rest_framework_simplejwt.views import TokenObtainPairView
from rest_framework.views import APIView
from rest_framework import status, permissions
from rest_framework.response import Response
from .serializers import TokenObtainPairSerializer
from rest_framework.generics import CreateAPIView
from .serializers import CustomUserSerializer, MyTokenObtainPairSerializer
class CustomUserCreate(APIView):
permission_classes = (permissions.AllowAny,)
def post(self, request, format = 'json'):
serializer = CustomUserSerializer(data= request.data)
if serializer.is_valid():
user = serializer.save()
if user:
json = serializer.data
return Response(status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
serializers.py
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
from rest_framework import serializers
from .models import CustomUser
class CustomUserSerializer(serializers.ModelSerializer):
email = serializers.EmailField(required=True)
username = serializers.CharField()
password = serializers.CharField(min_length = 8, write_only= True)
class Meta:
model = CustomUser
fields = ('email', 'username', 'password')
extra_kwargs = {'password': {'write_only': True}}
def create(self, validated_data):
password = validated_data.pop('password', None)
instance = self.Meta.model(**validated_data)
if password is not None:
instance.set_password(password)
instance.save()
return instance
urls.py
from django.urls import path
from rest_framework_simplejwt import views as jwt_views
from .views import ObtainTokenPairWithColorView, CustomUserCreate, HelloWorld
urlpatterns = [
path('user/create/', CustomUserCreate.as_view(), name="create_user"),
]
Your view does not return anything when serializer.is_valid() returns False. In that case you need to produce a response as well. Likely this is due to bad indentation:
class CustomUserCreate(APIView):
permission_classes = (permissions.AllowAny,)
def post(self, request, format = 'json'):
serializer = CustomUserSerializer(data= request.data)
if serializer.is_valid():
user = serializer.save()
if user:
json = serializer.data
return Response(status=status.HTTP_201_CREATED)
# outside the body of the if clause
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
I have to provide a DB status response (which tells if the serializer gets stored or not) after a POST request in ModelViewSet. Please help me perform that in the view.
from django.shortcuts import render
from .models import Booking
from rest_framework import viewsets
from .serializers import BookingSerializer
class BookingViewSet(viewsets.ModelViewSet):
queryset = Booking.objects.all()
serializer_class = BookingSerializer
Just a 200 or 201 will do and django already handles this. If you want to override the response message
Here is a snippet
from rest_framework.response import Response
from rest_framework import status
def create(self, request, *args, **kwargs):
...
return Response({'success': 'Data successfully submitted'}, status=status.HTTP_200_OK)
I'm working in a project which relies in a Django User model and TokenAuthentication under DjangoRestFramework
I was requested to get last login datetime for each user and I've realized that this field is not getting updated when I call the authentication REST endpoint.
Is this a known fact? Am I missing something I must do in order to get that field updated each time the token authentication is called?
Thanks
Well, at the end I inherited from the REST Framework TokenAuthentication, pointing to it in the urls file
url(r'^api-token-auth/', back_views.TokenAuthenticationView.as_view()),
and the View handles the request and manually calls the update_last_login like this:
from django.contrib.auth.models import update_last_login
class TokenAuthenticationView(ObtainAuthToken):
"""Implementation of ObtainAuthToken with last_login update"""
def post(self, request):
result = super(TokenAuthenticationView, self).post(request)
try:
request_user, data = requests.get_parameters(request)
user = requests.get_user_by_username(data['username'])
update_last_login(None, user)
except Exception as exc:
return None
return result
#F.D.F. answer is great. Here is another way of doing it.
We send user_logged_in signals that will call update_last_login:
user_logged_in.send(sender=user.__class__, request=request, user=user)
Here is a working view (based on a custom User model that uses email as USERNAME_FIELD) :
from rest_framework import parsers, renderers
from rest_framework.authtoken.models import Token
from rest_framework.response import Response
from rest_framework.views import APIView
from django.contrib.auth.signals import user_logged_in
from emailauth.serializers import AuthTokenSerializer, UserSerializer
class ObtainAuthToken(APIView):
throttle_classes = ()
permission_classes = ()
parser_classes = (parsers.FormParser, parsers.MultiPartParser, parsers.JSONParser,)
renderer_classes = (renderers.JSONRenderer,)
serializer_class = AuthTokenSerializer
def post(self, request, *args, **kwargs):
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user']
token, created = Token.objects.get_or_create(user=user)
user_logged_in.send(sender=user.__class__, request=request, user=user)
return Response({'token': token.key, 'user': UserSerializer(user).data})
obtain_auth_token = ObtainAuthToken.as_view()
You can find the full source code here : Api View with last_login updated
Hope this helps.
A cleaner way to do it:
from django.contrib.auth.models import update_last_login
from rest_framework.authtoken.models import Token
from rest_framework.authtoken.views import ObtainAuthToken
class LoginToken(ObtainAuthToken):
def post(self, request, *args, **kwargs):
result = super().post(request, *args, **kwargs)
token = Token.objects.get(key=result.data['token'])
update_last_login(None, token.user)
return result
And then setup urls.py:
url(r'^api-token-auth/', views.LoginToken.as_view()),
My answer for Django==2.0.5, django-rest-framework-social-oauth2==1.1.0
from django.contrib.auth import user_logged_in
from oauth2_provider.models import AccessToken
from rest_framework import status
from rest_framework_social_oauth2.views import TokenView
class MyTokenView(TokenView):
def post(self, request, *args, **kwargs):
response = super().post(request, *args, **kwargs)
if response.status_code == status.HTTP_200_OK:
token = AccessToken.objects.get(token=response.data['access_token'])
user = token.user
user_logged_in.send(sender=type(user), request=request, user=user)
return response
urls.py:
from django.urls import path
urlpatterns = [
path('token', MyTokenView.as_view(), name='token'),
]
Here is my solution using a ViewSet:
views.py:
from rest_framework.authtoken.views import ObtainAuthToken
from rest_framework.authtoken.serializers import AuthTokenSerializer
from rest_framework import viewsets
from django.contrib.auth.models import update_last_login
class LoginViewSet(viewsets.ViewSet):
"""Checks email and password and returns an auth token."""
serializer_class = AuthTokenSerializer
def create(self, request):
"""Use the ObtainAuthToken APIView to validate and create a token."""
##update last_login
try:
user = models.User.objects.get(email = request.data['username'])
update_last_login(None, user)
except:
pass
return ObtainAuthToken().post(request)
Now just add this viewset to the urls.py:
router.register('login', views.LoginViewSet, base_name="login")
This is the newest code for django 3.0.8.
:)
thx F.D.F.!
from django.contrib.auth.models import update_last_login
from rest_framework.authtoken.views import ObtainAuthToken
from django.contrib.auth import get_user_model
class TokenAuthenticationView(ObtainAuthToken):
"""Implementation of ObtainAuthToken with last_login update"""
def post(self, request):
result = super(TokenAuthenticationView, self).post(request)
currentUserModel = get_user_model()
try:
user = currentUserModel.objects.get(username=request.data['username'])
update_last_login(None, user)
except Exception as exc:
return None
return result
impl via signals
from django.dispatch import receiver
from django.db.models.signals import post_save
from django.contrib.auth import user_logged_in
from oauth2_provider.models import AccessToken
#receiver(post_save, sender=AccessToken)
def post_save_access_token(instance, created, raw, **kwargs):
if not created or raw:
return
user_logged_in.send(sender=instance.user.__class__, user=instance.user)
for anyone using knox to authenticate the user then need to edit api.py file and import user_logged_in like below
from django.contrib.auth.signals import user_logged_in
after that in LoginAPI class in the same api.py file add the below line after _, token = AuthToken.objects.create(user) like below
user_logged_in.send(sender=user.__class__, request=request, user=user)
If you're using Simple JWT for Django, you can do it pretty much easily by following this link. You just have to add
SIMPLE_JWT = {
...,
'UPDATE_LAST_LOGIN': True,
...,
}
Sometimes you don't want exactly the login time, since front-end stores the token and use it without logging in again.
You can save the current time in every request that user is authenticated and authorized by subclassing APIView from rest_framework
from django.contrib.auth.models import update_last_login
from rest_framework.views import APIView
class CustomAPIView(APIView):
def check_permissions(self, request):
super().check_permissions(request)
# here user is authorized (otherwise an exception would have been raised)
update_last_login(None, request.user)
class my_endpoint(CustomAPIView):
permission_classes = [IsAuthenticated]
def get(self, request, company_id=None):
...