Django unit testing authentication token not accepted - django

I've checked the DRF official documentation, have read the below posts and copy-pasted code from some answers to avoid any typo, but I still can't authenticate in my unit tests. I always get a 401 response code.
I have a custom User model that just has a couple of none required fields so far.
Can anyone see any logic issue, or error in the code?
Could the issue originate from having a proper local DB and a testing DB and the authentication mixing both up somehow?
I've had to use self.client.force_authenticate(user=self.user, token=None) to bypass the authentication issue. But that defeats the purpose of the tests.
Checked posts that did not solve the problem:
DRF documentation
SO post 1
SO post 2
SO post 3
So here's my code.
Unit test
from django.test.testcases import SimpleTestCase
from django.urls import reverse, resolve
from rest_framework import status
from rest_framework.test import APITestCase
from rest_framework.authtoken.models import Token
from test_project.models.user import *
from test_project.views import UserViewSet
# from django.contrib.auth.models import User
from test_project.models import *
class UserAPIViewTests(APITestCase):
def setUp(self) -> None:
self.users_list_url = reverse('user-list')
self.users_detail_url = reverse('user-detail', args=[1])
self.user =User.objects.create(
username="admin",
password="admin",
email="test#necktie.com",
first_name="test first name",
last_name="test last name",
is_superuser=True,
is_staff=True,
is_active=True
)
self.token = Token.objects.create(user=user)
self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + self.token.key)
# The testing DB is automatically torn down, no implementation required for those tests
def tearDown(self) -> None:
pass
def test_get_users_authenticated(self):
"""
Test that an authenticated user can get the users' list
"""
response = self.client.get(self.users_list_url)
self.assertEquals(response.status_code, status.HTTP_200_OK)
User model
from django.db import models
from django.contrib.auth.models import AbstractUser
from django.utils.translation import gettext_lazy as _
class User(AbstractUser):
"""auth/login-related fields"""
gender = models.CharField(_('gender'), max_length=50, null=True)
nationality = models.CharField(_('nationality'), max_length=50, null=True)
def __str__(self):
return "{} {}".format(self.first_name, self.last_name)
User View
from rest_framework import viewsets
from rest_framework.response import Response
from rest_framework import permissions
from ..models.user import User
from ..serializers.user import *
from ..permissions import *
class UserViewSet(viewsets.ModelViewSet):
"""
This viewset automatically provides `list`, `create`, `retrieve`,
`update` and `destroy` actions.
"""
queryset = User.objects.all()
serializer_class = UserSerializer
def get_permissions(self):
"""
Method to apply permissions depending on request type (GET, PUT etc.)
"""
if self.request.method == 'GET':
return [permissions.IsAuthenticated(), IsStaff()]
else:
return [permissions.IsAuthenticated(), IsSuperuser()]
User serializers
from rest_framework import serializers
from ..models.user import User
class UserSerializer(serializers.ModelSerializer):
"""
Serializer for all actions on User model
"""
class Meta:
model = User
fields = [
'id',
'username',
'first_name',
'last_name',
'is_staff',
'is_superuser',
'is_active',
'date_joined'
]
Thanks for the help!

Related

Django 2.2 and Rest Framework 3.11 - partial updating model "owner" field (ForeignKey) with owner's username string instead of pk

Django 2.2 and Rest Framework 3.11
I have an Alarm model. Every alarm instance can optionally have an owner (the standard django User model).
I want to be able to partially update (PATCH) an alarm by setting its owner just using his/her username (string) instead of the pk.
Right now, I can update an alarm's owner only by using his/her pk.
I tried various things, like:
override the update() method in the AlarmSerializer class but whenever I use the owner's username string instead of the pk in the PATCH call, I get back:
{
"owner": [
"Incorrect type. Expected pk value, received str."
]
}
play with nested serializers and lookup_field but no luck so far.
The api call (PATCH) should look like this:
url: /api/alarms/{alarm_id}/
Payload:
{
"owner": "owner_username"
}
How can I do that? Thanks
models.py
from django.db import models
from django.conf import settings
class Alarm(models.Model):
"""
this class is meant to represent a network alarm (a.k.a. event or ticket)
coming from some monitoring system (Zabbix, Nagios, etc.)
"""
customer = models.CharField(max_length=50)
owner = models.ForeignKey(
settings.AUTH_USER_MODEL,
models.SET_NULL,
blank=True,
null=True,
)
managed = models.BooleanField(default=False, blank=False, verbose_name="Managed ?")
device_type = models.CharField(max_length=150)
...
serializers.py
from django.contrib.auth.models import User
from rest_framework import serializers
from .models import Alarm
class AlarmSerializer(serializers.ModelSerializer):
class Meta:
model = Alarm
fields = [
"customer",
"owner",
"device_type",
"device_name",
"ip_address",
"date",
]
views.py
from rest_framework import viewsets
from .models import Alarm
from .serializers import AlarmSerializer
# Create your views here.
class AlarmViewSet(viewsets.ModelViewSet):
queryset = Alarm.objects.all()
serializer_class = AlarmSerializer
I found a solution: convert the owner username string in pk inside the ModelViewSet.
You need to override the partial_update() method inside the ModelViewSet in views.py.
This view gets hit by the api PATCH call before serialization validation kicks in; hence this is the correct place to swap the username string with the username pk.
views.py
from django.contrib.auth.models import User
from django.shortcuts import get_object_or_404
from rest_framework import viewsets
from rest_framework.response import Response
from .models import Alarm
from .serializers import AlarmSerializer
class AlarmViewSet(viewsets.ModelViewSet):
queryset = Alarm.objects.all()
serializer_class = AlarmSerializer
def partial_update(self, request, *args, **kwargs):
instance = self.get_object()
# if the owner field is not a string, it should be
# a legit pk, so we don't do anything.
if isinstance(request.data["owner"], str):
owner_username = request.data.pop("owner")
owner = get_object_or_404(User, username=owner_username)
request.data["owner"] = owner.pk
serializer = self.serializer_class(instance, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data)

