I'm working with DRF and came across this issue. I have a third-party view which I'm importing in my urls.py file like this :
from some_package import some_view
urlpatterns = [
path('view/',some_view)
]
but the issue I'm facing is since I have enabled default permission classes in my settings.py like this:
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
),
'DEFAULT_PERMISSION_CLASSES':(
'rest_framework.permissions.IsAuthenticated',
),
}
now when I call that view using the url , it gives me authentication error as I'm not providing token .Is there a way I can bypass authentication error without having to make changes in view directly,I know that we can remove permission for that particular view , but for that I'll have to make changes to that some_view function code. But I don't want to do that,let's say we don't have access to that function we can only pass data and receive response. How can I bypass authentication without having to change that functions code .
I tried searching but couldn't find what I'm looking for.
I was assuming that there might be someway we can do that from urls.py like specifying any parameter or something like that which make that particular view to bypass authentication without having to change functions code.
somthing like this :
from some_package import some_view
urlpatterns = [
path('view/',some_view,"some_parameter") #passing someparameter from here or something like that
]
Is it possible what I'm looking for ? Thanks in advance :)
So, the most appropriate way for third-party views is to use decorators by defining them inside your urls.py:
Case 1
I assume that some_view is a class inherited from rest_framework.views.APIView:
urls.py
from django.urls import path
from rest_framework.decorators import permission_classes, authentication_classes
from rest_framework.permissions import AllowAny
from some_package import some_view
urlpatterns = [
path('', authentication_classes([])(permission_classes([AllowAny])(some_view)).as_view())
]
Case 2
I assume that some_view is a simple Django view function and you need to define it for GET method:
urls.py
from django.urls import path
from rest_framework.decorators import api_view, permission_classes, authentication_classes
from rest_framework.permissions import AllowAny
from some_package import some_view
urlpatterns = [
path('', api_view(['GET'])(authentication_classes([])(permission_classes([AllowAny])(some_view))))
]
Case 3
I assume that some_view is an api_view decorated DRF view function. This is the hardest and most probably the most impossible part because you have to undecorate the previous api_view decorator. If view function is decorated with api_view, then it is already converted into Django view function so neither permision_classes nor authentication_classes can be appended to class:
You can override the default authentication class to skip auth for specific urls.
For example:
class CustomIsAuthenticated(IsAuthenticated):
def has_permission(self, request, view):
# make a list of public urls
if request.path in PUBLIC_URLS:
return True
return super().has_permission(request, view)
You have to create a list of PUBLIC_URLS which bypass authentication.
Now use this class in the DEFAULT_PERMISSION_CLASSES in rest framework settings. And remove the default IsAuthenticated class.
Although I recommend the decorators approach.
check docs: https://www.django-rest-framework.org/api-guide/permissions/#allowany
Decorators approach is more verbose and by looking at the function you can make out if it is public or not.
#Bedilbek's answer worked for me, but if you'd prefer to use different syntax and define the permission_classes and authentication_classes inside your view instead of urls.py, you can do the following:
from rest_framework import permissions
from rest_framework.views import APIView
class SomeView(APIView):
permission_classes = [permissions.AllowAny]
authentication_classes = []
Related
The bounty expires in 4 days. Answers to this question are eligible for a +50 reputation bounty.
varnie wants to draw more attention to this question.
I have the following urls.py for an app named service where I register API endpoints:
from .views import AccessViewSet, CheckViewSet
app_name = "api"
router = DefaultRouter()
router.register(r"access/(?P<endpoint>.+)", AccessViewSet, basename="access")
router.register(r"check/(?P<endpoint>.+)", CheckViewSet, basename="check")
urlpatterns = [
path("", include(router.urls)),
]
Below is my project's urls.py where I use it:
from django.conf import settings
from django.contrib import admin
from django.urls import include, path
import service.urls as service_urls
urlpatterns = [
# ...
path("service/", include('service.urls')),
]
The APIs themselves are functioning properly, but I am having trouble making them work with DRF's default API root view. The view is displaying an empty list of available endpoints. I'm not sure, but this issue may be related to the regular expressions I'm using when registering endpoints, such as r"access/(?P<endpoint>.+). If this is indeed the problem, how can I resolve it?"
DefaultRouter needs to run reverse(viewname) on your views to generate urls for the default view. It won't know about your dynamic parameter endpoint when it runs.
If it's acceptable to change your url structure to {endpoint}/access/... and {endpoint}/check/... you can:
router.register(r"access", AccessViewSet, basename="access")
router.register(r"check", CheckViewSet, basename="check")
urlpatterns = [
re_path(r"(?P<endpoint>.+)/", include(router.urls)),
]
After which, each {endpoint}/ view should have a working default API view.
If you need to preserve your url structure, you'll have to manually generate a list of available routes like this:
from collections import OrderedDict
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.reverse import reverse
class ApiRoot(APIView):
"""My API root view"""
def get(self, request, format=None):
"""List of available API endpoints"""
api_methods = [
"access-list",
"check-list",
]
endpoints = [
"endpoint-a",
"endpoint-b",
]
routes = OrderedDict()
for endpoint in endpoints:
for method in api_methods:
routes[endpoint + method] = reverse(
method, endpoint=endpoint, request=request, format=format
)
return Response(routes)
Notice the endpoint=endpoint in the reverse call.
If the code above doesn't help, please, expand your question with more details of what you're trying to archive in the first place. I have a feeling you might want to replace your viewsets with an EndpointViewSet with custom ViewSet actions for check and access.
Finally, if you're looking for routes like {endpoint_id}/access/{access_id}/... and {endpoint_id}/check/{check_id}/... check out drf-nested-routers - a drop-in replacement for DRF's Routers that supports nesting.
I am trying to learn to create a Backend with Django, together with an Angular frontend.
In order to make the api a little more consistent I tried to create a API schema to use the OpenAPI Generator.
I have run the command ./manage.py generateschema --file schema.yml. But: The yml file does not have any information about the users.url. I have added the get_schema_view from the rest_framework, with the same result.
The (main) app urls.py looks like this:
from django.conf.urls import include
from django.contrib import admin
from django.urls import path
from rest_framework.schemas import get_schema_view
urlpatterns = [
path('admin/', admin.site.urls),
path('api/users/', include('users.urls'), name="users"),
path('api/network/', include('networkController.urls'), name="network"),
path('api/files/', include('fileController.urls'), name="files"),
path('api/', get_schema_view(
title="API Documentation",
description="API for all things"
), name='openapi-schema')
]
The networkController.urls looks like this:
from django.urls import path
from . import views
urlpatterns = [
path('', views.startNetwork)
]
which is found by the schema generator.
The users.urls looks like this:
from django.urls import path
from . import views
urlpatterns = [
path('login/', views.login),
path('register/', views.registerUser)
]
I have tried to move all urls to the (main) backend.urls and include the views directly, I have tried "layering" them all.
# backend.urls:
path('api/', include('api.urls'))
# api.urls:
path('users/', include('users.urls'))
without any changes.
I tried looking up, why - but without success. If I run the server and make a GET-Request via curl to localhost:8000/api/users/login directly, it works perfectly fine.
Could you please help me figure out, what I did wrong or guide me to a tutorial, which would cover the topic a little more detailled?
(And yes. Maybe I should just switch to something like FastAPI, but I really love Djangos Auth.Users and the easy constant, connection to a database)
Thanks in advance!
(EDIT: You can find the whole code in my GitHub)
I just figure out why my code didn't work. I use Class Based Views but I think this will also work for Method Based ones.
It looks like generateschema does not like uppercase method names:
class ...(UpdateAPIView):
serializer_class = ...
permission_classes = [IsAuthenticated]
http_method_names = ['PUT']
...
Instead of ['PUT'] I used ['put']
class ...(UpdateAPIView):
serializer_class = ...
permission_classes = [IsAuthenticated]
http_method_names = ['put']
...
So, replace all the uppercase letters with lowercase letters.
When I try a GET request on one of my API endpoints it can't find the endpoint.
urls.py file looks like this
from django.urls import path, include
from django.contrib import admin
from api.resources import NoteResource
note_resource = NoteResource()
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include(note_resource.urls)),
]
api.resources looks like this
from tastypie.resources import ModelResource
from api.models import Note
class NoteResource(ModelResource):
class Meta:
queryset = Note.objects.all()
resource_name = 'note'
Any idea why this is happening?
Solution: It appears that http://127.0.0.1:8000/api/note/ works properly.. why would this be?
You should also have one url entry in note_resource.urls for only /api request. Something similar to
path('api/', APIClass).
But, you never need that endpoint. Because, /api does not represent any actual request in your system.
I rather suggest to have following endpoints :
path('api/notes/',include(note_resource.urls))
in your main urls.py.
So that you can have multiple urls in main urls.py file representing each app.
path('api/<APP_NAME>(s)/',include(<APP_NAME>.urls))
And, you will manage other endpoints in your app urls.py file:
# Create a new Note
path(
'create/',
NoteCreate.as_view()
),
Im looking for a simple way to disable all the CSRF validation to can test my API in Postman.
Till now I have tried add #decorator csrf_exempt without success.
I also tried create a disable.py file inside the app, but didn't work also.
Also I want desactivate for all requests, so some way to dont have to add the decorator everywhere. This a project that I just got, is already in Production, but I want to start to write Tests first in Postman, later TestCases.
All my views are using a "api_generics.CRUDGeneric",
the declaration of that class is:
class CRUDGeneric(mixins.CreateModelMixin, mixins.ListModelMixin, mixins.RetrieveModelMixin,
mixins.DestroyModelMixin, mixins.UpdateModelMixin, viewsets.GenericViewSet):
thanks is advice
#62009030 you should be able to do what #smarber mentioned.. This could also work.. It is a traversed way to add csrf_exempt
from django.conf.urls import patterns, url
from django.views.decorators.csrf import csrf_exempt
import views
urlpatterns = patterns('',
url('^asimpleurl/$', csrf_exempt(views.CRUDGeneric.as_view())),
...
)
This could be a work around for your problem..
I have the model named Artist and I want expose this model with Django Rest Framework to create an API and this data can be consumed.
I've created a class based view in artists/views.py named ArtistViewSet
#CBV for rest frameworks
from rest_framework import viewsets
class ArtistViewSet(viewsets.ModelViewSet):
model = Artist
I also have an url named api/ in the urls.py file (view third url named api/) which the user could access to the view above mentioned.
# coding=utf-8
from django.conf.urls import patterns, include, url
from django.conf import settings
from django.contrib import admin
admin.autodiscover()
from rest_framework import routers
from artists.views import ArtistViewSet
#I create a router by default
router = routers.DefaultRouter()
#Register the model 'artists' in ArtistViewSet
router.register(r'artists', ArtistViewSet)
urlpatterns = patterns('',
(r'^grappelli/', include('grappelli.urls')), # grappelli URLS
url(r'^admin/', include(admin.site.urls)),
#Include url api/ with all urls of router
url(r'^api/', include(routers.urls)),
)
When I go to my browser and type http://localhost:8000/api/ I get this message error:
What did can be happened me?
In Django REST framework 2.4+ (including 3.0+), the model attribute for views has been deprecated and removed. This means that you should be defining your view as
from rest_framework import viewsets
class ArtistViewSet(viewsets.ModelViewSet):
queryset = Artist.objects.all()
Which should give you the result you are expecting. Now, you asked in the comments
I cannot understand the role of base_name. I mean, this base name is the url that I've created? My viewset ArtistViewSet does not have a queryset attribute, due to this, according to documentation, it's necessary put the base_name argument, but i don't know how to do it.
The base_name that can be optionally defined when registering a ViewSet is used when naming the automatically generated routes. By default, the format is [base]-list and [base]-detail, where [base] is the base_name that can be defined. When you do not specify your own base_name, it is automatically generated based on the model name. As the queryset method must be defined for ViewSet instances, this is where the model (and later model name) is retrieved. As you did not provide the queryset argument, Django REST framework triggers an error because it cannot generate a base_name.
To quote from the documentation on routers
Note: The base_name argument is used to specify the initial part of the view name pattern.
The documentation goes on to further explain exactly why you are getting the issue, even including an example, and how to fix it.