How to add 2FA in Django and DRF - python

I am not sure how to add 2FA to my django project and DRF ?
I will eventually connect this DRF with react and redux as the frontend.
Here is my Django side:
Models:
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
def __str__(self):
return self.username
My Django api serializer
# Login API
class LoginAPI(generics.GenericAPIView):
serializer_class = LoginSerializer
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data
return Response({
"user": UserSerializer(user, context=self.get_serializer_context()).data,
"token": AuthToken.objects.create(user)[1]
})
# Check token and send back what user is associated with that token
class UserAPI(generics.RetrieveAPIView):
permission_classes = [
permissions.IsAuthenticated,
]
serializer_class = UserSerializer
def get_object(self):
return self.request.user
My Django urls:
from django.urls import path, include
from knox import views as knox_views
from accounts.api.views import LoginAPI, UserAPI
urlpatterns = [
path('api/auth', include('knox.urls')),
path('api/auth/login', LoginAPI.as_view()),
path('api/auth/user', UserAPI.as_view()),
path('api/auth/logout', knox_views.LogoutView.as_view(), name='knox_logout'),
My django views:
from rest_framework import generics, permissions
from rest_framework.response import Response
from knox.models import AuthToken
from accounts.api.serializers import LoginSerializer, UserSerializer
# Login API
class LoginAPI(generics.GenericAPIView):
serializer_class = LoginSerializer
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data
return Response({
"user": UserSerializer(user, context=self.get_serializer_context()).data,
"token": AuthToken.objects.create(user)[1]
})
# Check token and send back what user is associated with that token
class UserAPI(generics.RetrieveAPIView):
permission_classes = [
permissions.IsAuthenticated,
]
serializer_class = UserSerializer
def get_object(self):
return self.request.user

Related

Can't change user password for Django user model using ModelViewSet

I was using Django user model using the ModelViewSet. When I am making a request to update the password for the current user that is logged in. Although I get a 200 OK response but my password never changes to the new one that I changed.
I also tried making the request from my admin user and when I made the PUT request with the password, it got changed to something else and I was logged out from the django admin panel.
Here is my
views.py
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = [IsAuthenticated, IsOwnerOfObject]
authentication_classes = (TokenAuthentication,)
serializers.py
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'username', 'password']
extra_kwargs = {
'password' : {
'write_only':True,
'required': True
}
}
def create(self, validated_data):
user = User.objects.create_user(**validated_data)
Token.objects.create(user=user) # create token for the user
return user
urls.py
router = DefaultRouter()
router.register('articles', ArticleViewSet, basename='articles')
router.register('users', UserViewSet, basename = 'users')
urlpatterns = [
path('api/', include(router.urls)),
]
permissions.py
class IsOwnerOfObject(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
return obj == request.user
Here is how I am making the request, with Authorisation token in the Headers field
Response :
As #BrianDestura says, you need call set_password to correctly update it.
class UserSerializer(serializers.ModelSerializer):
# --> Add this method
def update(self, instance, validated_data):
# We Can change the username?
instance.username = validated_data['username']
instance.set_password(validated_data['password'])
instance.save()
return instance

"POST /user/create/ HTTP/1.1" 500 82190

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)

Django doesn't provide a DB representation for AnonymousUser- Django rest api

I tried to change password without the traditional way. Give old password and new password, update the old password. I used UpdateApiView.But i get the following error.
Django doesn't provide a DB representation for AnonymousUser
I tried passigng Authorization token in header using POST MAN. But Same error.
view.py
class ChangePassword(generics.UpdateAPIView):
serializer_class = serializers.ChangePasswordSerializer
model = models.Customer
def get_object(self, queryset=None):
obj = self.request.user
return obj
def update(self, request, *args, **kwargs):
self.object = self.get_object()
serializer = self.get_serializer(data=request.data)
if serializer.is_valid():
if not self.object.check_password(serializer.data.get("old_password")):
return Response({"old_password": ["Wrong password."]}, status=HTTP_400_BAD_REQUEST)
self.object.set_password(serializer.data.get("new_password"))
self.object.save()
return Response("Success.", status=HTTP_200_OK)
return Response(serializer.errors, status=HTTP_400_BAD_REQUEST)
serializers.py
class ChangePasswordSerializer(serializers.Serializer):
old_password = serializers.CharField(required=True)
new_password = serializers.CharField(required=True)
urls.py
path('update_password', views.ChangePassword.as_view()),
EDIT:
I added TokenAuthentication in settings.py
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
)
}
In views.py, i added
authentication_classes = (TokenAuthentication, )
Now i got name 'TokenAuthentication' is not defined
I imported TokenAuthentication in views.py
from rest_framework.authentication import SessionAuthentication, TokenAuthentication
But Django doesn't provide a DB representation for AnonymousUser.
Add the attribute authentication_classes = (TokenAuthentication, ) to ChangePassword view.

Routers urls with django rest framework 3.6 and django 1.11