Django+React rest-auth

I am using the rest-auth module to enable user authentication on my web app. Though I am facing some difficulties in fetching details about the user. The Django-rest-framework return a key when I post my username and password, while that's enough for logging in I also want to fetch additional details like user.is_staff, user.username and user.email.
I tried to use Token serializer, but I am not sure if I am doing it right.
** settings.py **
REST_AUTH_SERIALIZERS = {
'TOKEN_SERIALIZER': '## How to define the path to my serializer ##',
}
** serializers.py **
from rest_framework import serializers
from lms.models.post_models import Post
from django.contrib.auth.models import User
from rest_auth.models import TokenModel
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('username', 'email')
class TokenSerializer(serializers.ModelSerializer):
user = UserSerializer()
class Meta:
model = TokenModel
fields = ('key', 'user')
Please tell what piece is missing or if any piece is incorrect. Also, please help me figure out the part between ## ##.
Thank you!
I think you are doing it right, in your custom TokenSerializer you need to fetch the user somehow. If I look at the code of the LoginView, I see that you can use request object from context within serializer, so your TokenSerializer should be like:
# serializers.py
class TokenSerializer(serializers.ModelSerializer):
user = serializers.SerializerMethodField()
class Meta:
model = TokenModel
fields = ('key', 'user')
def get_user(self, instance):
request = self.context.get('request')
return UserSerializer(request.user).data
and then settings.py
REST_AUTH_SERIALIZERS = {
'TOKEN_SERIALIZER': 'project.serializers.TokenSerializer',
}
EDITED:
This might break your register view because if you look at the source code, at line 60 it uses the same serializer but doesn't pass the request object in the context of serializer. You can make it work by overriding this method
# views.py
from django.conf import settings
from rest_auth.registeraion.views import RegisterView
from allauth.account import app_settings as allauth_settings
from rest_auth.app_settings import (TokenSerializer,
JWTSerializer)
class CustomRegisterView(RegisterView):
def get_response_data(self, user):
if allauth_settings.EMAIL_VERIFICATION == \
allauth_settings.EmailVerificationMethod.MANDATORY:
return {"detail": _("Verification e-mail sent.")}
if getattr(settings, 'REST_USE_JWT', False):
data = {
'user': user,
'token': self.token
}
return JWTSerializer(data).data
else:
return TokenSerializer(user.auth_token, context={"request": self.request}).data
and then use this view for registration in your urls
# urls.py
from views import CustomRegisterView
urlpatterns = [
...,
url(r'^rest-auth/', include('rest_auth.urls')),
url(r'^rest-auth/registration/', CustomRegisterView.as_view())
]

django rest_framework caches sql queries

