namespace with router.urls in django2 - django-rest-framework

In django2.1.5 and DRF 3.9.1, I am trying to add router.urls namespace which doesn't work.
path('api/v2/', include(router.urls, namespace="v2"))
The error in my terminal is
"Specifying a namespace in include() without providing an app_name '
django.core.exceptions.ImproperlyConfigured: Specifying a namespace in include() without providing an app_name is not supported. Set the app_name attribute in the included module, or pass a 2-tuple containing the list of patterns and app_name instead"
I won't find any suitable solution as I set app_name for a namespace. How can I use the namespace on router.urls or there is no way to use it in Django 2 version?
Trying to add app_name but it won't solve my problem
Here is my code.
config.urls.py
from django.urls import path, include
from django.contrib import admin
from rest_framework import routers
from project.courses import views
router = routers.SimpleRouter()
router.register(r'courses', views.CourseViewSet)
router.register(r'reviews', views.ReviewViewSet)
urlpatterns = [
path('admin/', admin.site.urls),
path('api-auth/', include('rest_framework.urls')),
path('api/v1/courses/', include('project.courses.urls', namespace='courses')),
path('api/v2/', include(router.urls, namespace="v2")),
]
courses.urls.py
from django.urls import path
from . import views
app_name = 'project.courses'
urlpatterns = [
path('', views.ListCreateCourse.as_view(), name='course_list'),
path('<int:pk>', views.RetrieveUpdateDestroyCourse.as_view(),
name='course_detail'),
path('/reviews/', views.ListCreateReview.as_view(), name='review_list'),
path('/reviews/', views.RetrieveUpdateDestroyReview.as_view(), name='review_detail'),
]
Here is the code I want to write.
reviews = serializers.HyperlinkedRelatedField(
many=True,
read_only=True,
view_name='v2:review-detail'
)
I want to access review_detail with namespace v2.
Thanks.

You try like this,
urlpatterns = [
url(r'^api/', include((router.urls, 'app_name'), namespace='instance_name')),
]
Inside the include you can't add namespace. Follow the above way. If you any doubt refer this https://www.django-rest-framework.org/api-guide/routers/

Related

Django Rest Framework Routing: Reverse for 'orders' not found. 'orders' is not a valid view function or pattern name

I'm not quite sure I'm understanding how routing works in DRF. I went through the documentation but still haven't grasp the differences.
I've got the following view:
from django.shortcuts import render
from django.shortcuts import get_object_or_404
from rest_framework.viewsets import ViewSet
from rest_framework.response import Response
from .models import Order
from .serializer import OrderSerializer
class OrderAPIViewSet(ViewSet):
def post(self, request):
print(request)
and this is urls.py within my app:
from django.urls import include, path
from rest_framework import routers
from .views import OrderAPIViewSet
router = routers.DefaultRouter()
router.register(r'orders', OrderAPIViewSet, basename='order')
urlpatterns = router.urls
and this is the main urls.py:
from django.contrib import admin
from django.urls import path, include
from django.conf.urls.static import static
from django.conf import settings
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('products.urls')),
path('', include('orders.urls'))
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
but when I try to access the orders endpoint via a simple test:
ORDERS_URL = reverse('orders')
class PublicOrderApiTests(TestCase):
"""
Test the public facing Order API
"""
def setUp(self):
self.client = APIClient()
def test_sample(self):
data = {
"product_id": 1,
"price": 5.80
}
res = self.client.post(ORDERS_URL, data)
print(res)
I'm getting the following error:
django.urls.exceptions.NoReverseMatch: Reverse for 'orders' not found.
'orders' is not a valid view function or pattern name.
what am I doing wrong? the endpoint for products works just fine but not for orders.
Found the issue or issues in this case:
In my test I had defined the wrong parameter to get the url, so instead of ORDERS_URL = reverse('orders') it needed to beORDERS_URL = reverse('order-list') . *-list is used for GET /orders/ and POST /orders/ while *-detail is for all other endpoints: PUT /orders/{id} GET /orders/{id} etc
The method in my view was post but ViewSetwhich I'm inheriting from doesn't have that method, it has the create method instead, so I needed to rename my post to create

How to disable the browsable API for non staff users (is_staff=False)?

