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())
]
Related
class Admin(models.Model):
username = models.CharField(primary_key=True, max_length=30)
password = models.CharField(max_length=255)
email = models.EmailField(unique=True)
created_on = models.DateTimeField(auto_now=True)
django_user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='admin')
class AdminAPIViewSet(viewsets.ModelViewSet):
queryset = Admin.objects.all()
serializer_class = AdminSerializer
permission_classes = [permissions.IsAdminUser]
def get_queryset(self):
if self.request.user.is_authenticated:
return Admin.objects.filter(username=self.request.user.admin.username)
else:
return []
def create(self, request, *args, **kwargs):
serializer = AdminSerializer(data=request.data)
if serializer.is_valid():
email = serializer.data['email']
username = serializer.data['email']
password = serializer.data['password']
with transaction.atomic():
django_user = User.objects.create_user(username, email, password)
admin = Admin.objects.create(**serializer.data, django_user=django_user)
#User.objects.filter(pk=1001).update(is_superuser=True, is_staff=True)
return Response(admin.pk)
return Response('/error')
class ClientFullAccessAPIViewSet(viewsets.ModelViewSet):
queryset = Client.objects.all()
serializer_class = ClientSerializer
permission_classes = [permissions.IsAdminUser]
def create(self, request, *args, **kwargs):
serializer = ClientSerializer(data=request.data)
if serializer.is_valid():
email = serializer.data['email']
username = serializer.data['email']
password = serializer.data['password']
with transaction.atomic():
django_user = User.objects.create_user(username, email, password)
client = Client.objects.create(**serializer.data, django_user=django_user)
return Response(client.username)
return Response('/error')
`Here am trying to make the admin see the all the clients and the client see his data only ,... but I couldn't find why the i cant see the all the list clients as an admin, I am keep getting not authorized to access this endpoint..
`
urls.py
from django.contrib import admin
from django.urls import path, include
from rest_framework import routers
import user_management.views
router = routers.DefaultRouter()
router.register(r'clients', user_management.views.ClientReadOnlyAPIViewSet)
router.register(r'clientslist', user_management.views.ClientFullAccessAPIViewSet)
router.register(r'admin', user_management.views.AdminAPIViewSet)
urlpatterns = [
path('admin/', admin.site.urls),
path('api-auth/', include('rest_framework.urls')),
path('api/v1/', include(router.urls)),
#path('clients/', user_management.views.ClientAPIViewSet.as_view(), name="clients"),
]
`Here am trying to make the admin see the all the clients and the client see his data only ,... but I couldn't find why the i cant see the all the list clients as an admin, I am keep getting not authorized to access this endpoint..any help please?
The problem is that you defined your Admin model from a regular Django model so Django cannot find the user permissions associated to it.
You should inherit from a Django authentication user model (AbstractUser or AbstractBaseUser) as indicated in the documentation.
For instance, you can do:
from django.contrib.auth.models import AbstractUser
class CustomAdminUser(AbstractUser):
# Here you normally only want to
# define the fields not defined in
# the base model AbstractUser
pass
Then, to create your admin:
CustomAdminUser.objects.create_superuser(...)
Last but not least, two important things (mentioned in the above Django documentation's link):
Don’t forget to point the AUTH_USER_MODEL to your custom user model. Do this before creating any migrations or running manage.py migrate for the first time.
Register the model in the app’s admin.py.
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!
Scenario:
I have two APIs currently. A RegisterAPI and a LoginAPI. The RegisterAPI registers a new user and a LoginAPI allows them to access their details only. I used JWT authentication as well.
I want to retrieve data from the database for a particular user who logs in using the Login API in DRF. My intention is to interact with the database but the problem is that I have two models in my serializer with a One-To-One relationship and I am confused about how to move forward with this issue. To get a clearer picture, view my code below:
models.py:
from django.db import models
from django.contrib.auth.models import User
class UserData(models.Model):
user = models.OneToOneField(User, on_delete = models.CASCADE)
company = models.CharField(max_length=100, blank=True, null=True)
address = models.TextField()
views.py:
class RegisterAPI(APIView):
permission_classes = [AllowAny]
def post(self, request, format=None):
serializer = UserDataSerializer(data=request.data)
if serializer.is_valid():
content = {
'status': 'Thanks for registering'}
return Response(content, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class LoginAPI(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated]
def list(self, request, *args, **kwargs):
user_id = request.user.id
queryset = User.objects.filter(user = request.user)
user_serializer = UserSerializer(queryset, many = True)
return Response(user_serializer.data)
serializers.py:
from rest_framework import serializers
from users.models import UserData
from django.contrib.auth.models import User
class UserDataSerializer(serializers.ModelSerializer):
class Meta:
model = UserData
fields = ('company', 'address')
class UserSerializer(serializers.ModelSerializer):
info = UserDataSerializer()
class Meta:
model = User
fields = ('username', 'email', 'password', 'info')
def create(self, validated_data):
p_data = validated_data['info']
password = validated_data['password', None]
user = User.objects.create(**validated_data)
if password is not None:
user.set_password(password)
user.save()
UserData.objects.create(user = user, **p_data)
return validated_data
urls.py:
from rest_framework.routers import DefaultRouter
from users.views import LoginAPI, RegisterAPI
router = DefaultRouter()
router.register('login_api', LoginAPI, basename = 'login_api')
urlpatterns = [
path('admin/', admin.site.urls),
path('', include(router.urls)),
path('register_api/', RegisterAPI.as_view(), name = 'register_api'),
]
I want to return the user's Username, Email, Company and Address within the LoginAPI. These are from both the User and Profile models.
Example:
{'username' = 'Tim',
'email' = 'tim#company.com',
'company' = 'Riko',
'address' = 'Kenya'}
However, I am not able to retrieve the records from the User model and Profile model when accessing the Login API URL.
I am getting this error:
AttributeError at /login_api/
Got AttributeError when attempting to get a value for field `info` on serializer `UserSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `User` instance.
Original exception text was: 'User' object has no attribute 'info'.**
Can anyone explain to me what went wrong?
DRF serializers subclasses Field (serializers.Field), so you can use the source argument to refer the attribute which should be used to resolve the value of the field (UserDataSerializer in this case):
class UserSerializer(serializers.ModelSerializer):
info = UserDataSerializer(source='userdata')
# ^^^^ Here
...
The error you're getting is pretty self-explanatory. You're passsing a User instance to the UserSerializer but you have a field defined as info. As the User instance does not have such attribute, it shows the error. So you need some way to tell the serializer the value of the field, using source to refer an existing attribite is one way of doing that.
I have created a login and signup model also a post model in which a user can post the title and description. But when I login with a user and create a post then that post is also viewed by other user also.
I am using Django(python) with database sql.
For sign up form :
from django.contrib.auth import get_user_model
from django.contrib.auth.forms import UserCreationForm
class UserCreateForm(UserCreationForm):
class Meta:
fields = ("username", "email", "password1", "password2")
model = get_user_model()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["username"].label = "Display name"
self.fields["email"].label = "Email address"
url.py :
from django.conf.urls import url
from django.contrib.auth import views as auth_views
from . import views
app_name = 'accounts'
urlpatterns = [
url(r"login/$",auth_views.LoginView.as_view(template_name="accounts/login.html"),name='login'),
url(r"logout/$", auth_views.LogoutView.as_view(), name="logout"),
url(r"signup/$", views.SignUp.as_view(), name="signup"),
]
How can I limit my post to that specific user?
Well, its simple. You need to define a relation between post model and user model like this:
from django.contrib.auth import get_user_model
User = get_user_model()
class Post(models.Model):
created_by = models.ForeignKey(User)
# rest of the fields
Now in the Post Create View, you can do it like this:
from django.contrib.auth.mixins import LoginRequiredMixin
class PostCreateView(LoginRequiredMixin, CreateView):
# ...
def form_valid(self, form):
form.created_by = self.request.user
return super(PostCreateView, self).form_valid(form)
And finally in ListView, you need to define the queryset like this:
class PostListView(LoginRequiredMixin, ListView):
# ...
def get_queryset(self):
queryset = super(PostCreateView).get_queryset()
return queryset.filter(created_by=self.request.user)
Your problem is a concept called Authorization. you need to check authorization of your users on access to posts, so each user only sees his posts.
First of all save user beside post model on each instance (using ForeignKey). So that each post has a user assigned to it.
Now on accessing posts view with each user check if the user is authenticated, if so, then check if this user is the owner of that post or not, if not then give 403 permission denied error to him.
For example if your Post model is like this:
class Post(models.Model):
user = models.ForeignKey(User)
content = models.CharFie.........
.....
Then on each request to access a post, check if user is allowed to see it or not:
post = Post.objects.get(id=1) # For example you want to check post with id of 1
if post.user.id == request.user.id:
# Then allow user to see it,
else:
# Raise error 403
Note: Also add login_required decorator before post views. or IsAuthenticated permission class if you are using CBV.
I am stuck with the JWT, can any one tell me how can I get user ID along with token.
My url.py :
from rest_framework_jwt.views import obtain_jwt_token
from scrumboard.views import UserDetail
from scrumboard import views
urlpatterns = [
url(r'^api-token-auth/', obtain_jwt_token),
url(r'^users/$', views.UserList.as_view()),
]
views.py:
class UserList(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
class UserDetail(generics.RetrieveAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
serializers.py:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username', 'email', 'password')
The problem is that when i go to api-token-auth/, its only giving me the username and password and not the id.
You can get the token, userid and username by following these steps:
Inside your app, create a utils.py file (at the same level where serializers.py is placed).
Content of utils.py:
from .serializers import UserSerializer #you have already created UserSerializer
def jwt_response_payload_handler(token, user=None, request=None):
user = UserSerializer(user, context={'request': request}).data
return {
'token': token,
'userid': user['id'],
'username':user['username']
}
In your settings.py, add
JWT_AUTH = {
'JWT_RESPONSE_PAYLOAD_HANDLER':
'app_name.utils.jwt_response_payload_handler', #app_name is name of the app which contains utils.py
}
Now when you hit the api (api-token-auth/) to get the token, it will respond with token, userid and username.
Example of response:
{
'token':'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6ImdhbmVzaG5lZ2lAZ21haWwuY29tIiwiZXhwIjoxNTI0NTAyNzIzLCJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImdhbmVzaG5lZ2kifQ.RxnYF1gwaYywSnnWC-ngrcZaw90lpD6Rq7jcbnEoZVk',
'userid':1,
'username':'ganeshnegi'
}
According to the django-rest-framework-jwt docs this view only returns the username and password (emphasis mine).
In your urls.py add [url(r'^api-token-auth/', obtain_jwt_token)] to enable obtaining a token via a POST included the user's username and password.
I wouldn't worry about not obtaining the id, you should be able to query the User table by username.