I have a django 1.7 with djangorestframework 3.3.2 site, and I'm migrating to django 1.11 with DRF 3.6. All things are working fine except routers with urls.
My Router:
router = routers.DefaultRouter()
router.register(r'options', MicrositeViewSet)
My urls:
urlpatterns = [
# '',
url(r'^api/v1/', include(router.urls)),
]
My viewsets:
from django.shortcuts import get_object_or_404
from rest_framework import viewsets, mixins
from rest_framework.authentication import SessionAuthentication, TokenAuthentication
from rest_framework.response import Response
from rest_framework.permissions import IsAdminUser
from microsites.permissions import AccountPermission
from microsites.models import Microsite
from microsites.serializers import SelfManagedMicrositeSerializer, MicrositeTestProfileSerializer
class MicrositeViewSet(mixins.UpdateModelMixin,
mixins.ListModelMixin,
mixins.RetrieveModelMixin,
viewsets.GenericViewSet):
"""
A viewset for viewing and editing microsites.
"""
authentication_classes = (SessionAuthentication,)
permission_classes = (AccountPermission,)
serializer_class = SelfManagedMicrositeSerializer
queryset = Microsite.objects.all()
def list(self, request, *args, **kwargs):
microsite = request.microsite
account = request.account
queryset = Microsite.objects.filter(account=account)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
def retrieve(self, request, pk=None):
queryset = self.get_queryset()
microsite = get_object_or_404(queryset, api_key=pk)
self.check_object_permissions(self.request, microsite)
microsite.pull()
microsite.set_request_user(request.user)
serializer = self.get_serializer(
microsite,
metadata=request.GET.get('metadata', False),
categories=request.GET.get('categories', False),
)
return Response(serializer.data)
def update(self, request, pk=None, *args, **kwargs):
partial = kwargs.pop('partial', False)
queryset = self.get_queryset()
microsite = get_object_or_404(queryset, api_key=pk)
serializer = self.get_serializer(microsite, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
return Response(serializer.data)
This code exactly as it is works with django 1.7 and DRF 3.2, but in django 1.11 and DRF 3.6 does not calls the urls of the router. Any clues? I thought that could be router.ulr with quotes, but says I don't have any urlpatterns in routers.

API call to update a field of an object

So I have an model serializer which consists of
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'name', 'description')
This is my ViewSet
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
This is my URLs.py file:
from django.conf.urls import include, url
from rest_framework import routers
import views
router = DefaultRouter()
router.register('user', views.UserViewSet)
urlpatterns = [
url(r'^', include(router.urls)),
url(r'^login/', include('rest_framework.urls', namespace='rest_framework'))
]
Using the serializer, I can make it print out the objects inside my database. If I have the object PK/ID, I want to be able to update the field id or name of the object. Is there a way I can do that with a patch/post request using the serializer? I'm new to this so I'd love it if someone can help me out with this.
I'm thinking of just doing a POST request, then have it do this:
user = User.objects.get(id=id)
user.name = "XXXXX"
user.save()
But I want to do this using the serializer, using a PATCH request.
the below code will help to you,
**filename : views.py**
from user.models import User
from users.serializers import UserSerializer
from django.http import Http404
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
class UserList(APIView):
"""
List all users, or create a new user.
"""
def get(self, request, format=None):
users = User.objects.all()
serializer = UserSerializer(users, many=True)
return Response(serializer.data)
def post(self, request, format=None):
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)
class UserDetail(APIView):
"""
Retrieve, update or delete a user instance.
"""
def get_object(self, pk):
try:
return User.objects.get(pk=pk)
except User.DoesNotExist:
raise Http404
def get(self, request, pk, format=None):
user = self.get_object(pk)
serializer = UserSerializer(user)
return Response(serializer.data)
def put(self, request, pk, format=None):
user = self.get_object(pk)
serializer = UserSerializer(user, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk, format=None):
user = self.get_object(pk)
user.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
**filename : urls.py**
from django.conf.urls import url
from rest_framework.urlpatterns import format_suffix_patterns
from users import views
urlpatterns = [
url(r'^users/$', views.UserList.as_view()),
url(r'^user/(?P<pk>[0-9]+)/$', views.UserDetail.as_view()),
]
urlpatterns = format_suffix_patterns(urlpatterns)
Reference : http://www.django-rest-framework.org/tutorial/3-class-based-views/
Django rest framework comes with some pre-defined concrete generic views such as UpdateAPIView, RetrieveUpdateAPIView.
First you need to create a view for user which uses one of the views which can update. The update views provides handlers for patch method on the view.
RetrieveUpdateAPIView
Used for read or update endpoints to represent a single model instance.
Provides get, put and patch method handlers.
Now, use this to create a view:
class UserDetail(generics.RetrieveUpdateAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
To access this view you need to have a url which uses user's primary key to access the user:
url(r'users/(?P<pk>\d+)/$', UserDetail.as_view(), name='api-user-detail'),
Then using PATCH call you can update the user's name.
Since you're using a ModelViewset, this capability should be built in. If you use the browsable API to navigate to /user/<pk>/, you'll see the operations you can perform on that object. By default, a ModelViewset provides list(), retrieve(), create(), update(), and destroy() capability.
http://www.django-rest-framework.org/api-guide/viewsets/#modelviewset
You can also override any or all of the provided methods, however an update of a single object is built in to DRF ModelViewsets. Use curl to try a PATCH to /user/<pk>/ with the information you'd like to update.

Categories

Resources