I am new to Django and React. I am taking over a Django / React project and have an issue where an api url is called:
GET /api/admin/parents/
but the underlying sql statement:
from django.http import HttpResponseRedirect
from django.db.models import Q
from .models import *
from rest_framework import permissions, status
from rest_framework.decorators import api_view, permission_classes
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.generics import ListAPIView, ListCreateAPIView, CreateAPIView, RetrieveUpdateAPIView, DestroyAPIView
from .serializers import *
from twilio.rest import Client
class AdminAllParentsView(ListAPIView):
serializer_class = UserSerializer
queryset = User.objects.filter(is_staff=False, is_parent=True, is_active=True)
does not get re-executed each time the GET /api/admin/parents/ call is performed.
Say for instance.. a sql record is changed so that there are fewer records matching the is_parent=True part of the api call / query... the results returned by the api call don't reflect the real / current status of the query. The GET /api/admin/parents/ still returns the data from a previous, sql query ( a cached version I assume ) and does not re-execute the query.
The urls.py part of this is:
from django.urls import path
from rest_framework_jwt.views import obtain_jwt_token
from .views import *
urlpatterns = [
path('admin/parents/', AdminAllParentsView.as_view())
So how to I get current sql data from this each time I do a api call?
jack
... I see in the browser that the /api/admin/parents/ request goes out and returns the old data via json data. It's not a cached browser html thing.
and
... here is the user serializer code... someone said that this might be important too.
from rest_framework import serializers
from rest_framework_jwt.settings import api_settings
from .models import *
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = (
'id',
'email',
'first_name',
'last_name',
'is_parent',
'phone',
'mobile_phone',
'address',
'city',
'zip',
'county',
'agency_name',
'caseworker_name',
'caseworker_email',
'caseworker_phone',
'caseworker_ext',
'is_active',
'agency_approved',
)
class UserSerializerWithToken(serializers.ModelSerializer):
token = serializers.SerializerMethodField()
password = serializers.CharField(write_only=True)
def get_token(self, obj):
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
payload = jwt_payload_handler(obj)
token = jwt_encode_handler(payload)
return token
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
class Meta:
model = User
fields = '__all__'

Django API: get authenticated user from request

The user is authenticated using allauth. I want to create a profile and set the authenticated user as the owner of the profile. How can I get the user?
Model class:
from django.db import models
from allauth.utils import get_user_model
from courses.models import Course
class Profile(models.Model):
owner = models.OneToOneField(get_user_model(), on_delete=models.CASCADE)
courses = models.ManyToManyField(Course, blank=True)
def get_courses_items(self):
return self.courses.all()
def __str__(self):
return self.owner.username
Views:
from rest_framework.generics import CreateAPIView
from profiles.models import Profile
from .serializers import ProfileSerializer
class ProfileCreateView(CreateAPIView):
queryset = Profile.objects.all()
serializer_class = ProfileSerializer
You can get the user from the request with request.user. Then probably you should override the def create method of the CreateAPIView to use that user and create the object.
set your user model in the settings file (base.py) and import it
AUTH_USER_MODEL = 'users.User' #(format is module.user model name)
from django.conf import settings
user = models. OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
method 2
Override the get_create method of the ProfileView. Every authenticated request has a request.user object in it which represents the user making the request. To get the user's id with this you just run request.user.id
class ProfileCreateView(CreateAPIView):
queryset = Profile.objects.all()
serializer_class = ProfileSerializer
def create(self, request, *args, **kwargs):
user = User.objects.get(id=request.user.id)
# create the profile and save it
...
in your serializers you can also get the current user this way
from rest_framework.serializers import CurrentUserDefault, PrimaryKeyRelatedField
class ProfileModelSerializer(ModelSerializer):
user = PrimaryKeyRelatedField(read_only=True, default=CurrentUserDefault())
class Meta:
...
I don't know how the remainder of your setup is but any or a combination of these works

Django Restful API Design Validation Logic

Here I have an endpoint to create media content for users. The endpoint works, but I have a feeling my design implementation is incorrect.
Should validation logic be contained in serializers create? Is this bad practice? I attempted to move validation logic to models.py, but ran into issues with accessing the model, specifically this line - self.model(user=user, category=category).
view.py
from rest_framework import status
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from .models import UserMedia
from .renderers import UserMediaSerializerJSONRenderer
from .serializers import UserMediaSerializer
class UserMediaCreateAPIView(APIView):
permission_classes = (IsAuthenticated,)
renderer_classes = (UserMediaSerializerJSONRenderer,)
serializer_class = UserMediaSerializer
def post(self, request):
userMedia = request.data.get('userMedia', {})
serializer = self.serializer_class(data=userMedia)
serializer.is_valid(raise_exception=True)
serializer.save(user=request.user, category=userMedia['category'])
return Response(serializer.data, status=status.HTTP_201_CREATED)
serializers.py
from rest_framework import serializers
from .models import UserMedia
class UserMediaSerializer(serializers.ModelSerializer):
category = serializers.CharField(allow_blank=False, required=True)
class Meta:
model = UserMedia
fields = ('category',)
read_only_fields = ('category',)
def get_category(self, obj):
if obj.category:
return obj.category
return 'N/A'
def create(self, validated_data):
if validated_data['user'] is None:
raise TypeError('User media must have a user')
if validated_data['category'] is None:
raise TypeError('User media must have a category.')
if validated_data['category'] not in dict(UserMedia.CATEGORY_CHOICES):
raise TypeError('User media category is not available.')
userMedia = UserMedia(**validated_data)
userMedia.save()
return userMedia
models.py
from django.db import models
class UserMedia(models.Model):
user = models.ForeignKey('authentication.User', on_delete=models.CASCADE, related_name='media')
MUSIC = 'M'
VIDEO = 'V'
CATEGORY_CHOICES = (
(MUSIC, 'Music'),
(VIDEO, 'Video'),
)
category = models.CharField(max_length=1, choices=CATEGORY_CHOICES, blank=False)
The validation should be done in your view. The serializers should just be for serializing data. The validation should be done in your view then the serializer is called from your view. As far as this line self.model(user=user, category=category) is concerned it does not appear that you ever import user any where.