Multi-Role User Authentication in Django Rest Framework

Introduction:

User authentication is a fundamental aspect of many web applications. Django provides a powerful authentication system out-of-the-box, but sometimes you need to extend it to support multiple user roles. In this post, we’ll explore how to implement multi-role user authentication using Django Rest Framework (DRF).

Setting Up the Project:

Let’s start by creating a new Django project. If you don’t have a Django environment set up, you can create one by following these steps:

  1. Create a virtual environment: python -m venv venv
  2. Activate the virtual environment: venv\Scripts\activate

Once your environment is activated, install Django and Django Rest Framework:

pip install django djangorestframework
pip install django djangorestframework
pip install django djangorestframework

Enter fullscreen mode Exit fullscreen mode

You can follow any approach you prefer to setup environment. When your env is ready, open terminal (with env activated) and run the following commands:

django-admin startproject multi_role_auth
<span>cd </span>multi_role_auth
django-admin startproject multi_role_auth
<span>cd </span>multi_role_auth
django-admin startproject multi_role_auth cd multi_role_auth

Enter fullscreen mode Exit fullscreen mode

Start our authentication app:

Open terminal (with env activated) and run the following commands:

python manage.py startapp authentication
python manage.py startapp authentication
python manage.py startapp authentication

Enter fullscreen mode Exit fullscreen mode

Now that we have our project structure ready, let’s dive into the implementation.

Defining User Model:

In the authentication/models.py file, we’ll define a custom user model that extends the AbstractUser class from Django’s authentication models. This model will include a role field to assign different roles to each user.

<span># authentication/models.py </span>
<span>from</span> <span>django.db</span> <span>import</span> <span>models</span>
<span>from</span> <span>django.contrib.auth.models</span> <span>import</span> <span>AbstractUser</span>
<span>from</span> <span>django.conf</span> <span>import</span> <span>settings</span>
<span>from</span> <span>rest_framework.authtoken.models</span> <span>import</span> <span>Token</span>
<span>class</span> <span>User</span><span>(</span><span>AbstractUser</span><span>):</span>
<span>ROLE_CHOICES</span> <span>=</span> <span>(</span>
<span>(</span><span>'</span><span>administrator</span><span>'</span><span>,</span> <span>'</span><span>Administrator</span><span>'</span><span>),</span>
<span>(</span><span>'</span><span>teacher</span><span>'</span><span>,</span> <span>'</span><span>Teacher</span><span>'</span><span>),</span>
<span>(</span><span>'</span><span>student</span><span>'</span><span>,</span> <span>'</span><span>Student</span><span>'</span><span>),</span>
<span>(</span><span>'</span><span>staff</span><span>'</span><span>,</span> <span>'</span><span>Staff</span><span>'</span><span>),</span>
<span>)</span>
<span>role</span> <span>=</span> <span>models</span><span>.</span><span>CharField</span><span>(</span><span>max_length</span><span>=</span><span>15</span><span>,</span> <span>choices</span><span>=</span><span>ROLE_CHOICES</span><span>)</span>
<span># authentication/models.py </span>
<span>from</span> <span>django.db</span> <span>import</span> <span>models</span>
<span>from</span> <span>django.contrib.auth.models</span> <span>import</span> <span>AbstractUser</span>
<span>from</span> <span>django.conf</span> <span>import</span> <span>settings</span>
<span>from</span> <span>rest_framework.authtoken.models</span> <span>import</span> <span>Token</span>


<span>class</span> <span>User</span><span>(</span><span>AbstractUser</span><span>):</span>
    <span>ROLE_CHOICES</span> <span>=</span> <span>(</span>
        <span>(</span><span>'</span><span>administrator</span><span>'</span><span>,</span> <span>'</span><span>Administrator</span><span>'</span><span>),</span>
        <span>(</span><span>'</span><span>teacher</span><span>'</span><span>,</span> <span>'</span><span>Teacher</span><span>'</span><span>),</span>
        <span>(</span><span>'</span><span>student</span><span>'</span><span>,</span> <span>'</span><span>Student</span><span>'</span><span>),</span>
        <span>(</span><span>'</span><span>staff</span><span>'</span><span>,</span> <span>'</span><span>Staff</span><span>'</span><span>),</span>
    <span>)</span>

    <span>role</span> <span>=</span> <span>models</span><span>.</span><span>CharField</span><span>(</span><span>max_length</span><span>=</span><span>15</span><span>,</span> <span>choices</span><span>=</span><span>ROLE_CHOICES</span><span>)</span>
# authentication/models.py from django.db import models from django.contrib.auth.models import AbstractUser from django.conf import settings from rest_framework.authtoken.models import Token class User(AbstractUser): ROLE_CHOICES = ( ('administrator', 'Administrator'), ('teacher', 'Teacher'), ('student', 'Student'), ('staff', 'Staff'), ) role = models.CharField(max_length=15, choices=ROLE_CHOICES)

Enter fullscreen mode Exit fullscreen mode

Feel free to customize the ROLE_CHOICES tuple to include the specific roles that are relevant to your application. Additionally, if you require more fields for the User model, you can easily add them to this model. You can refer to the documentation to explore all the default fields provided by Django’s User model. This flexibility allows you to tailor the User model to meet the specific requirements of your project.

Creating Serializers:

Next, create serializers for our authentication app. Serializers help in converting complex data types into JSON, making it easy to send data over HTTP.

Create authentication/serializers.py and add the following code:

<span># authentication/serializers.py </span>
<span>from</span> <span>rest_framework</span> <span>import</span> <span>serializers</span>
<span>from</span> <span>.models</span> <span>import</span> <span>User</span>
<span>class</span> <span>UserSerializer</span><span>(</span><span>serializers</span><span>.</span><span>ModelSerializer</span><span>):</span>
<span>class</span> <span>Meta</span><span>:</span>
<span>model</span> <span>=</span> <span>User</span>
<span>fields</span> <span>=</span> <span>[</span><span>'</span><span>username</span><span>'</span><span>,</span> <span>'</span><span>email</span><span>'</span><span>,</span> <span>'</span><span>role</span><span>'</span><span>,</span> <span>'</span><span>password</span><span>'</span><span>]</span>
<span>extra_kwargs</span> <span>=</span> <span>{</span><span>'</span><span>password</span><span>'</span><span>:</span> <span>{</span><span>'</span><span>write_only</span><span>'</span><span>:</span> <span>True</span><span>}}</span>
<span>def</span> <span>create</span><span>(</span><span>self</span><span>,</span> <span>validated_data</span><span>):</span>
<span>user</span> <span>=</span> <span>User</span><span>.</span><span>objects</span><span>.</span><span>create_user</span><span>(</span><span>**</span><span>validated_data</span><span>)</span>
<span>return</span> <span>user</span>
<span># authentication/serializers.py </span>
<span>from</span> <span>rest_framework</span> <span>import</span> <span>serializers</span>
<span>from</span> <span>.models</span> <span>import</span> <span>User</span>

