django-rest-framework-simplejwt disable refresh - django-rest-framework

Is there a way to disable refresh token?
Take away refresh field from the response.

Thank you #alamshafi2263. I think you give the perfect direction.
Don't know why, I still git the refresh item from server response, not Django Shell. (also see the debug variables in photo).
So I just simply data.pop('refresh', None) it out, then problem solved.
Thank you for your time and code.

The Easy Way
Write a view of your own extending the TokenObtainPairView and override the post method.
# in your views.py
from rest_framework import status
from rest_framework.response import Response
from rest_framework_simplejwt.views import TokenObtainPairView
class MyTokenView(TokenObtainPairView):
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
try:
serializer.is_valid(raise_exception=True)
except TokenError as e:
raise InvalidToken(e.args[0])
serializer.validated_data.pop('refresh', None)
return Response(serializer.validated_data, status=status.HTTP_200_OK)
# in your urls.py
urlpatterns = [
path('api/token/', MyTokenView.as_view()),
]
The Complicated but Nicer Way
You need to create a serializer extending TokenObtainSerializer and then define a Custom View as above. This time put your new serializer as the serializer_class of this view and forget about the post method.
# in your serializers.py
from rest_framework_simplejwt.serializers import TokenObtainSerializer
from rest_framework_simplejwt.tokens import RefreshToken
class MyTokenObtainSerializer(TokenObtainSerializer):
#classmethod
def get_token(cls, user):
return RefreshToken.for_user(user)
def validate(self, attrs):
data = super().validate(attrs)
refresh = self.get_token(self.user)
data['access'] = str(refresh.access_token)
return data
# in your views.py
from rest_framework_simplejwt.views import TokenObtainPairView
from .serializers import MyTokenObtainSerializer
class MyTokenView(TokenObtainPairView):
serializer_class = MyTokenObtainSerializer
# in your urls.py
urlpatterns = [
path('api/token/', MyTokenView.as_view()),
]