in my case I am using Django REST Framework (DRF) as internal api. it is not intended to be consumed by regular users. therefore I would like to disable it for regular users.
an admin (is_staff=True) should be able to access it and see it:
https://restframework.herokuapp.com/
a non staff user (is_staff=False) should just get the JSON response of a GET request like:
https://restframework.herokuapp.com/?format=json
he should not(!) see the browsable api. this applies for the root view and all endpoints.
to configure this, I applied the following:
# settings.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': ['rest_framework.authentication.SessionAuthentication'],
'DEFAULT_PERMISSION_CLASSES': ['rest_framework.permissions.IsAuthenticated'],
}
my endpoints are the following (to keep the example simple I just show 1):
# api/urls.py
from django.urls import include, path
from rest_framework import routers
from . import views
app_name = 'api'
router = routers.DefaultRouter() # browseable api
router.register('segments', views.SegmentViewSet)
# there are a lot more...
urlpatterns = [
path('', include(router.urls)),
]
based on answer https://stackoverflow.com/a/58894198/420953 my settings.py looks like this:
# settings.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': ['rest_framework.authentication.SessionAuthentication'],
'DEFAULT_PERMISSION_CLASSES': ['rest_framework.permissions.IsAuthenticated'],
# enable JSON renderer by default
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer',
],
}
and my api/views.py:
# api/views.py
from django_filters import rest_framework as drf_filters
from rest_framework import filters, renderers, viewsets
from . import serializers
from segment.models import Segment
class StaffBrowsableAPIMixin:
def get_renderers(self):
"""
add BrowsableAPIRenderer if user is staff (regular users see JSONRenderer response)
"""
# explicitly set renderer to JSONRenderer (the default for non staff users)
rends = [renderers.JSONRenderer]
if self.request.user.is_staff:
# staff users see browsable API
rends.append(renderers.BrowsableAPIRenderer)
return [renderer() for renderer in rends]
class SegmentViewSet(StaffBrowsableAPIMixin, viewsets.ReadOnlyModelViewSet):
queryset = Segment.objects.all()
serializer_class = serializers.SegmentSerializer
this works fine for all endpoints (when a regular user calls the endpoint via GET, they only see the JSON, not the browsable API). Unfortunately it does not work for APIRootView (the root view of the api, e.g. https://restframework.herokuapp.com/).
how to get this to work for APIRootView as well?
I believe you can lock the base URL of your API down pretty simply (the mixin should probably be moved to another file but just kept everything together for clarity):
# api/urls.py
from django.urls import include, path
from rest_framework import permissions, renderers, routers
from . import views
app_name = 'api'
class StaffBrowsableAPIMixin:
def get_renderers(self):
"""
add BrowsableAPIRenderer if user is staff (regular users see JSONRenderer response)
"""
# explicitly set renderer to JSONRenderer (the default for non staff users)
rends = [renderers.JSONRenderer]
if self.request.user.is_staff:
# staff users see browsable API
rends.append(renderers.BrowsableAPIRenderer)
return [renderer() for renderer in rends]
class CustomAPIRootView(StaffBrowsableAPIMixin, routers.APIRootView):
permission_classes = (permissions.IsAdminUser,)
class CustomDefaultRouter(routers.DefaultRouter):
APIRootView = CustomAPIRootView
router = CustomDefaultRouter() # browseable api
router.register('segments', views.SegmentViewSet)
# there are a lot more...
urlpatterns = [
path('', include(router.urls)),
]
The permission_classes will handle not showing any of your endpoints to non-Admin users but the Browsable API template will still be shown. To remove that as well, you need to change the renderer using the StaffBrowsableAPIMixin.
Original Answer
One way to do this is using DRF's renderer settings and methods.
In your settings.py:
REST_FRAMEWORK = {
# Only enable JSON renderer by default.
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer',
],
}
And 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
Basically, use StaffBrowsableMixin for any APIView you want the BrowsableAPI to be enabled for staff.
Similar question, as linked above in comments, and my answer there as well: https://stackoverflow.com/a/58762483/4599228

Swagger navigation like https://petstore.swagger.io/#/pet doesn't work in my projects

All the Swagger apps I've seen have navigation between endpoints-related blocks like this: https://petstore.swagger.io/#/store/getInventory
When you click on certain blocks the URL changes. Can't understand what I'm doing wrong, but I have nothing like this. My browser shows the same URL all the time, i.e. it's like 'https://petstore.swagger.io/' without #, etc.
Some example of how I'm using Swagger. To make sure it's not about my project, tried with a tutorial one, but it has the same problem
serializers.py:
class PostSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Post
fields = ('author', 'title')
views.py:
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
urls.py:
router = DefaultRouter()
router.register('posts', views.PostViewSet)
schema_view = get_swagger_view(title='Posts API')
urlpatterns = [
url('^$', schema_view),
url(r'^', include(router.urls)),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]
What I'm doing wrong? Thanks!!
Enable deeplinking in your swagger ui config file:
deepLinking: true
https://github.com/swagger-api/swagger-ui/blob/8dab91f184103be794ae2fcfeb67b02126730e88/docs/deep-linking.md

TemplateResponseMixin requires either a definition of 'template_name' or an implementation of 'get_template_names()' in DRF