<span>class</span> <span>UserSerializer</span><span>(</span><span>serializers</span><span>.</span><span>ModelSerializer</span><span>):</span>
    <span>class</span> <span>Meta</span><span>:</span>
        <span>model</span> <span>=</span> <span>User</span>
        <span>fields</span> <span>=</span> <span>[</span><span>'</span><span>username</span><span>'</span><span>,</span> <span>'</span><span>email</span><span>'</span><span>,</span> <span>'</span><span>role</span><span>'</span><span>,</span> <span>'</span><span>password</span><span>'</span><span>]</span>
        <span>extra_kwargs</span> <span>=</span> <span>{</span><span>'</span><span>password</span><span>'</span><span>:</span> <span>{</span><span>'</span><span>write_only</span><span>'</span><span>:</span> <span>True</span><span>}}</span>

    <span>def</span> <span>create</span><span>(</span><span>self</span><span>,</span> <span>validated_data</span><span>):</span>
        <span>user</span> <span>=</span> <span>User</span><span>.</span><span>objects</span><span>.</span><span>create_user</span><span>(</span><span>**</span><span>validated_data</span><span>)</span>
        <span>return</span> <span>user</span>
# authentication/serializers.py from rest_framework import serializers from .models import User class UserSerializer(serializers.ModelSerializer): class Meta: model = User fields = ['username', 'email', 'role', 'password'] extra_kwargs = {'password': {'write_only': True}} def create(self, validated_data): user = User.objects.create_user(**validated_data) return user

Enter fullscreen mode Exit fullscreen mode

Here, we’ve defined a UserSerializer that inherits from the ModelSerializer provided by DRF. We specify the model as our custom User model and define the fields to include in the serialized representation. Additionally, we set the “password” field as write-only to prevent it from being exposed in responses. In the fields attribute, you can include all fields by passing fields = '__all__'.

Creating Views:

Now, let’s implement the views for user registration, login, and logout.

In authentication/views.py, add the following code:

<span># authentication/views.py </span>
<span>from</span> <span>authentication.models</span> <span>import</span> <span>User</span>
<span>from</span> <span>authentication.serializers</span> <span>import</span> <span>UserSerializer</span>
<span>from</span> <span>django.contrib.auth</span> <span>import</span> <span>authenticate</span><span>,</span> <span>login</span>
<span>from</span> <span>rest_framework</span> <span>import</span> <span>status</span>
<span>from</span> <span>rest_framework.response</span> <span>import</span> <span>Response</span>
<span>from</span> <span>rest_framework.views</span> <span>import</span> <span>APIView</span>
<span>from</span> <span>rest_framework.authtoken.views</span> <span>import</span> <span>ObtainAuthToken</span>
<span>from</span> <span>rest_framework.authtoken.models</span> <span>import</span> <span>Token</span>
<span>from</span> <span>rest_framework.permissions</span> <span>import</span> <span>IsAuthenticated</span>
<span>class</span> <span>UserRegistrationView</span><span>(</span><span>APIView</span><span>):</span>
<span>def</span> <span>post</span><span>(</span><span>self</span><span>,</span> <span>request</span><span>):</span>
<span>serializer</span> <span>=</span> <span>UserSerializer</span><span>(</span><span>data</span><span>=</span><span>request</span><span>.</span><span>data</span><span>)</span>
<span>if</span> <span>serializer</span><span>.</span><span>is_valid</span><span>():</span>
<span>serializer</span><span>.</span><span>save</span><span>()</span>
<span>return</span> <span>Response</span><span>(</span><span>serializer</span><span>.</span><span>data</span><span>,</span> <span>status</span><span>=</span><span>status</span><span>.</span><span>HTTP_201_CREATED</span><span>)</span>
<span>return</span> <span>Response</span><span>(</span><span>serializer</span><span>.</span><span>errors</span><span>,</span> <span>status</span><span>=</span><span>status</span><span>.</span><span>HTTP_400_BAD_REQUEST</span><span>)</span>
<span>class</span> <span>UserLoginView</span><span>(</span><span>ObtainAuthToken</span><span>):</span>
<span>def</span> <span>post</span><span>(</span><span>self</span><span>,</span> <span>request</span><span>,</span> <span>*</span><span>args</span><span>,</span> <span>**</span><span>kwargs</span><span>):</span>
<span>username</span> <span>=</span> <span>request</span><span>.</span><span>data</span><span>.</span><span>get</span><span>(</span><span>'</span><span>username</span><span>'</span><span>)</span>
<span>password</span> <span>=</span> <span>request</span><span>.</span><span>data</span><span>.</span><span>get</span><span>(</span><span>'</span><span>password</span><span>'</span><span>)</span>
<span>user</span> <span>=</span> <span>authenticate</span><span>(</span><span>request</span><span>,</span> <span>username</span><span>=</span><span>username</span><span>,</span> <span>password</span><span>=</span><span>password</span><span>)</span>
<span>if</span> <span>user</span> <span>is</span> <span>not</span> <span>None</span><span>:</span>
<span>login</span><span>(</span><span>request</span><span>,</span> <span>user</span><span>)</span>
<span>token</span><span>,</span> <span>created</span> <span>=</span> <span>Token</span><span>.</span><span>objects</span><span>.</span><span>get_or_create</span><span>(</span><span>user</span><span>=</span><span>user</span><span>)</span>
<span>if</span> <span>created</span><span>:</span>
<span>token</span><span>.</span><span>delete</span><span>()</span> <span># Delete the token if it was already created </span> <span>token</span> <span>=</span> <span>Token</span><span>.</span><span>objects</span><span>.</span><span>create</span><span>(</span><span>user</span><span>=</span><span>user</span><span>)</span>
<span>return</span> <span>Response</span><span>({</span><span>'</span><span>token</span><span>'</span><span>:</span> <span>token</span><span>.</span><span>key</span><span>,</span> <span>'</span><span>username</span><span>'</span><span>:</span> <span>user</span><span>.</span><span>username</span><span>,</span> <span>'</span><span>role</span><span>'</span><span>:</span> <span>user</span><span>.</span><span>role</span><span>})</span>
<span>else</span><span>:</span>
<span>return</span> <span>Response</span><span>({</span><span>'</span><span>message</span><span>'</span><span>:</span> <span>'</span><span>Invalid username or password</span><span>'</span><span>},</span> <span>status</span><span>=</span><span>status</span><span>.</span><span>HTTP_401_UNAUTHORIZED</span><span>)</span>
<span>class</span> <span>UserLogoutView</span><span>(</span><span>APIView</span><span>):</span>
<span>permission_classes</span> <span>=</span> <span>[</span><span>IsAuthenticated</span><span>]</span>
<span>def</span> <span>post</span><span>(</span><span>self</span><span>,</span> <span>request</span><span>):</span>
<span>print</span><span>(</span><span>request</span><span>.</span><span>headers</span><span>)</span>
<span>token_key</span> <span>=</span> <span>request</span><span>.</span><span>auth</span><span>.</span><span>key</span>
<span>token</span> <span>=</span> <span>Token</span><span>.</span><span>objects</span><span>.</span><span>get</span><span>(</span><span>key</span><span>=</span><span>token_key</span><span>)</span>
<span>token</span><span>.</span><span>delete</span><span>()</span>
<span>return</span> <span>Response</span><span>({</span><span>'</span><span>detail</span><span>'</span><span>:</span> <span>'</span><span>Successfully logged out.</span><span>'</span><span>})</span>
<span># authentication/views.py </span>
<span>from</span> <span>authentication.models</span> <span>import</span> <span>User</span>
<span>from</span> <span>authentication.serializers</span> <span>import</span> <span>UserSerializer</span>
<span>from</span> <span>django.contrib.auth</span> <span>import</span> <span>authenticate</span><span>,</span> <span>login</span>
<span>from</span> <span>rest_framework</span> <span>import</span> <span>status</span>
<span>from</span> <span>rest_framework.response</span> <span>import</span> <span>Response</span>
<span>from</span> <span>rest_framework.views</span> <span>import</span> <span>APIView</span>
<span>from</span> <span>rest_framework.authtoken.views</span> <span>import</span> <span>ObtainAuthToken</span>
<span>from</span> <span>rest_framework.authtoken.models</span> <span>import</span> <span>Token</span>
<span>from</span> <span>rest_framework.permissions</span> <span>import</span> <span>IsAuthenticated</span>


