How to get user in Django REST framework? - python

I'm using the Django REST framework and I'm trying to get the user like this in one of my class-based views:
class ListPDF(PDFTemplateView):
"""
Return a PDF
"""
permission_classes = (IsAuthenticated,)
template_name = 'pdf.html'
def get_context_data(self, **kwargs):
user = User.objects.get(id=self.request.user.id)
but for some reason I keep getting the error User matching query does not exist. I'm using the rest_framework.authtoken for authentication.
I've investigated and when I don't log in through the admin section the user is anonymous even though the user token is sent with the request. How do I get the user object in this view?
**Update
I found this answer:
from rest_framework.authtoken.models import Token
user = Token.objects.get(key='token string').user
but is there not an easier way to get the user?

Make sure you have added TokenAuthentication to DEFAULT_AUTHENTICATION_CLASSES:
# settings.py:
INSTALLED_APPS = [
...
'rest_framework',
'rest_framework.authtoken',
...
]
REST_FRAMEWORK = {
...
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication',
],
...
}
Docs: http://www.django-rest-framework.org/api-guide/authentication/#tokenauthentication
Edit: I've noticed that you are subclassing PDFTemplateView. Did you write it yourself? Is it inherited from the DRF's APIView?

Related

DRF: request.user returns nothing for token based authentication

I am basically new to DRF & trying to make one API endpoint to update user via mobile app,here is my code for that,
class MobileAppUserUpdateView(APIView):
def post(self, request, format=None):
user = request.user
print('USER',user) #<- prints nothing
print('TOKEN',request.auth) #<- prints the token
return ResponseBuilder.success([constants.RECORD_UPDATED])
there I want to access the user who has made that request using the auth token but getting nothing there.Here is my settings for DRF,
NON_FIELD_ERRORS_KEY = 'message'
REST_FRAMEWORK = {
# Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users.
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
# 'rest_framework.authentication.TokenAuthentication',
],
'DEFAULT_AUTHENTICATION_CLASSES': (
# 'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.TokenAuthentication',
),
'NON_FIELD_ERRORS_KEY': NON_FIELD_ERRORS_KEY,
}
I have also added the "rest_framework.authtoken" in the INSTALLED_APPS list as per the documentation.Any help would be appreciated...
Just few things more..
You need to add authentication_classes in MobileAppUserUpdateView which includes TokenAuthentication, like this:
class MobileAppUserUpdateView(APIView):
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
def post(self, request, format=None):
...
Then make sure you have added rest_framework.authtoken in your INSTALLED_APPS settings:
INSTALLED_APPS = [
...
'rest_framework.authtoken'
]
Then run manage.py migrate after changing your settings.
Use a debugger to debug the code and check what your request object is receiving.
You can follow this doc for more.
The problem was in the users table, User model returns the email for str method & the email field had null value that's why I was getting nothing when I try to print it.

Django REST Framework Web Login not working

I can't seem to figure out why my rest framework login in my django project won't work... I have got the log in link there and all and it does redirect me to the rest login page but after filling in my credentials and clicking login it just takes me back to the page I came from but it does not seem to authenticate me as it still says "Login" in the top right corner...
I would expect it to change to "admin" in this case as I am trying to login using my superuser named "admin"...
I am surely missing something..
Here are my REST Urls
path("rest/auctions", auction_list, name="auction-list"),
path('rest/auctions/<int:pk>', auction_detail, name="auction-detail"),
path('rest-auth/', include('rest_framework.urls', namespace='rest_framework')),
The rest-auth/ one I know is the one that somehow adds the login button and all (Magic?) But maybe I have to do something else too to make it authenticate? I don't know... I am using the built in django authentication system and User model.
views.py
#api_view(['GET'])
def auction_list(request, format=None):
if request.method == 'GET':
query = request.GET.get('q')
if query is not None:
auctions = Auction.objects.filter(Q(title__icontains=query), deadline__gte=timezone.now(),
status=Auction.STATUS_ACTIVE)
else:
auctions = Auction.objects.all()
serializer = AuctionSerializer(auctions, many=True, context={'request': request})
return Response(serializer.data)
serializer.py
class AuctionSerializer(serializers.ModelSerializer):
winning_bid = serializers.SlugRelatedField(many=False, read_only=True, slug_field='amount', allow_null=True)
seller = serializers.SlugRelatedField(many=False, read_only=True, slug_field='username', allow_null=False)
class Meta:
model = Auction
fields = ('id', 'url', 'title', 'description', 'deadline', 'min_price', 'winning_bid', 'seller')
in my Settings.py
REST_FRAMEWORK = {
# Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users.
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticatedOrReadOnly'
],
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.BasicAuthentication',
],
}
Lastly I can mention that the authentication works when I use the API via Postman.
If the other solution doesn't work, add this line too
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.SessionAuthentication',
),
You should remove BasicAuthentication from the settings.py and it should work as expected. BasicAuthentication works by base64 encoding the user login information and attaching them to HTTP Authorization Header. That's why your requests via Postman work normally.
By default Django uses Session based authentication, which depends on cookies on the client to store the user session information once the user is logged in. Obviously username and password are not stored in cookie with BasicAuthentication.
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticatedOrReadOnly'
],
}