i am trying to confirm email address in django rest framework but i am getting the error
i changed url patterns and import confirm_email view form allauth but still getting the error
urls.py
from allauth.account.views import confirm_email
urlpatterns = [
path('rest-auth/registration/account-confirm-email/(?P<key>.+)/', confirm_email, name='account_confirm_email'),
]
views.py
from django.shortcuts import render
from rest_framework import generics
#for facebook authentication
from allauth.socialaccount.providers.facebook.views import FacebookOAuth2Adapter
from rest_auth.registration.views import SocialLoginView
from . import models
from . import serializers
class UserListView(generics.ListCreateAPIView):
queryset = models.CustomUser.objects.all()
serializer_class = serializers.UserSerializer
class FacebookLogin(SocialLoginView):
adapter_class = FacebookOAuth2Adapter

Djoser for DRF with Knox tokens

I'm trying to use djoser with token authentication, but using django-rest-knox tokens.
I have set the TOKEN_MODEL to knox.models.AuthToken, and the rest framework's DEFAULT_AUTHENTICATION_CLASSES to knox.auth.TokenAuthentication.
I naïvely thought that this would be enough, but it seems that Djoser's inbuilt serializers (create token, and token), don't work properly with the knox tokens. I tried overriding them with custom serializers, but I didn't get anywhere (which is not to say it's not possible, just that I'm bad at this).
It occurred to me that perhaps I should try using Knox's own login views... Is that possible, or can they not be mixed like that? (I'm mainly asking because I don't want to get it to 'work', but find that I've actually introduced a security hole in doing so).
Settings:
DJOSER = {
"TOKEN_MODEL": "knox.models.AuthToken",
"SERIALIZERS": {"token": "users.serializers.TokenSerializer"},
}
Where users.serializers.TokenSerializer is:
class TokenSerializer(serializers.ModelSerializer):
auth_token = serializers.CharField(source="token_key")
class Meta:
model = settings.TOKEN_MODEL
fields = ("auth_token",)
This is only slightly modified from the original Djoser TokenSerializer. It was throwing an error that AuthToken objects did not have a key attribute. Knox tokens seem to call it token_key, so I replaced the line:
auth_token = serializers.CharField(source="key") with auth_token = serializers.CharField(source="token_key")
Now, it doesn't throw an error, but it returns an empty token. Inspecting the actual db shows that it has saved a token with the correct user and creation time, but with 'null' for digest, salt, and token_key
Yes, it is possible to mixin's Djoser's and knox's additional view point. For that we are going to create an app name auth from where we are going to serve all authenticational related end-points. Now our project structure is like
MainProject
-auth
--__init__.py
--urls.py
-mainapp
....
Now in our auth app's urls we are going to serve our necessary end-points for authentication. For that we are going to take help from Djoser's urls link and Knox's urls link
And our auth's urls.py will be like following
from django.conf.urls import url, include
from django.contrib.auth import get_user_model
from djoser import views as djsoer_views
from knox import views as knox_views
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register('users', djsoer_views.UserViewSet)
User = get_user_model()
djoser_urlpatterns = [
url(
r'^users/create/?$',
djsoer_views.UserCreateView.as_view(),
name='user-create'
),
url(
r'^users/delete/?$',
djsoer_views.UserDeleteView.as_view(),
name='user-delete'
),
url(
r'^users/activate/?$',
djsoer_views.ActivationView.as_view(),
name='user-activate'
),
url(
r'^{0}/?$'.format(User.USERNAME_FIELD),
djsoer_views.SetUsernameView.as_view(),
name='set_username'
),
url(r'^password/?$', djsoer_views.SetPasswordView.as_view(), name='set_password'),
url(
r'^password/reset/?$',
djsoer_views.PasswordResetView.as_view(),
name='password_reset'
),
url(
r'^password/reset/confirm/?$',
djsoer_views.PasswordResetConfirmView.as_view(),
name='password_reset_confirm'
),
url(r'^$', djsoer_views.RootView.as_view(), name='root'),
url(r'^', include(router.urls)), ### If you want to add user view set
]
knox_urlpatterns = [
url(r'login/', knox_views.LoginView.as_view(), name='knox_login'),
url(r'logout/', knox_views.LogoutView.as_view(), name='knox_logout'),
url(r'logoutall/', knox_views.LogoutAllView.as_view(), name='knox_logoutall'),
]
urlpatterns = knox_urlpatterns + djoser_urlpatterns
Now we are going to add this urls under our main_app's urls
from django.urls import path
from django.conf import settings
auth_urls = include('auth.urls')
urlpatterns = [
path('api/auth/', auth_urls),
......
]
Now we are going to able to access every end-point like login as api/auth/login/ or user-create as api/auth/user/create/ etc.
What i can see that djoser add some additional UserViewset end-point by default mostly you may not like this , you should include what is really needed for you.

Resources