<span>class</span> <span>UserRegistrationView</span><span>(</span><span>APIView</span><span>):</span>
    <span>def</span> <span>post</span><span>(</span><span>self</span><span>,</span> <span>request</span><span>):</span>
        <span>serializer</span> <span>=</span> <span>UserSerializer</span><span>(</span><span>data</span><span>=</span><span>request</span><span>.</span><span>data</span><span>)</span>
        <span>if</span> <span>serializer</span><span>.</span><span>is_valid</span><span>():</span>
            <span>serializer</span><span>.</span><span>save</span><span>()</span>
            <span>return</span> <span>Response</span><span>(</span><span>serializer</span><span>.</span><span>data</span><span>,</span> <span>status</span><span>=</span><span>status</span><span>.</span><span>HTTP_201_CREATED</span><span>)</span>
        <span>return</span> <span>Response</span><span>(</span><span>serializer</span><span>.</span><span>errors</span><span>,</span> <span>status</span><span>=</span><span>status</span><span>.</span><span>HTTP_400_BAD_REQUEST</span><span>)</span>



<span>class</span> <span>UserLoginView</span><span>(</span><span>ObtainAuthToken</span><span>):</span>
    <span>def</span> <span>post</span><span>(</span><span>self</span><span>,</span> <span>request</span><span>,</span> <span>*</span><span>args</span><span>,</span> <span>**</span><span>kwargs</span><span>):</span>
        <span>username</span> <span>=</span> <span>request</span><span>.</span><span>data</span><span>.</span><span>get</span><span>(</span><span>'</span><span>username</span><span>'</span><span>)</span>
        <span>password</span> <span>=</span> <span>request</span><span>.</span><span>data</span><span>.</span><span>get</span><span>(</span><span>'</span><span>password</span><span>'</span><span>)</span>

        <span>user</span> <span>=</span> <span>authenticate</span><span>(</span><span>request</span><span>,</span> <span>username</span><span>=</span><span>username</span><span>,</span> <span>password</span><span>=</span><span>password</span><span>)</span>
        <span>if</span> <span>user</span> <span>is</span> <span>not</span> <span>None</span><span>:</span>
            <span>login</span><span>(</span><span>request</span><span>,</span> <span>user</span><span>)</span>
            <span>token</span><span>,</span> <span>created</span> <span>=</span> <span>Token</span><span>.</span><span>objects</span><span>.</span><span>get_or_create</span><span>(</span><span>user</span><span>=</span><span>user</span><span>)</span>
            <span>if</span> <span>created</span><span>:</span>
                <span>token</span><span>.</span><span>delete</span><span>()</span>  <span># Delete the token if it was already created </span>                <span>token</span> <span>=</span> <span>Token</span><span>.</span><span>objects</span><span>.</span><span>create</span><span>(</span><span>user</span><span>=</span><span>user</span><span>)</span>
            <span>return</span> <span>Response</span><span>({</span><span>'</span><span>token</span><span>'</span><span>:</span> <span>token</span><span>.</span><span>key</span><span>,</span> <span>'</span><span>username</span><span>'</span><span>:</span> <span>user</span><span>.</span><span>username</span><span>,</span> <span>'</span><span>role</span><span>'</span><span>:</span> <span>user</span><span>.</span><span>role</span><span>})</span>
        <span>else</span><span>:</span>
            <span>return</span> <span>Response</span><span>({</span><span>'</span><span>message</span><span>'</span><span>:</span> <span>'</span><span>Invalid username or password</span><span>'</span><span>},</span> <span>status</span><span>=</span><span>status</span><span>.</span><span>HTTP_401_UNAUTHORIZED</span><span>)</span>