You can parse the user.tokens() dictionary
user.tokens()['access']
user.tokens()['refresh]
And pass them to "tokens" key while in Response

Related

PUT request doesn't update - Django Rest Framework

I'm trying to build a todos app using django-rest-framework and when I send PUT request to change the content of the task, the content is still the same. It seems that the serializer.save() is not working as I expected.
Here are the code:
views.py
from rest_framework.response import Response
from rest_framework.decorators import api_view
from .models import Note
from .serializers import NoteSerializer
#api_view(['PUT'])
def updateNote(request, pk):
note = Note.objects.get(id=pk)
serializer = NoteSerializer(instance=note, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
models.py
from django.db import models
class Note(models.Model):
body = models.TextField(null=True, blank=True)
updated = models.DateTimeField(auto_now=True)
created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.body[0:50]
serializers.py
from rest_framework.serializers import ModelSerializer
from .models import Note
class NoteSerializer(ModelSerializer):
class Meta:
model = Note
fields = '__all__'
I'm learning django so this is kinda new to me. I had searched for this problem but I'm kinda new to django so I don't understand these answers much. Any help would be appreciated.
#api_view(['PUT'])
def updateNote(request, self, pk):
note = get_object_or_404(Note.objects.all(), pk=pk)
serializer = NoteSerializer(instance=note, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)

Is it safe to use caching on get_object in django class-based view

I use some permission checks inside overridden check_permissions. I get the instance I will work with using get_object. I know that after this the get_object method will be called again - during retrieve or destroy methods. So I use caching (#lru_cache). See full example below.
from functools import lru_cache
from rest_framework.exceptions import PermissionDenied
from rest_framework.generics import RetrieveDestroyAPIView
class UserView(RetrieveDestroyAPIView):
permission_classes = [ProjectAdminOnly]
serializer_class = UserSerializer
#lru_cache
def get_object(self, *args, **kwargs):
return UserService.get(self.kwargs['user_id'])
def check_permissions(self, request):
super().check_permissions(request)
user = self.get_object()
# Only within the same project
if self.request.user.project != user.project:
raise PermissionDenied()
This code passes my tests successfully, but I wonder is there any unsafe situation in which the caching will do the bad service for me?

How to send PUT request to ModelViewSet without passing a primary key in the url?

I am particularly interested in using ModelViewSet
for solving the challenge of updating the logged in user's profile. I am using the following definition:
from rest_framework import viewsets
class ProfileRetrieveUpdate(viewsets.ModelViewSet):
serializer_class = UserProfileSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
def get_queryset(self):
return UserProfile.objects.filter(user=self.request.user)
def perform_update(self, serializer):
serializer.save(user=self.request.user)
By overriding get_queryset, I am able to get the expected behavior, i.e. when I access the endpoints (say profile/), I get the profile of the logged in user. However, if I have to update the profile, I can only access PUT by going to profile/6/. Is there a way I can get ModelViewSet to expose PUT at the same endpoint i.e. profile/?
You can register ModelViewSet HTTP method under any path you want.
path(
"profile",
ProfileRetrieveUpdate.as_view(
{"put": "partial_update", "get": "retrieve"}
),
name="profile-retrieve-update",
)
You will have to adjust other things as well as you don't provide pk.
Instead of overriding get_queryset method you need to adjust get_object for your needs.
from rest_framework import viewsets
from rest_framework.generics import get_object_or_404
class ProfileRetrieveUpdate(viewsets.ModelViewSet):
serializer_class = UserProfileSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
queryset = UserProfile.objects.all()
def get_object(self):
obj = get_object_or_404(self.queryset, user=self.request.user)
self.check_object_permissions(self.request, obj)
return obj
def perform_update(self, serializer):
serializer.save(user=self.request.user)
Now if you PUT profile/ it should work as expected.

Set 2 permmissions for 2 different actions in ListCreateAPIView in django-rest-framework

I am developing an end-point where users can get the categories and only admin can create them. I am using Django and DjangoRestFramework. More specifically I am using ListCreateAPIView.
Here is my code.
class TagView(ListCreateAPIView):
serializer_class = TagSerializer
queryset = Tag.objects.all()
permission_classes = [IsAdminUser, ]
My Task: I need to set two permission for list and create, they are AllowAny and IsAdminUser.
Any ideas or suggestions and answers are welcome. Thanks beforehand.
I recommend using rest_condition library. In your case, you can code like this:
...
from rest_condition import And, Or
from rest_framework.permissions import BasePermission
class IsPostMethod(BasePermission):
def has_permission(self, request, view):
return request.method.upper() == 'POST'
class IsSafeMethod(BasePermission):
def has_permission(self, request, view):
return request.method.upper() in ('OPTIONS', 'HEAD', 'GET')
class TagView(ListCreateAPIView):
serializer_class = TagSerializer
queryset = Tag.objects.all()
permission_classes = [
Or(
And(IsPostMethod, IsAdminUser),
And(IsSafeMethod, AllowAny),
)
]
Use method_decorator in combination with permission_required on top of your API class, like this:
#method_decorator(permission_required('permission_name'), name='method_name')
class TagView ...
I would suggest using custom permissions, It worked pretty well, and clean.
from rest_framework.permissions import SAFE_METHODS, BasePermission
class IsAdminOrReadOnly(BasePermission):
def has_permission(self, request, view):
if request.method in SAFE_METHODS:
return True
return request.user.is_superuser

DRF: Pagination without queryset

I am trying to make use of Django Rest Framework's pagination mechanisms in my case without success.
class TransactionView(viewsets.ViewSet):
serializer_class = TransactionSerializer
def list(self, request):
# fetching data from external API...
serializer = self.serializer_class(data=list_of_json, many=True)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
else:
return Response(serializer.errors)
class TransactionSerializer(serializers.Serializer):
# Serializer (transaction's) fields ...
def create(self, validated_data):
return APITransaction(**validated_data)
class APITransaction(object):
def __init__(self, arg1, arg2, ...):
self.arg1 = arg1
...
The problem is that registering the pagination_class (like I have done for the rest of my resources which are represented by Models), doesn't work since the data are created/fetched on the fly, thus I don't have a Model/queryset.
Any ideas on how I could use DRF's pagination mechanism?
Here's the class I wound up creating and using locally for this sort of thing. (Thanks to stelios above for the initial clues.) Of course the contents of "data" must be JSONable.
from typing import List, Any
from collections import OrderedDict
from django.core.paginator import Paginator
from django.http.response import JsonResponse
from rest_framework.request import Request
class ListPaginator:
def __init__(self, request: Request):
# Hidden HtmlRequest properties/methods found in Request.
self._url_scheme = request.scheme
self._host = request.get_host()
self._path_info = request.path_info
def paginate_list(self, data: List[Any], page_size: int, page_number: int) -> JsonResponse:
paginator = Paginator(data, page_size)
page = paginator.page(page_number)
previous_url = None
next_url = None
if self._host and self._path_info:
if page.has_previous():
previous_url = '{}://{}{}?limit={}&page={}'.format(self._url_scheme, self._host, self._path_info, page_size, page.previous_page_number())
if page.has_next():
next_url = '{}://{}{}?limit={}&page={}'.format(self._url_scheme, self._host, self._path_info, page_size, page.next_page_number())
response_dict = OrderedDict([
('count', len(data)),
('next', next_url),
('previous', previous_url),
('results', page.object_list)
])
return JsonResponse(response_dict, status=200, safe=False)
You can't reuse existing DRF's pagination because they are supposed to work with queryset.
However, you may roll your own class by inheriting BasePagination though I haven't done myself.

Resources