How to use the OAuth2 toolkit with the Django REST framework with class-based views?

I'm trying to add an API using the Django REST framework to an existing codebase which uses the Django OAuth2 toolkit. The existing views make use of the fact that the OAuth2 toolkit's backend modifies the behavior of Django's login_required decorator so that it uses OAuth2 authentication. The function-based views look similar to the one in the tutorial example:
from django.contrib.auth.decorators import login_required
from django.http.response import HttpResponse
#login_required()
def secret_page(request, *args, **kwargs):
return HttpResponse('Secret contents!', status=200)
I'm trying to adapt the example given on https://django-oauth-toolkit.readthedocs.io/en/latest/rest-framework/getting_started.html to my situation, but I'm finding it's not an exact correspondence, because the example uses the DRF's ModelViewSet classes whereas my current view uses a (less-integrated) generic class-based view:
from rest_framework import generics
from ..models import Session
from ..serializers import SessionSerializer
class SessionDetail(generics.UpdateAPIView):
queryset = Session.objects.all()
serializer_class = SessionSerializer
where I've set the default permission class to IsAuthenticated in settings.py:
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
}
This should allow a Session object to be updated using PATCH requests, but it appears to return a 403 Forbidden response for use cases similar to the ones for the current views decorated with login_required.
How can I update the SessionDetail view so that it behaves the same as a function view decorated with login_required? I suspect that I need to use the TokenHasReadWriteScope permission class, since this appears to be the only one for which the required_scopes is optional. (The views decorated with login_required don't provide required_scopes either).
Would it be like this:
from rest_framework import generics
from rest_framework.permissions import IsAuthenticated
from oauth2_provider.contrib.rest_framework import OAuth2Authentication, TokenHasReadWriteScope
from ..models import Session
from ..serializers import SessionSerializer
class SessionDetail(generics.UpdateAPIView):
authentication_classes = [OAuth2Authentication]
permission_classes = [TokenHasReadWriteScope]
queryset = Session.objects.all()
serializer_class = SessionSerializer
Also, would I have to update my REST_FRAMEWORK setting with a DEFAULT_AUTHENTICATION_CLASSES like this:
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
'DEFAULT_AUTHENTICATION_CLASSES': (
'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
)
}
Right now if I run the server and try to make a patch request with the browsable API, I get a 401 Unauthorized error:
Also if I Log in using a username and password that I just created using python manage.py createsuperuser, it goes straight back to this 401 page instead of one with a form to make a PATCH request as I'd expect.
Any ideas on how to fix this?

How to restrict Django Rest Framework browsable API interface to admin users