<span>class</span> <span>UserLogoutView</span><span>(</span><span>APIView</span><span>):</span>
    <span>permission_classes</span> <span>=</span> <span>[</span><span>IsAuthenticated</span><span>]</span>

    <span>def</span> <span>post</span><span>(</span><span>self</span><span>,</span> <span>request</span><span>):</span>
        <span>print</span><span>(</span><span>request</span><span>.</span><span>headers</span><span>)</span> 
        <span>token_key</span> <span>=</span> <span>request</span><span>.</span><span>auth</span><span>.</span><span>key</span>
        <span>token</span> <span>=</span> <span>Token</span><span>.</span><span>objects</span><span>.</span><span>get</span><span>(</span><span>key</span><span>=</span><span>token_key</span><span>)</span>
        <span>token</span><span>.</span><span>delete</span><span>()</span>

        <span>return</span> <span>Response</span><span>({</span><span>'</span><span>detail</span><span>'</span><span>:</span> <span>'</span><span>Successfully logged out.</span><span>'</span><span>})</span>
# authentication/views.py from authentication.models import User from authentication.serializers import UserSerializer from django.contrib.auth import authenticate, login from rest_framework import status from rest_framework.response import Response from rest_framework.views import APIView from rest_framework.authtoken.views import ObtainAuthToken from rest_framework.authtoken.models import Token from rest_framework.permissions import IsAuthenticated class UserRegistrationView(APIView): def post(self, request): 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 UserLoginView(ObtainAuthToken): def post(self, request, *args, **kwargs): username = request.data.get('username') password = request.data.get('password') user = authenticate(request, username=username, password=password) if user is not None: login(request, user) token, created = Token.objects.get_or_create(user=user) if created: token.delete() # Delete the token if it was already created token = Token.objects.create(user=user) return Response({'token': token.key, 'username': user.username, 'role': user.role}) else: return Response({'message': 'Invalid username or password'}, status=status.HTTP_401_UNAUTHORIZED) class UserLogoutView(APIView): permission_classes = [IsAuthenticated] def post(self, request): print(request.headers) token_key = request.auth.key token = Token.objects.get(key=token_key) token.delete() return Response({'detail': 'Successfully logged out.'})

Enter fullscreen mode Exit fullscreen mode

In the UserRegistrationView, we handle the HTTP POST request for user registration. We validate the data using the UserSerializer and save the user if it’s valid.

In the UserLoginView, we handle the user login functionality. We authenticate the user using the provided username and password, and if successful, generate a token using the Token model from DRF. We return the token along with the username and role in the response.

The UserLogoutView is responsible for logging out the authenticated user. It retrieves the token from the request’s authentication header, deletes the token if it exists, and returns a success message.

Updating URLs:

Finally, we need to define the URLs for our authentication app.

In multi_role_auth/urls.py, add the following code:

