DRF Authentication and Permissions
When you build APIs with Django REST Framework (DRF), you need a way to check who the user is (authentication) and what they are allowed to do (permissions). Without this, anyone could read or change your data. DRF gives you a flexible system to handle both of these things. In this section, we will look at different ways to authenticate users and control access using permission classes, and how to use them in your API views.
Authentication
Section titled “Authentication”Authentication means checking “who is this user?” You can build this yourself by writing custom authentication classes, but most people use a library called djoser instead. Djoser is a popular package that already comes with ready-made views for user registration, login, logout, password reset, and more. It works well with DRF and saves you a lot of time, so you don’t have to write all this code from scratch.
Official documentation: https://djoser.readthedocs.io/en/latest/
Setup of Djoser
Section titled “Setup of Djoser”-
Install Djoser:
Terminal window uv add djoser djangorestframework-simplejwt -
Add Djoser to your
INSTALLED_APPSinsettings.py:INSTALLED_APPS = [# other apps'djoser','rest_framework_simplejwt',] -
Include Djoser’s URLs in your project’s
urls.py:from django.urls import path, includeurlpatterns = [# other urlspath('auth/', include('djoser.urls')),path('auth/', include('djoser.urls.jwt')), # for JWT authentication] -
Tell DRF to use JWT authentication in
settings.py:REST_FRAMEWORK = {'DEFAULT_AUTHENTICATION_CLASSES': ('rest_framework_simplejwt.authentication.JWTAuthentication',),} -
Set up Simple JWT settings in
settings.py:djangorestframework-simplejwtdocs: https://django-rest-framework-simplejwt.readthedocs.io/en/latest/settings.html
You can change how long tokens last, whether they rotate, and other settings. Here’s an example:
from datetime import timedeltaSIMPLE_JWT = {'AUTH_HEADER_TYPES': ('JWT',),"ACCESS_TOKEN_LIFETIME": timedelta(minutes=5),"REFRESH_TOKEN_LIFETIME": timedelta(days=1),"ROTATE_REFRESH_TOKENS": False,"BLACKLIST_AFTER_ROTATION": False,"UPDATE_LAST_LOGIN": False,} -
Run migrations to create the database tables you need:
Terminal window uv run python manage.py makemigrationsuv run python manage.py migrate
Custom Serializers for Djoser
Section titled “Custom Serializers for Djoser”Djoser already gives you default serializers for things like user registration and login. But you can change these serializers to add more fields or change how authentication behaves. For example, you can make a custom serializer for registration that also asks for first_name and last_name:
# serializers.pyfrom djoser.serializers import UserCreateSerializer as BaseUserCreateSerializer, UserSerializer as BaseUserSerializer
class UserCreateSerializer(BaseUserCreateSerializer): class Meta(BaseUserCreateSerializer.Meta): fields = BaseUserCreateSerializer.Meta.fields + ('first_name', 'last_name')
class UserSerializer(BaseUserSerializer): class Meta(BaseUserSerializer.Meta): fields = BaseUserSerializer.Meta.fields + ('first_name', 'last_name')Now tell Djoser to use your custom serializer in settings.py:
Docs: https://djoser.readthedocs.io/en/latest/settings.html#serializers
DJOSER = { 'SERIALIZERS': { 'user_create': 'your_app.serializers.UserCreateSerializer', 'current_user': 'your_app.serializers.UserSerializer', },}Example of User and Profile Models
# models.pyfrom django.db import modelsfrom django.contrib.auth.models import AbstractUser
class User(AbstractUser): # You can add additional fields here if neededpass
class Profile(models.Model):user = models.OneToOneField(User, on_delete=models.CASCADE)dob = models.DateField(null=True, blank=True)bio = models.TextField(null=True, blank=True)
def __str__(self): return f"{self.user.username}'s Profile"# serializers.pyfrom rest_framework import serializersfrom .models import User, Profile
# user serializer is created by djoser, we will create a profile serializer
class ProfileSerializer(serializers.ModelSerializer): class Meta: model = Profile fields = ['dob', 'bio'] # we dont need to include the user field here because # we will set it in the view using the request.user # and this api is only for the authenticated user
def create(self, validated_data): user = self.context['request'].user profile = Profile.objects.create(user=user, **validated_data) return profile# views.pyfrom rest_framework import viewsetsfrom .models import Profilefrom .serializers import ProfileSerializerfrom rest_framework.permissions import IsAuthenticated
class ProfileViewSet(viewsets.ModelViewSet): queryset = Profile.objects.all() serializer_class = ProfileSerializer permission_classes = [IsAuthenticated]
def get_queryset(self): # return only the profile of the authenticated user return self.queryset.filter(user=self.request.user)
def get_serializer_context(self): context = super().get_serializer_context() context['request'] = self.request return contextJWT (JSON Web Token) is a small, safe-to-put-in-a-URL piece of text that carries information between two sides - usually between your app (client) and your server. It is widely used for login and access control in web apps. A JWT has three parts: a header, a payload, and a signature.
- The header tells you what type of token it is and which method was used to sign it.
- The payload holds the actual information (called “claims”), usually about the user, plus any extra data you want to include.
- The signature proves that nobody changed the token after it was created.
Here’s how it works in simple terms: when a user logs in, the server creates a JWT that holds the user’s info and sends it back to them. The client then attaches this token to every future request, inside the Authorization header. The server checks the token’s signature, reads the user’s info from it, and decides if the request should be allowed.
If you want to see what’s actually inside a JWT, you can use a free online tool like jwt.io to break it down and look at the contents.
JWT Debugger lets you paste a JWT token and see its header, payload, and signature in a format that’s easy to read. This is a great way to understand what data is being stored inside the token and how it’s put together.
Djoser API Endpoints
Section titled “Djoser API Endpoints”Djoser gives you a ready-made set of API endpoints (URLs) for handling users - things like signing up, logging in, and managing accounts.
Docs: https://djoser.readthedocs.io/en/latest/getting_started.html#available-endpoints
/users/(POST for registration, GET for listing users)/users/me/(GET for current user details, PUT/PATCH for updating current user)/users/resend_activation/(POST for resending activation email)/users/set_password/(POST for setting password)/users/reset_password/(POST for resetting password)/users/reset_password_confirm/(POST for confirming password reset)/users/set_username/(POST for setting username)/users/reset_username/(POST for resetting username)/users/reset_username_confirm/(POST for confirming username reset)/token/login/(Token Based Authentication)/token/logout/(Token Based Authentication)/jwt/create/(JSON Web Token Authentication)/jwt/refresh/(JSON Web Token Authentication)/jwt/verify/(JSON Web Token Authentication)
Dojser Default API Endpoints in Detail
-
POST
/api/auth/jwt/create/Description: Log in and get an access token plus a refresh token
-
Request
{"email": "user@example.com","password": "password"} -
Response
{"access": "access_token","refresh": "refresh_token"} -
Access
- Public
-
-
POST
/api/auth/jwt/refresh/Description: Get a new access token
-
Request
{"refresh": "refresh_token"} -
Response
{"access": "new_access_token"} -
Access
- Public (you need a valid refresh token)
-
-
POST
/api/auth/jwt/verify/Description: Check if a token is still valid
-
Request
{"token": "access_token"} -
Response
- 200 OK (valid)
- 401 Unauthorized (invalid)
-
Access
- Public
-
-
GET
/api/auth/users/Description: List all users
-
Response
[{"id": 1,"email": "user@example.com"}] -
Access
- Admin only (you must restrict this yourself)
-
-
POST
/api/auth/users/Description: Sign up a new user
-
Request
{"email": "user@example.com","password": "password"} -
Response
{"id": 1,"email": "user@example.com"} -
Access
- Public
-
-
GET
/api/auth/users/{id}/Description: Get a user by their ID
-
Response
{"id": 1,"email": "user@example.com"} -
Access
- Admin or the user themself (Owner)
-
-
PUT
/api/auth/users/{id}/Description: Fully update a user
-
Request
{"email": "new@example.com"} -
Access
- Admin or the user themself (Owner)
-
-
PATCH
/api/auth/users/{id}/Description: Partly update a user (only some fields)
-
Request
{"email": "updated@example.com"} -
Access
- Admin or the user themself (Owner)
-
-
DELETE
/api/auth/users/{id}/Description: Delete a user
-
Response
- 204 No Content
-
Access
- Admin or the user themself (Owner)
-
-
POST
/api/auth/users/activation/Description: Activate an account using a token sent by email
-
Request
{"uid": "encoded_user_id","token": "activation_token"} -
Response
- 204 No Content
-
Access
- Public (you just need a valid token)
-
-
GET
/api/auth/users/me/Description: Get the details of the currently logged-in user
-
Headers
Authorization: Bearer <access_token> -
Response
{"id": 1,"email": "user@example.com"} -
Access
- Authenticated user (must be logged in)
-
-
PUT
/api/auth/users/me/Description: Fully update the currently logged-in user
-
Request
{"email": "new@example.com"} -
Access
- Authenticated user (must be logged in)
-
-
PATCH
/api/auth/users/me/Description: Partly update the currently logged-in user
-
Request
{"email": "updated@example.com"} -
Access
- Authenticated user (must be logged in)
-
-
DELETE
/api/auth/users/me/Description: Delete your own account
-
Response
- 204 No Content
-
Access
- Authenticated user (must be logged in)
-
-
POST
/api/auth/users/resend_activation/Description: Send the activation email again
-
Request
{"email": "user@example.com"} -
Response
- 204 No Content
-
Access
- Public
-
-
POST
/api/auth/users/reset_email/Description: Ask to change your email
-
Request
{"email": "new@example.com"} -
Response
- 204 No Content
-
Access
- Authenticated user (must be logged in)
-
-
POST
/api/auth/users/reset_email_confirm/Description: Confirm the email change
-
Request
{"uid": "encoded_user_id","token": "email_reset_token"} -
Response
- 204 No Content
-
Access
- Public (you just need a valid token)
-
-
POST
/api/auth/users/reset_password/Description: Ask to reset your password
-
Request
{"email": "user@example.com"} -
Response
- 204 No Content
-
Access
- Public
-
-
POST
/api/auth/users/reset_password_confirm/Description: Confirm the password reset
-
Request
{"uid": "encoded_user_id","token": "reset_token","new_password": "newpassword"} -
Response
- 204 No Content
-
Access
- Public (you just need a valid token)
-
-
POST
/api/auth/users/set_email/Description: Change your email while you’re logged in
-
Request
{"email": "new@example.com"} -
Access
- Authenticated user (must be logged in)
-
-
POST
/api/auth/users/set_password/Description: Change your password while you’re logged in
-
Request
{"current_password": "oldpassword","new_password": "newpassword"} -
Response
- 204 No Content
-
Access
- Authenticated user (must be logged in)
-
Reference URLs for below examples
# urls.pyfrom django.urls import path, includeurlpatterns = [ # other urls path('auth/', include('djoser.urls')), path('auth/', include('djoser.urls.jwt')), # for JWT authentication]Registering Users
Section titled “Registering Users”First, let’s sign up a user using the /users/ endpoint. We send a POST request with the user’s details, and it creates a new account.
curl -X POST http://localhost:8000/auth/users/ \-H "Content-Type: application/json" \-d '{ "username": "john_doe", "email": "john_doe@example.com", "password": "secure_password"}'This creates a new user with the username john_doe, email john_doe@example.com, and password secure_password.
Logging In
Section titled “Logging In”To log in, we use the /jwt/create/ endpoint to get a JWT token. We send a POST request with the user’s username and password.
curl -X POST http://localhost:8000/auth/jwt/create/ \-H "Content-Type: application/json" \-d '{ "username": "john_doe", "password": "secure_password"}'This gives back a JSON response with the access token and refresh token:
{ "access": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImpvaG5fZG9lIiwiZXhwIjoxNjE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c", "refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImpvaG5fZG9lIiwiZXhwIjoxNjE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"}- access token: You use this to prove who you are on every request you make after logging in. It usually has a short lifespan (e.g. 5 minutes) to stay safe.
- refresh token: You use this to get a brand new access token once the old one expires. It usually lasts longer (e.g. 1 day) and lets the user stay “logged in” without typing their password again.
Getting a New Access Token using the Refresh Token
Section titled “Getting a New Access Token using the Refresh Token”Once the access token expires, you don’t need to ask the user to log in again. Instead, send the refresh token to the /jwt/refresh/ endpoint, and you’ll get a brand new access token back.
curl -X POST http://localhost:8000/auth/jwt/refresh/ \-H "Content-Type: application/json" \-d '{ "refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImpvaG5fZG9lIiwiZXhwIjoxNjE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"}'This returns a new access token:
{ "access": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImpvaG5fZG9lIiwiZXhwIjoxNjE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"}Getting the Current User’s Profile
Section titled “Getting the Current User’s Profile”To see the profile of the user who is currently logged in, send a GET request to the /users/me/ endpoint with the access token in the Authorization header.
curl -X GET http://localhost:8000/auth/users/me/ \-H "Authorization: JWT eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImpvaG5fZG9lIiwiZXhwIjoxNjE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c" \-H "Content-Type: application/json"If you’re testing this in a browser, you can use an extension like ModHeader to add the Authorization header with the JWT token. ModHeader for Chrome
Custom Actions
Section titled “Custom Actions”Sometimes the standard CRUD operations (Create, Read, Update, Delete) are not enough. You can add your own custom actions to a viewset to handle something extra. For example, you might want users to be able to change their password through a special endpoint.
Reference code for custom action
# models.pyfrom django.db import modelsfrom django.contrib.auth import get_user_model
User = get_user_model()
class Customer(models.Model):user = models.OneToOneField(User, on_delete=models.CASCADE) # other fields
def __str__(self) -> str: return self.user.username# serializers.pyfrom rest_framework import serializersfrom .models import Customer
class CustomerSerializer(serializers.ModelSerializer): class Meta: model = Customer fields = [ 'user', # other fields ]# views.pyfrom rest_framework import viewsetsfrom .models import Customer
class CustomerViewSet(viewsets.ModelViewSet): queryset = Customer.objects.all() serializer_class = CustomerSerializer# urls.pyfrom django.urls import path, includefrom rest_framework.routers import DefaultRouterfrom .views import CustomerViewSet
router = DefaultRouter()router.register(r'customers', CustomerViewSet)
urlpatterns = [ path('', include(router.urls)),]Now let’s say we want an endpoint like /customers/me/ that lets a logged-in user see their own customer profile. We can do this with a custom action inside CustomerViewSet:
from rest_framework.decorators import actionfrom rest_framework.permissions import IsAuthenticatedfrom rest_framework import viewsetsfrom .models import Customerfrom rest_framework.response import Response
class CustomerViewSet(viewsets.ModelViewSet): queryset = Customer.objects.all() serializer_class = CustomerSerializer
@action(detail=False, methods=['GET', 'PUT'], permission_classes=[IsAuthenticated]) def me(self, request): customer, _ = Customer.objects.get_or_create(user=request.user) if request.method == 'GET': serializer = self.get_serializer(customer) return Response(serializer.data) elif request.method == 'PUT': serializer = self.get_serializer(customer, data=request.data, partial=True) serializer.is_valid(raise_exception=True) serializer.save() return Response(serializer.data)Let’s break down what’s happening in this code:
-
detail=Falsemeans this action is not about one specific item (it doesn’t need an ID/primary key in the URL). If we useddetail=Trueinstead, the URL would become/customers/{pk}/me/, which is not what we want here. -
methods=['GET', 'PUT']means this action can handle both GET and PUT requests. GET is used to fetch the customer’s profile, and PUT is used to update it. -
permission_classes=[IsAuthenticated]makes sure only logged-in users can use this endpoint. You can set different permissions on a single action, and the rest of the viewset can have its own permissions too. Whatever permission you set on the action will override the viewset’s permission, but only for that specific action.
Permissions
Section titled “Permissions”Permissions decide who is allowed to do what in your API.
This is one of the most important things to understand properly, so let’s go slow and build it up step by step.
Think of authentication and permissions as two separate questions:
- Authentication asks: “Who are you?” (are you logged in, and if yes, which user are you?)
- Permissions ask: “Now that I know who you are, what are you allowed to do?”
In Django REST Framework (DRF), permission classes help you answer questions like:
- Is the user logged in at all?
- Is the user an admin?
- Is this particular user allowed to edit this particular object (for example, can John edit Mary’s post)?
This is what keeps your API safe from people doing things they shouldn’t be able to do.
DRF comes with several ready-made permission classes, so you don’t always have to build your own:
AllowAny: Anyone can use this endpoint, even people who are not logged in.IsAuthenticated: Only people who are logged in can use it.IsAdminUser: Only admin/staff accounts can use it.IsAuthenticatedOrReadOnly: Anyone can read (view) the data, but only logged-in users can create, update, or delete it.DjangoModelPermissions: Uses Django’s own model permission system (add,change,delete, and sometimesview).DjangoObjectPermissions: Like the one above, but checks permission on a specific object, not just the model in general.Custom permissions: If none of the above fit your need, you can write your own rules by extendingBasePermission.
Setting Global Permissions
Section titled “Setting Global Permissions”Global permissions are the default rule that applies to your whole API. You set this once, in settings.py.
This default will apply to every view, unless you specifically change it for one view.
REST_FRAMEWORK = { 'DEFAULT_PERMISSION_CLASSES': [ 'rest_framework.permissions.IsAuthenticated', # or 'rest_framework.permissions.IsAdminUser' # or 'rest_framework.permissions.AllowAny' # or any other permission class ],}In the example above, every single endpoint in your project will require the user to be logged in, by default.
Setting Permissions at the View Level
Section titled “Setting Permissions at the View Level”If just one endpoint needs different rules than the rest, you can set permission_classes directly on that view or viewset.
This will override the global default, but only for that one view.
from rest_framework.permissions import IsAuthenticatedfrom rest_framework import viewsets
class ProductViewSet(viewsets.ModelViewSet): queryset = Product.objects.all() serializer_class = ProductSerializer # Only authenticated users can access this view permission_classes = [IsAuthenticated]Different Permission for Different HTTP Methods
Section titled “Different Permission for Different HTTP Methods”Sometimes you want different rules for the same endpoint depending on the type of request. For example:
- Anyone can read (view) products using
GET - Only logged-in users can change products using
POST,PUT,PATCH, orDELETE
You can set this up by overriding the get_permissions() method.
from rest_framework.permissions import IsAuthenticated, AllowAnyfrom rest_framework import viewsets
class ProductViewSet(viewsets.ModelViewSet): queryset = Product.objects.all() serializer_class = ProductSerializer
def get_permissions(self): if self.request.method in ['POST', 'PUT', 'PATCH', 'DELETE']: return [IsAuthenticated()] return [AllowAny()]This is a very common, beginner-friendly setup: anyone can read your data, but only logged-in users can change it.
Creating Custom Permissions
Section titled “Creating Custom Permissions”If the built-in permissions don’t cover what you need, you can write your own permission class.
There are two main methods you can add:
has_permission()- for checking access at the view level (a general check before anything else happens)has_object_permission()- for checking access to one specific object (for example, “is this user the owner of this exact post?”)
# permissions.pyfrom rest_framework import permissionsfrom rest_framework.permissions import BasePermission
class IsOwner(BasePermission): def has_object_permission(self, request, view, obj): return obj.owner == request.user
class IsAdminOrReadOnly(BasePermission): def has_permission(self, request, view): if request.method in permissions.SAFE_METHODS: return True return request.user and request.user.is_staffHere we made two custom permissions:
IsOwner: only the person who owns the object can access it.IsAdminOrReadOnly: everyone can read the data, but only staff/admin users can change it.
Model Permissions
Section titled “Model Permissions”DRF can also plug into Django’s own built-in model permission system, instead of you writing everything from scratch.
Django Model Permissions
Section titled “Django Model Permissions”DjangoModelPermissions checks the user’s Django model permissions before letting them make any changes.
In simple words, DRF is basically asking: “Does this user actually have permission to do this action on this model?”
The common checks are:
addchangedelete- (and
view, only if you set it up for GET requests) - This permission requires the user to be logged in for every request, even
GETrequests.
This is useful if you’re already managing permissions through Django’s admin panel, with users and groups.
from rest_framework.permissions import DjangoModelPermissions
class ProductViewSet(viewsets.ModelViewSet): queryset = Product.objects.all() serializer_class = ProductSerializer permission_classes = [DjangoModelPermissions]Django Model Permissions Or Anon Read Only
Section titled “Django Model Permissions Or Anon Read Only”Use DjangoModelPermissionsOrAnonReadOnly when you want this exact setup:
- Users who are not logged in (anonymous users): can only read the data
- Users who are logged in: get checked against model permissions before they can write (change) anything
This permission class already comes built into DRF, so you can just plug it in directly - no extra setup needed.
from rest_framework.permissions import DjangoModelPermissionsOrAnonReadOnlyfrom rest_framework import viewsets
class ProductViewSet(viewsets.ModelViewSet): queryset = Product.objects.all() serializer_class = ProductSerializer permission_classes = [DjangoModelPermissionsOrAnonReadOnly]Custom Model Permissions
Section titled “Custom Model Permissions”Use this approach when you want to create your own, fully custom access rules from scratch.
Here’s the simple flow to follow:
- Add your custom permission inside the model’s
Metaclass. - Run migrations.
- Give that permission to users or groups.
- Use a custom DRF permission class in your view to check for it.
1. Create Permission in Model (Meta class)
Section titled “1. Create Permission in Model (Meta class)”# models.pyfrom django.db import models
class Product(models.Model): name = models.CharField(max_length=100) description = models.TextField() price = models.DecimalField(max_digits=10, decimal_places=2)
class Meta: permissions = [ ('can_publish_product', 'Can publish product'), ]2. Run Migrations
Section titled “2. Run Migrations”uv run python manage.py makemigrationsuv run python manage.py migrate3. Create Group and Assign Permissions to Users
Section titled “3. Create Group and Assign Permissions to Users”You can do this in two beginner-friendly ways.
Admin panel way:
- Open Django admin and go to
Groups. - Create a group (example:
ProductManagers). - Add permissions to that group (example:
view_product,change_product,can_publish_product). - Open a user and add that user to the group.
Shell way:
from django.contrib.auth.models import Group, Permissionfrom django.contrib.auth import get_user_model
User = get_user_model()
# 1) Create or get groupgroup, _ = Group.objects.get_or_create(name='ProductManagers')
# 2) Add permissions to groupgroup.permissions.add( Permission.objects.get(codename='view_product'), Permission.objects.get(codename='change_product'), Permission.objects.get(codename='can_publish_product'),)
# 3) Add user to groupuser = User.objects.get(username='john_doe')user.groups.add(group)4. Create a Custom DRF Permission Class
Section titled “4. Create a Custom DRF Permission Class”# permissions.pyfrom rest_framework.permissions import BasePermission
class CanPublishProduct(BasePermission): message = 'You do not have permission to publish products.'
def has_permission(self, request, view): if not request.user or not request.user.is_authenticated: return False # Format: app_label.permission_codename return request.user.has_perm('store.can_publish_product')Now use it in your viewset:
from rest_framework import viewsetsfrom .permissions import CanPublishProduct
class ProductViewSet(viewsets.ModelViewSet): queryset = Product.objects.all() serializer_class = ProductSerializer permission_classes = [CanPublishProduct]Control Existing Permissions by HTTP Method
Section titled “Control Existing Permissions by HTTP Method”Use this pattern when you want to reuse Django’s already-existing model permissions (view, add, change, delete), but check a different one depending on which HTTP method is being used.
Here’s the simple idea:
GET/HEAD/OPTIONS-> needsview_productPOST-> needsadd_productPUT/PATCH-> needschange_productDELETE-> needsdelete_product
This gives you fine control over access, without having to invent brand new permission names.
# permissions.py
# it is a generic permission but you can customize the perms_map to fit your needsfrom rest_framework import permissions
class ViewProductPermission(permissions.BasePermission): def has_permission(self, request, view): if not request.user or not request.user.is_authenticated: return False
perms_by_method = { 'GET': 'store.view_product', 'HEAD': 'store.view_product', 'OPTIONS': 'store.view_product', 'POST': 'store.add_product', 'PUT': 'store.change_product', 'PATCH': 'store.change_product', 'DELETE': 'store.delete_product', }
required_perm = perms_by_method.get(request.method) if not required_perm: return False
# Format: app_label.permission_codename return request.user.has_perm(required_perm)