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'
Related
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.
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?
Trying to do a simple 'GET' wih admin credentials returns
"detail": "Invalid username/password."
I have a custom user model where I deleted the username, instead I use facebook_id :
USERNAME_FIELD = 'facebook_id'
I tried changing the DEFAULT_PERMISSION_CLASSES:
('rest_framework.permissions.IsAuthenticated',), -- doesn't work!
('rest_framework.permissions.IsAdminUser',), -- doesn't work!
The only one that works is:
('rest_framework.permissions.AllowAny',),
But I do not want that, since I'm building an API for a Mobile App
I also declared a CustomUserAdmin model and CustomUserCreationForm , apparently this was not the problem
Help me understand what needs to be done to fix this annoying problem, I'm guessing it might have something to do with Permissions/Authentication or the fact that I CustomUserModel..
Also, let me know if there is a better way for a mobile app client to authenticate to the api
Have just had the same problem. In my case the source of the problem was Apache's Basic Authentication, my browser was sending Authorization header and Django REST Framework thought that this header was to be handled by it. The solution is pretty simple: just remove
'rest_framework.authentication.BasicAuthentication' from your
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [
# ... auth classes here ...
]
}
Or explicitly set the default DEFAULT_AUTHENTICATION_CLASSES to remove BasicAuth from DRF's defaults.
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": (
"rest_framework.authentication.SessionAuthentication",
),
}
You have the default, and then you have per view. You can set the default to IsAuthenticated, and then you override your view's particular permission_classes. e.g.
class ObtainJSONWebLogin(APIView):
permission_classes = ()
or
class Foo(viewsets.ModelViewSet):
permission_classes = ()
I am making an API call in the test client:
response2 = self.client.patch('/object/update/%d/' %
object_id, {'object_attribute':4})
The relevant serializer and view class for the object:
class ObjectUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = Object
include=('object_attribte','another_attribute',)
class ObjectView(APIView):
def patch(self, request, pk, format=None):
obj = Object.objects.get(id=pk)
data = request.data.copy()
"""do some stuff with the data here..."""
serializer = ObjectUpdateSerializer(instance = obj, data=data,
partial=True)
if serializer.is_valid(raise_exception=True):
serializer.save()
return Response(serializer.data,status.HTTP_200_OK)
I was able to work with the PUT method when I used that, but I wanted to have the API call methods be more in-line with what the methods actually mean (so PATCH would be a partial replacement). However, the response to the test client call above is this:
{u'detail': u'Method "PATCH" not allowed.'}
Which is a 405 error (method not allowed).
I checked to see if there were any issues with Django 1.10, and I also got this output in the django shell:
>>> from django.views.generic import View
>>> View.http_method_names
[u'get', u'post', u'put', u'patch', u'delete', u'head', u'options', u'trace']
It appears as if it isn't an issue with Django's settings, but something that I've set up. What could be the issue here?
I faced the same problem. It appeared that it's not allowed to use different views for the same route:
urlpatterns = [
# ...
url('^sessions/?$', views.TokenDelete.as_view()), # Viewer has delete()
url('^sessions/?$', views.TokenEdit.as_view()), # Viewer has patch()
]
The correct way is:
urlpatterns = [
# ...
url('^sessions/?$', views.Token.as_view()), # Viewer has both patch() and delete()
]
I'm not sure if we have the exact same problem, but I got a problem with the same error code 405 using #api_view(['PUT', 'DELETE']) from rest_framework.
I solved it but reordering my urls in urlpatterns by putting the more specific urls before the others .e.g:
urlpatterns = [
url(
r'$',
views.swot_list,
name='get_post'
),
url(
r'(?P<swot_id>[0-9]+)/$',
views.swot_detail,
name='put_delete'
),
]
reordered into:
urlpatterns = [
url(
r'(?P<swot_id>[0-9]+)/$',
views.swot_detail,
name='put_delete'
),
url(
r'$',
views.swot_list,
name='get_post'
),
]
It turns out the order is important, and that makes sense to me. Hope this may be of help to anyone out there.
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