<span># multi_role_auth/urls.py </span>
<span>from</span> <span>django.urls</span> <span>import</span> <span>path</span><span>,</span> <span>include</span>
<span>from</span> <span>authentication.views</span> <span>import</span> <span>UserRegistrationView</span><span>,</span> <span>UserLoginView</span><span>,</span> <span>UserLogoutView</span>
<span>urlpatterns</span> <span>=</span> <span>[</span>
<span>path</span><span>(</span><span>'</span><span>api/auth/register/</span><span>'</span><span>,</span> <span>UserRegistrationView</span><span>.</span><span>as_view</span><span>(),</span> <span>name</span><span>=</span><span>'</span><span>user-registration</span><span>'</span><span>),</span>
<span>path</span><span>(</span><span>'</span><span>api/auth/login/</span><span>'</span><span>,</span> <span>UserLoginView</span><span>.</span><span>as_view</span><span>(),</span> <span>name</span><span>=</span><span>'</span><span>user-login</span><span>'</span><span>),</span>
<span>path</span><span>(</span><span>'</span><span>api/auth/logout/</span><span>'</span><span>,</span> <span>UserLogoutView</span><span>.</span><span>as_view</span><span>(),</span> <span>name</span><span>=</span><span>'</span><span>user-logout</span><span>'</span><span>),</span>
<span># Add other URLs here </span><span>]</span>
<span># multi_role_auth/urls.py </span>
<span>from</span> <span>django.urls</span> <span>import</span> <span>path</span><span>,</span> <span>include</span>
<span>from</span> <span>authentication.views</span> <span>import</span> <span>UserRegistrationView</span><span>,</span> <span>UserLoginView</span><span>,</span> <span>UserLogoutView</span>

<span>urlpatterns</span> <span>=</span> <span>[</span>
    <span>path</span><span>(</span><span>'</span><span>api/auth/register/</span><span>'</span><span>,</span> <span>UserRegistrationView</span><span>.</span><span>as_view</span><span>(),</span> <span>name</span><span>=</span><span>'</span><span>user-registration</span><span>'</span><span>),</span>
    <span>path</span><span>(</span><span>'</span><span>api/auth/login/</span><span>'</span><span>,</span> <span>UserLoginView</span><span>.</span><span>as_view</span><span>(),</span> <span>name</span><span>=</span><span>'</span><span>user-login</span><span>'</span><span>),</span>
    <span>path</span><span>(</span><span>'</span><span>api/auth/logout/</span><span>'</span><span>,</span> <span>UserLogoutView</span><span>.</span><span>as_view</span><span>(),</span> <span>name</span><span>=</span><span>'</span><span>user-logout</span><span>'</span><span>),</span>
    <span># Add other URLs here </span><span>]</span>
# multi_role_auth/urls.py from django.urls import path, include from authentication.views import UserRegistrationView, UserLoginView, UserLogoutView urlpatterns = [ path('api/auth/register/', UserRegistrationView.as_view(), name='user-registration'), path('api/auth/login/', UserLoginView.as_view(), name='user-login'), path('api/auth/logout/', UserLogoutView.as_view(), name='user-logout'), # Add other URLs here ]

Enter fullscreen mode Exit fullscreen mode

Here, we map
/api/auth/register/ URL to the UserRegistrationView,
/api/auth/login/ URL to the UserLoginView, and
api/auth/logout/ URL to the UserLogoutView.

Modifying settings.py:

To enable token-based authentication in DRF, we need to make some modifications to the settings.py file.

In “multi_role_auth/settings.py”, add or update the following settings:

<span># multi_role_auth/settings.py </span>
<span># ... </span>
<span>INSTALLED_APPS</span> <span>=</span> <span>[</span>
<span># ... </span> <span>'</span><span>rest_framework</span><span>'</span><span>,</span>
<span>'</span><span>rest_framework.authtoken</span><span>'</span><span>,</span>
<span>'</span><span>authentication</span><span>'</span><span>,</span>
<span>]</span>
<span># ... </span>
<span>AUTH_USER_MODEL</span> <span>=</span> <span>'</span><span>authentication.User</span><span>'</span>
<span>REST_FRAMEWORK</span> <span>=</span> <span>{</span>
<span>'</span><span>DEFAULT_AUTHENTICATION_CLASSES</span><span>'</span><span>:</span> <span>[</span>
<span>'</span><span>rest_framework.authentication.TokenAuthentication</span><span>'</span><span>,</span>
<span>],</span>
<span>}</span>
<span># multi_role_auth/settings.py </span>
<span># ... </span>
<span>INSTALLED_APPS</span> <span>=</span> <span>[</span>
    <span># ... </span>    <span>'</span><span>rest_framework</span><span>'</span><span>,</span>
    <span>'</span><span>rest_framework.authtoken</span><span>'</span><span>,</span>
    <span>'</span><span>authentication</span><span>'</span><span>,</span>
<span>]</span>

<span># ... </span>
<span>AUTH_USER_MODEL</span> <span>=</span> <span>'</span><span>authentication.User</span><span>'</span>

<span>REST_FRAMEWORK</span> <span>=</span> <span>{</span>
    <span>'</span><span>DEFAULT_AUTHENTICATION_CLASSES</span><span>'</span><span>:</span> <span>[</span>
        <span>'</span><span>rest_framework.authentication.TokenAuthentication</span><span>'</span><span>,</span>
    <span>],</span>
<span>}</span>
# multi_role_auth/settings.py # ... INSTALLED_APPS = [ # ... 'rest_framework', 'rest_framework.authtoken', 'authentication', ] # ... AUTH_USER_MODEL = 'authentication.User' REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ 'rest_framework.authentication.TokenAuthentication', ], }

Enter fullscreen mode Exit fullscreen mode

Here, we’ve added rest_framework and authentication to the INSTALLED_APPS list to include the necessary packages. Additionally, we’ve configured the DEFAULT_AUTHENTICATION_CLASSES to use the TokenAuthentication class for token-based authentication.

Remember to run migrations before testing the app:

python manage.py makemigrations
python manage.py migrate
python manage.py makemigrations
python manage.py migrate
python manage.py makemigrations python manage.py migrate

Enter fullscreen mode Exit fullscreen mode

Test:

Here I tested authentication endpoints using Postman

Register a user:
Send a POST request to http://localhost:8000/api/auth/register/ with the following payload in the request body:

{
"username": "johndoe",
"email": "johndoe@example.com",
"password": "$tr0ngPa$$w0rd",
"role": "student"
}
{
  "username": "johndoe",
  "email": "johndoe@example.com",
  "password": "$tr0ngPa$$w0rd",
  "role": "student"
}
{ "username": "johndoe", "email": "johndoe@example.com", "password": "$tr0ngPa$$w0rd", "role": "student" }

Enter fullscreen mode Exit fullscreen mode

Login:
Send a POST request to http://localhost:8000/api/auth/login/ with the following payload in the request body:

{
"username": "johndoe",
"password": "$tr0ngPa$$w0rd"
}
{
  "username": "johndoe",
  "password": "$tr0ngPa$$w0rd"
}
{ "username": "johndoe", "password": "$tr0ngPa$$w0rd" }

Enter fullscreen mode Exit fullscreen mode

Logout:
Send a POST request to http://localhost:8000/api/auth/logout/ with the token in the request headers. Include an Authorization header with the value Token {token} (replace {token} with the actual token value obtained during login).

Use it in user-specific class(es)

Now that we have a User with the role field, we can use it in our user-specific classes. For example:

<span># student/models.py </span>
<span>from</span> <span>django.db</span> <span>import</span> <span>models</span>
<span>from</span> <span>authentication.models</span> <span>import</span> <span>User</span>
<span>class</span> <span>Student</span><span>(</span><span>models</span><span>.</span><span>Model</span><span>):</span>
<span># other fields related to student ... </span> <span>student_id</span> <span>=</span> <span>models</span><span>.</span><span>CharField</span><span>(</span><span>max_length</span><span>=</span><span>10</span><span>,</span> <span>unique</span><span>=</span><span>True</span><span>)</span>
<span>user</span> <span>=</span> <span>models</span><span>.</span><span>OneToOneField</span><span>(</span><span>User</span><span>,</span> <span>on_delete</span><span>=</span><span>models</span><span>.</span><span>CASCADE</span><span>,</span> <span>related_name</span><span>=</span><span>"</span><span>student_account</span><span>"</span><span>)</span>
<span># student/models.py </span>
<span>from</span> <span>django.db</span> <span>import</span> <span>models</span>
<span>from</span> <span>authentication.models</span> <span>import</span> <span>User</span>

<span>class</span> <span>Student</span><span>(</span><span>models</span><span>.</span><span>Model</span><span>):</span>
    <span># other fields related to student ... </span>    <span>student_id</span> <span>=</span> <span>models</span><span>.</span><span>CharField</span><span>(</span><span>max_length</span><span>=</span><span>10</span><span>,</span> <span>unique</span><span>=</span><span>True</span><span>)</span>
    <span>user</span> <span>=</span> <span>models</span><span>.</span><span>OneToOneField</span><span>(</span><span>User</span><span>,</span> <span>on_delete</span><span>=</span><span>models</span><span>.</span><span>CASCADE</span><span>,</span> <span>related_name</span><span>=</span><span>"</span><span>student_account</span><span>"</span><span>)</span>
# student/models.py from django.db import models from authentication.models import User class Student(models.Model): # other fields related to student ... student_id = models.CharField(max_length=10, unique=True) user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="student_account")

Enter fullscreen mode Exit fullscreen mode

<span># student/serializers.py # import ... </span><span>class</span> <span>StudentSerializer</span><span>(</span><span>serializers</span><span>.</span><span>ModelSerializer</span><span>):</span>
<span>user</span> <span>=</span> <span>UserSerializer</span><span>(</span><span>read_only</span><span>=</span><span>True</span><span>)</span>
<span>class</span> <span>Meta</span><span>:</span>
<span>model</span> <span>=</span> <span>Student</span>
<span>fields</span> <span>=</span> <span>'</span><span>__all__</span><span>'</span>
<span>def</span> <span>create</span><span>(</span><span>self</span><span>,</span> <span>validated_data</span><span>):</span>
<span>user_data</span> <span>=</span> <span>validated_data</span><span>.</span><span>pop</span><span>(</span><span>'</span><span>user</span><span>'</span><span>)</span>
<span>user</span> <span>=</span> <span>User</span><span>.</span><span>objects</span><span>.</span><span>create_user</span><span>(</span><span>**</span><span>user_data</span><span>)</span>
<span>student</span> <span>=</span> <span>Student</span><span>.</span><span>objects</span><span>.</span><span>create</span><span>(</span><span>user</span><span>=</span><span>user</span><span>,</span> <span>**</span><span>validated_data</span><span>)</span>
<span>return</span> <span>student</span>
<span># student/serializers.py # import ... </span><span>class</span> <span>StudentSerializer</span><span>(</span><span>serializers</span><span>.</span><span>ModelSerializer</span><span>):</span>
    <span>user</span> <span>=</span> <span>UserSerializer</span><span>(</span><span>read_only</span><span>=</span><span>True</span><span>)</span>

    <span>class</span> <span>Meta</span><span>:</span>
        <span>model</span> <span>=</span> <span>Student</span>
        <span>fields</span> <span>=</span> <span>'</span><span>__all__</span><span>'</span>

    <span>def</span> <span>create</span><span>(</span><span>self</span><span>,</span> <span>validated_data</span><span>):</span>
        <span>user_data</span> <span>=</span> <span>validated_data</span><span>.</span><span>pop</span><span>(</span><span>'</span><span>user</span><span>'</span><span>)</span>
        <span>user</span> <span>=</span> <span>User</span><span>.</span><span>objects</span><span>.</span><span>create_user</span><span>(</span><span>**</span><span>user_data</span><span>)</span>
        <span>student</span> <span>=</span> <span>Student</span><span>.</span><span>objects</span><span>.</span><span>create</span><span>(</span><span>user</span><span>=</span><span>user</span><span>,</span> <span>**</span><span>validated_data</span><span>)</span>
        <span>return</span> <span>student</span>
# student/serializers.py # import ... class StudentSerializer(serializers.ModelSerializer): user = UserSerializer(read_only=True) class Meta: model = Student fields = '__all__' def create(self, validated_data): user_data = validated_data.pop('user') user = User.objects.create_user(**user_data) student = Student.objects.create(user=user, **validated_data) return student

Enter fullscreen mode Exit fullscreen mode

Update views and URLs:

<span># ... </span><span>class</span> <span>StudentRegistrationView</span><span>(</span><span>APIView</span><span>):</span>
<span>def</span> <span>post</span><span>(</span><span>self</span><span>,</span> <span>request</span><span>):</span>
<span>serializer</span> <span>=</span> <span>StudentSerializer</span><span>(</span><span>data</span><span>=</span><span>request</span><span>.</span><span>data</span><span>)</span>
<span>if</span> <span>serializer</span><span>.</span><span>is_valid</span><span>():</span>
<span>serializer</span><span>.</span><span>save</span><span>()</span>
<span>return</span> <span>Response</span><span>(</span><span>serializer</span><span>.</span><span>data</span><span>,</span> <span>status</span><span>=</span><span>status</span><span>.</span><span>HTTP_201_CREATED</span><span>)</span>
<span>return</span> <span>Response</span><span>(</span><span>serializer</span><span>.</span><span>errors</span><span>,</span> <span>status</span><span>=</span><span>status</span><span>.</span><span>HTTP_400_BAD_REQUEST</span><span>)</span>
<span># Update Login view </span><span>class</span> <span>UserLoginView</span><span>(</span><span>ObtainAuthToken</span><span>):</span>
<span>def</span> <span>post</span><span>(</span><span>self</span><span>,</span> <span>request</span><span>,</span> <span>*</span><span>args</span><span>,</span> <span>**</span><span>kwargs</span><span>):</span>
<span>username</span> <span>=</span> <span>request</span><span>.</span><span>data</span><span>.</span><span>get</span><span>(</span><span>'</span><span>username</span><span>'</span><span>)</span>
<span>password</span> <span>=</span> <span>request</span><span>.</span><span>data</span><span>.</span><span>get</span><span>(</span><span>'</span><span>password</span><span>'</span><span>)</span>
<span>user</span> <span>=</span> <span>authenticate</span><span>(</span><span>request</span><span>,</span> <span>username</span><span>=</span><span>username</span><span>,</span> <span>password</span><span>=</span><span>password</span><span>)</span>
<span>if</span> <span>user</span> <span>is</span> <span>not</span> <span>None</span><span>:</span>
<span>login</span><span>(</span><span>request</span><span>,</span> <span>user</span><span>)</span>
<span>token</span><span>,</span> <span>created</span> <span>=</span> <span>Token</span><span>.</span><span>objects</span><span>.</span><span>get_or_create</span><span>(</span><span>user</span><span>=</span><span>user</span><span>)</span>
<span>if</span> <span>created</span><span>:</span>
<span>token</span><span>.</span><span>delete</span><span>()</span> <span># Delete the token if it was already created </span> <span>token</span> <span>=</span> <span>Token</span><span>.</span><span>objects</span><span>.</span><span>create</span><span>(</span><span>user</span><span>=</span><span>user</span><span>)</span>
<span>response_data</span> <span>=</span> <span>{</span>
<span>'</span><span>token</span><span>'</span><span>:</span> <span>token</span><span>.</span><span>key</span><span>,</span>
<span>'</span><span>username</span><span>'</span><span>:</span> <span>user</span><span>.</span><span>username</span><span>,</span>
<span>'</span><span>role</span><span>'</span><span>:</span> <span>user</span><span>.</span><span>role</span><span>,</span>
<span>}</span>
<span>if</span> <span>user</span><span>.</span><span>role</span> <span>==</span> <span>'</span><span>student</span><span>'</span><span>:</span>
<span>student</span> <span>=</span> <span>user</span><span>.</span><span>student_account</span> <span># Assuming the related name is "student_account" </span> <span>if</span> <span>student</span> <span>is</span> <span>not</span> <span>None</span><span>:</span>
<span># Add student data to the response data </span> <span>student_data</span> <span>=</span> <span>StudentSerializer</span><span>(</span><span>student</span><span>).</span><span>data</span>
<span>response_data</span><span>[</span><span>'</span><span>data</span><span>'</span><span>]</span> <span>=</span> <span>student_data</span>
<span>return</span> <span>Response</span><span>(</span><span>response_data</span><span>)</span>
<span>else</span><span>:</span>
<span>return</span> <span>Response</span><span>({</span><span>'</span><span>message</span><span>'</span><span>:</span> <span>'</span><span>Invalid username or password</span><span>'</span><span>},</span> <span>status</span><span>=</span><span>status</span><span>.</span><span>HTTP_401_UNAUTHORIZED</span><span>)</span>
<span># ... </span><span>class</span> <span>StudentRegistrationView</span><span>(</span><span>APIView</span><span>):</span>
    <span>def</span> <span>post</span><span>(</span><span>self</span><span>,</span> <span>request</span><span>):</span>
        <span>serializer</span> <span>=</span> <span>StudentSerializer</span><span>(</span><span>data</span><span>=</span><span>request</span><span>.</span><span>data</span><span>)</span>
        <span>if</span> <span>serializer</span><span>.</span><span>is_valid</span><span>():</span>
            <span>serializer</span><span>.</span><span>save</span><span>()</span>
            <span>return</span> <span>Response</span><span>(</span><span>serializer</span><span>.</span><span>data</span><span>,</span> <span>status</span><span>=</span><span>status</span><span>.</span><span>HTTP_201_CREATED</span><span>)</span>
        <span>return</span> <span>Response</span><span>(</span><span>serializer</span><span>.</span><span>errors</span><span>,</span> <span>status</span><span>=</span><span>status</span><span>.</span><span>HTTP_400_BAD_REQUEST</span><span>)</span>

<span># Update Login view </span><span>class</span> <span>UserLoginView</span><span>(</span><span>ObtainAuthToken</span><span>):</span>
    <span>def</span> <span>post</span><span>(</span><span>self</span><span>,</span> <span>request</span><span>,</span> <span>*</span><span>args</span><span>,</span> <span>**</span><span>kwargs</span><span>):</span>
        <span>username</span> <span>=</span> <span>request</span><span>.</span><span>data</span><span>.</span><span>get</span><span>(</span><span>'</span><span>username</span><span>'</span><span>)</span>
        <span>password</span> <span>=</span> <span>request</span><span>.</span><span>data</span><span>.</span><span>get</span><span>(</span><span>'</span><span>password</span><span>'</span><span>)</span>

        <span>user</span> <span>=</span> <span>authenticate</span><span>(</span><span>request</span><span>,</span> <span>username</span><span>=</span><span>username</span><span>,</span> <span>password</span><span>=</span><span>password</span><span>)</span>
        <span>if</span> <span>user</span> <span>is</span> <span>not</span> <span>None</span><span>:</span>
            <span>login</span><span>(</span><span>request</span><span>,</span> <span>user</span><span>)</span>
            <span>token</span><span>,</span> <span>created</span> <span>=</span> <span>Token</span><span>.</span><span>objects</span><span>.</span><span>get_or_create</span><span>(</span><span>user</span><span>=</span><span>user</span><span>)</span>
            <span>if</span> <span>created</span><span>:</span>
                <span>token</span><span>.</span><span>delete</span><span>()</span>  <span># Delete the token if it was already created </span>                <span>token</span> <span>=</span> <span>Token</span><span>.</span><span>objects</span><span>.</span><span>create</span><span>(</span><span>user</span><span>=</span><span>user</span><span>)</span>

            <span>response_data</span> <span>=</span> <span>{</span>
                <span>'</span><span>token</span><span>'</span><span>:</span> <span>token</span><span>.</span><span>key</span><span>,</span>
                <span>'</span><span>username</span><span>'</span><span>:</span> <span>user</span><span>.</span><span>username</span><span>,</span>
                <span>'</span><span>role</span><span>'</span><span>:</span> <span>user</span><span>.</span><span>role</span><span>,</span>
            <span>}</span>

            <span>if</span> <span>user</span><span>.</span><span>role</span> <span>==</span> <span>'</span><span>student</span><span>'</span><span>:</span>
                <span>student</span> <span>=</span> <span>user</span><span>.</span><span>student_account</span>  <span># Assuming the related name is "student_account" </span>                <span>if</span> <span>student</span> <span>is</span> <span>not</span> <span>None</span><span>:</span>
                    <span># Add student data to the response data </span>                    <span>student_data</span> <span>=</span> <span>StudentSerializer</span><span>(</span><span>student</span><span>).</span><span>data</span>
                    <span>response_data</span><span>[</span><span>'</span><span>data</span><span>'</span><span>]</span> <span>=</span> <span>student_data</span>

            <span>return</span> <span>Response</span><span>(</span><span>response_data</span><span>)</span>
        <span>else</span><span>:</span>
            <span>return</span> <span>Response</span><span>({</span><span>'</span><span>message</span><span>'</span><span>:</span> <span>'</span><span>Invalid username or password</span><span>'</span><span>},</span> <span>status</span><span>=</span><span>status</span><span>.</span><span>HTTP_401_UNAUTHORIZED</span><span>)</span>
# ... class StudentRegistrationView(APIView): def post(self, request): serializer = StudentSerializer(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) # Update Login view class UserLoginView(ObtainAuthToken): def post(self, request, *args, **kwargs): username = request.data.get('username') password = request.data.get('password') user = authenticate(request, username=username, password=password) if user is not None: login(request, user) token, created = Token.objects.get_or_create(user=user) if created: token.delete() # Delete the token if it was already created token = Token.objects.create(user=user) response_data = { 'token': token.key, 'username': user.username, 'role': user.role, } if user.role == 'student': student = user.student_account # Assuming the related name is "student_account" if student is not None: # Add student data to the response data student_data = StudentSerializer(student).data response_data['data'] = student_data return Response(response_data) else: return Response({'message': 'Invalid username or password'}, status=status.HTTP_401_UNAUTHORIZED)

Enter fullscreen mode Exit fullscreen mode

<span># ... </span><span>urlpatterns</span> <span>=</span> <span>[</span>
<span># ... </span> <span>path</span><span>(</span><span>'</span><span>api/auth/register/student/</span><span>'</span><span>,</span> <span>StudentRegistrationView</span><span>.</span><span>as_view</span><span>(),</span> <span>name</span><span>=</span><span>'</span><span>student-registration</span><span>'</span><span>),</span>
<span># ... </span><span>]</span>
<span># ... </span><span>urlpatterns</span> <span>=</span> <span>[</span>
    <span># ... </span>    <span>path</span><span>(</span><span>'</span><span>api/auth/register/student/</span><span>'</span><span>,</span> <span>StudentRegistrationView</span><span>.</span><span>as_view</span><span>(),</span> <span>name</span><span>=</span><span>'</span><span>student-registration</span><span>'</span><span>),</span>
    <span># ... </span><span>]</span>
# ... urlpatterns = [ # ... path('api/auth/register/student/', StudentRegistrationView.as_view(), name='student-registration'), # ... ]

Enter fullscreen mode Exit fullscreen mode

In case you created a new app for student, add it to INSTALLED_APPS.
Run migrations.

Here is a JSON data to register a Student:

<span>{</span><span> </span><span>"student_id"</span><span>:</span><span> </span><span>"1234567890"</span><span>,</span><span> </span><span>"user"</span><span>:</span><span> </span><span>{</span><span> </span><span>"username"</span><span>:</span><span> </span><span>"john_doe@stu"</span><span>,</span><span> </span><span>"email"</span><span>:</span><span> </span><span>"john.doe@test.com"</span><span>,</span><span> </span><span>"role"</span><span>:</span><span> </span><span>"student"</span><span>,</span><span> </span><span>"password"</span><span>:</span><span> </span><span>"secretpassword"</span><span> </span><span>}</span><span> </span><span>}</span><span> </span>
<span>{</span><span> </span><span>"student_id"</span><span>:</span><span> </span><span>"1234567890"</span><span>,</span><span> </span><span>"user"</span><span>:</span><span> </span><span>{</span><span> </span><span>"username"</span><span>:</span><span> </span><span>"john_doe@stu"</span><span>,</span><span> </span><span>"email"</span><span>:</span><span> </span><span>"john.doe@test.com"</span><span>,</span><span> </span><span>"role"</span><span>:</span><span> </span><span>"student"</span><span>,</span><span> </span><span>"password"</span><span>:</span><span> </span><span>"secretpassword"</span><span> </span><span>}</span><span> </span><span>}</span><span> </span>
{ "student_id": "1234567890", "user": { "username": "john_doe@stu", "email": "john.doe@test.com", "role": "student", "password": "secretpassword" } }

Enter fullscreen mode Exit fullscreen mode

And this is the login response:

<span>{</span><span> </span><span>"token"</span><span>:</span><span> </span><span>"bc2369f6cf4c7bf015c449773dc285e9e8c69caf"</span><span>,</span><span> </span><span>"username"</span><span>:</span><span> </span><span>"john_doe@stu"</span><span>,</span><span> </span><span>"role"</span><span>:</span><span> </span><span>"student"</span><span>,</span><span> </span><span>"data"</span><span>:</span><span> </span><span>{</span><span> </span><span>"id"</span><span>:</span><span> </span><span>1</span><span>,</span><span> </span><span>"user"</span><span>:</span><span> </span><span>{</span><span> </span><span>"username"</span><span>:</span><span> </span><span>"john_doe@stu"</span><span>,</span><span> </span><span>"email"</span><span>:</span><span> </span><span>"john.doe@test.com"</span><span>,</span><span> </span><span>"role"</span><span>:</span><span> </span><span>"student"</span><span> </span><span>},</span><span> </span><span>"student_id"</span><span>:</span><span> </span><span>"1234567890"</span><span> </span><span>}</span><span> </span><span>}</span><span> </span>
<span>{</span><span> </span><span>"token"</span><span>:</span><span> </span><span>"bc2369f6cf4c7bf015c449773dc285e9e8c69caf"</span><span>,</span><span> </span><span>"username"</span><span>:</span><span> </span><span>"john_doe@stu"</span><span>,</span><span> </span><span>"role"</span><span>:</span><span> </span><span>"student"</span><span>,</span><span> </span><span>"data"</span><span>:</span><span> </span><span>{</span><span> </span><span>"id"</span><span>:</span><span> </span><span>1</span><span>,</span><span> </span><span>"user"</span><span>:</span><span> </span><span>{</span><span> </span><span>"username"</span><span>:</span><span> </span><span>"john_doe@stu"</span><span>,</span><span> </span><span>"email"</span><span>:</span><span> </span><span>"john.doe@test.com"</span><span>,</span><span> </span><span>"role"</span><span>:</span><span> </span><span>"student"</span><span> </span><span>},</span><span> </span><span>"student_id"</span><span>:</span><span> </span><span>"1234567890"</span><span> </span><span>}</span><span> </span><span>}</span><span> </span>
{ "token": "bc2369f6cf4c7bf015c449773dc285e9e8c69caf", "username": "john_doe@stu", "role": "student", "data": { "id": 1, "user": { "username": "john_doe@stu", "email": "john.doe@test.com", "role": "student" }, "student_id": "1234567890" } }

Enter fullscreen mode Exit fullscreen mode

Conclusion:

In this tutorial, we’ve covered the process of implementing multi-role user authentication using Django Rest Framework. We defined a custom user model, created serializers for user registration and login, implemented views for user registration, login, and logout, and updated the project’s URLs and settings to support token-based authentication. Furthermore, we have explored the detailed process of utilizing our customized User model in other specific models.

By extending Django’s built-in user model and utilizing the capabilities of Django Rest Framework, you can easily add role-based authentication to your Django applications. This allows you to differentiate user permissions and provide tailored experiences based on each user’s role.

Feel free to explore further and add additional functionalities, such as password reset and role-based access control (RBAC), based on your application requirements.

Happy coding!

原文链接:Multi-Role User Authentication in Django Rest Framework

© 版权声明
THE END
喜欢就支持一下吧
点赞15 分享
Behind every beautiful life, there has been some kind of pain.
破茧成蝶的美好生活都有伤痛
评论 抢沙发

请登录后发表评论

    暂无评论内容