I'm developing a Django Rest Framework backend for a mobile app. The API is private and will only ever be used internally.
The browsable API is convenient for helping developers working on the project but I would like to prevent anyone who's not set as an admin on the project from using the browsable interface.
I realize that the browsable admin doesn't grant any permissions that user wouldn't otherwise have, but it does have some security gray areas (e.g. for models with a foreign key relationship, the HTML selector field gets populated with all the possible related objects in the DB unless you specifically instruct it not to).
Because this app handles sensitive user data, I'd prefer to expose the smallest surface area possible to the public to reduce the risk of my own potential mistakes oversights.
Is there any way to disable the browsable API for non-admin users without disabling it for everyone? I've done a fair amount of Google searching and looked on SO and haven't found an answer. This question is close How to disable admin-style browsable interface of django-rest-framework? but not the same because those instructions disable the interface for everyone.
Is `DEFAULT_PERMISSION_CLASSES' setting not enough? This sets a default restriction on all views DRF docs on default permission classes
In settings.py:
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAdminUser',
]
}
They will 'reach' the browsable interface but all types of requests will be denied if not authorized.
If for some reason various end-points needed to be reached by non-admin users, you could loosen the restriction on a view-by-view basis.
Assuming you're using DRF's built in views, I think you can just override get_renderers().
In your settings file:
REST_FRAMEWORK = {
# Only enable JSON renderer by default.
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer',
],
}
And then in your views.py:
from rest_framework import generics, renderers
class StaffBrowsableMixin(object):
def get_renderers(self):
"""
Add Browsable API renderer if user is staff.
"""
rends = self.renderer_classes
if self.request.user and self.request.user.is_staff:
rends.append(renderers.BrowsableAPIRenderer)
return [renderer() for renderer in rends]
class CustomListApiView(StaffBrowsableMixin, generics.ListAPIView):
"""
List view.
"""
# normal stuff here
In rest_framework views we have a attribute called renderes_classes
Usually we have a method get_<something> as we do with queryset/get_queryset but in this case we didn't have that, so i needed to implement a property.
from tasks.models import Task
from tasks.serializers import TaskSerializer
from rest_framework.generics import ListAPIView
from rest_framework.permissions import IsAuthenticatedOrReadOnly
from rest_framework.renderers import CoreJSONRenderer
class CustomRendererView:
permission_classes = (IsAuthenticatedOrReadOnly,)
#property
def renderer_classes(self):
renderers = super(ListTask, self).renderer_classes
if not self.request.user.is_staff:
renderers = [CoreJSONRenderer]
return renderers
class ListTask(CustomRendererView, ListAPIView):
queryset = Task.objects.all()
serializer_class = FullTaskSerializer

Django-rest-framework: set default renderer not working?

I'm trying to build a Django-rest-framework REST API that outputs JSON by default, but has XML available too.
I have read the Renderers chapter of the documentation section on default ordering, and have put this in my settings file:
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
'rest_framework_xml.renderers.XMLRenderer',
)
}
However, this outputs XML by default. Switching the order makes no difference.
I do get JSON if I append format=json to the URL, and if I remove the XMLRenderer line altogether.
How can I set JSON to be the default?
I'm using v1.7 of Django and v3.1.1 of Django-rest-framework.
UPDATE: As requested here is the code for my views:
class CountyViewSet(viewsets.ModelViewSet):
queryset = County.objects.all()
serializer_class = CountySerializer
And the serializer:
from rest_framework import serializers
class CountySerializer(serializers.ModelSerializer):
class Meta:
model = County
fields = ('id', 'name', 'name_slug', 'ordering')
And then finally from my urls file:
router = routers.DefaultRouter()
router.register(r'county', CountyViewSet)
urlpatterns = [
url(r'^', include(router.urls)),
]
my solution: file renderers.py
from rest_framework.negotiation import DefaultContentNegotiation
class IgnoreClientContentNegotiation(DefaultContentNegotiation):
logger = logging.getLogger(__name__)
def select_renderer(self, request, renderers, format_suffix):
"""
Select the first renderer in the `.renderer_classes` list.
"""
# Allow URL style format override. eg. "?format=json
format_query_param = self.settings.URL_FORMAT_OVERRIDE
format = format_suffix or request.query_params.get(format_query_param)
request.query_params.get(format_query_param), format))
if format is None:
return (renderers[0], renderers[0].media_type)
else:
return DefaultContentNegotiation.select_renderer(self, request, renderers, format_suffix)
Now just need add to settings.py in
REST_FRAMEWORK = {
(...)
'DEFAULT_CONTENT_NEGOTIATION_CLASS': 'myapp.renderers.IgnoreClientContentNegotiation',
}
Can you post your code for the actual views?
Have you defined renderer_classes in your views? as this would override the default settings.
class YourView(APIView):
renderer_classes = (XMLRenderer, JSONRenderer, )
Most likely the issue you are hitting, especially if you are testing with a browser, is that XML comes before JSON in the Accepts header. Because of this, Django REST framework is rendering XML because you specifically requested it, even though its not what you are expecting.
By giving DRF a list of default renderers, you are telling it "use these if I don't tell you to use other ones in my view". DRF will then compare the media types of these to the ones in your Accepts header to determine the best renderer to use in the response. The order doesn't matter unless you don't include a specific media type in your Accepts header, at which point it should default to the first one in the list.
Sérgio's answer is correct.
Just to add some more details for anyone in the future that comes across this.
Add djangorestframework-xml to pipfile
Update
RENDERER_CLASSES = (
'rest_framework.renderers.JSONRenderer',
'rest_framework_xml.renderers.XMLRenderer',
)
REST_FRAMEWORK = {
...
'DEFAULT_RENDERER_CLASSES': RENDERER_CLASSES,
}
add rest_framework_xml to INSTALLED_APPS
INSTALLED_APPS = [
...
'rest_framework',
'rest_framework_xml',
]
Follow Sérgio's advice about creating 'renderers.py'

Categories