DRF - how to reverse a class based view in api_root - django

In Django REST Framework, I'm trying to add an API root to a views.py that has class based views.
Error:
$ http http://127.0.0.1:8000/api/
Error - django.urls.exceptions.NoReverseMatch: Reverse for 'SnippetListView' not found. 'SnippetList' is not a valid view function or pattern name.
backend/views.py
from backend.models import *
from backend.serializers import *
from rest_framework import generics
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework.reverse import reverse
#api_view(['GET'])
def api_root(request, format=None):
return Response({
'snippets': reverse('SnippetList')
# 'snippets': reverse('SnippetListView')
# 'snippets': reverse('snippet-list')
# 'snippets': reverse('snippet_list')
})
class SnippetList(generics.ListCreateAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
backend/urls.py
from backend import views
from django.urls import path, include
from rest_framework.urlpatterns import format_suffix_patterns
urlpatterns = [
path('', views.api_root),
path('snippets/', views.SnippetList.as_view()),
path('snippets/<int:pk>/', views.SnippetDetail.as_view()),
]
Docs:
https://www.django-rest-framework.org/tutorial/5-relationships-and-hyperlinked-apis/#creating-an-endpoint-for-the-highlighted-snippets

You need to name the view url in order to use the reverse.
#urls.py
path('snippets/', views.SnippetList.as_view(), name='snippet-list'),
#views.py
'snippets': reverse('snippet-list', request=request, format=format)
The tutorial did not originally give names to the urls of the class based views.

Related

Converting to using URL router is causing a ImproperlyConfigured exception

I have been working through the Django Rest Framework tutorial and on the very last step I am encountering the error:
Exception Type: ImproperlyConfigured.
Exception Value:
Could not resolve URL for hyperlinked relationship using view name "snippet-detail". You may have failed to include the related model in your API, or incorrectly configured the lookup_field attribute on this field.
When trying to view either /settings/ or /users/ (visiting any user pages yields the same exception but with "user-detail" in place of "snippet-detail") as well as any specific indices of them, etc. All that works is root and login.
All my code thus far has been working fine and I'm very confused as to why copy-pasting from the tutorial would yield such catastrophic results
In comparing my snippets files with those available on the tutorial's repo I have not been able to find any significant difference (all that I've found is inconsistencies in whitespace). That being said, here is the code I'm using.
snippets/views.py:
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer, UserSerializer
from rest_framework import generics, permissions
from django.contrib.auth.models import User
from snippets.permissions import IsOwnerOrReadOnly
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework.reverse import reverse
from rest_framework import renderers
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework import permissions
from rest_framework import viewsets
class UserViewSet(viewsets.ReadOnlyModelViewSet):
"""
This viewset automatically provides `list` and `retrieve` actions.
"""
queryset = User.objects.all()
serializer_class = UserSerializer
class SnippetViewSet(viewsets.ModelViewSet):
"""
This viewset automatically provides `list`, `create`, `retrieve`,
`update` and `destroy` actions.
Additionally we also provide an extra `highlight` action.
"""
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly]
#action(detail=True, renderer_classes=[renderers.StaticHTMLRenderer])
def highlight(self, request, *args, **kwargs):
snippet = self.get_object()
return Response(snippet.highlighted)
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
snippets/urls.py:
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from snippets import views
# Create a router and register our viewsets with it.
router = DefaultRouter()
router.register(r'snippets', views.SnippetViewSet,basename="snippets")
router.register(r'users', views.UserViewSet,basename="users")
# The API URLs are now determined automatically by the router.
urlpatterns = [
path('', include(router.urls)),
]
snippets/serializers.py:
from django.contrib.auth.models import User
from rest_framework import serializers
from snippets.models import Snippet
class SnippetSerializer(serializers.HyperlinkedModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')
highlight = serializers.HyperlinkedIdentityField(
view_name='snippet-highlight', format='html')
class Meta:
model = Snippet
fields = ('url', 'id', 'highlight', 'owner', 'title', 'code',
'linenos', 'language', 'style')
class UserSerializer(serializers.HyperlinkedModelSerializer):
snippets = serializers.HyperlinkedRelatedField(
many=True, view_name='snippet-detail', read_only=True)
class Meta:
model = User
fields = ('url', 'id', 'username', 'snippets')
tutorial/urls.py:
"""tutorial URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/4.0/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('snippets.urls')),
]
urlpatterns += [
path('api-auth/', include('rest_framework.urls')),
]
Router was being passed the wrong base names (plural forms of snippet and user rather than singular). Thanks to #IainShelvington for the answer in the comments!
To elaborate:
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from snippets import views
# Create a router and register our viewsets with it.
router = DefaultRouter()
router.register(r'snippets', views.SnippetViewSet,basename="snippets")
router.register(r'users', views.UserViewSet,basename="users")
# The API URLs are now determined automatically by the router.
urlpatterns = [
path('', include(router.urls)),
]
should have been
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from snippets import views
# Create a router and register our viewsets with it.
router = DefaultRouter()
router.register(r'snippets', views.SnippetViewSet,basename="snippet")
router.register(r'users', views.UserViewSet,basename="user")
# The API URLs are now determined automatically by the router.
urlpatterns = [
path('', include(router.urls)),
]

URL not resolved with APIView in Django REST Framework for non-model end point

I'm using Django Rest Framework to create a non-model API endpoint, but I'm a having a bit of trouble setting it up. Below is my code.
views.py
from rest_framework import views, viewsets
from rest_framework.response import Response
from myproject.apps.policies.models import Customer
from .serializers import CustomerSerializer
class CustomerViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Customer.objects.all()
serializer_class = CustomerSerializer
class CalculateQuoteView(views.APIView):
def get(self, request, *args, **kwargs):
print('Just a random test.')
return Response({"success": True, "content": "Hello World!"})
My url.py file:
from django.conf.urls import include, url
from .policies import views as policies_views
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register('policies', policies_views.CustomerViewSet)
#urlpatterns = router.urls
urlpatterns = [
url(r'^quote/', policies_views.CalculateQuoteView.as_view()),
url(r'^', include(router.urls)),
]
Using curl for testing:
curl -X GET http://localhost:8000/api/v1/policies/quote/ -H 'Authorization: Token 8636c43eb7a90randomtokenhere5c76555e93d3'
I get the following output:
{"detail":"Not found."}
Basically, in the end I will need to pass in details to the quote API endpoint and get some response data. Is there something I'm missing?
You should query:
curl -X GET http://localhost:8000/api/v1/quote/
or alter the urls as:
url(r'^policies/quote/', policies_views.CalculateQuoteView.as_view()),

can i use DefaultRouter with CreateAPIView in django rest?

when I try to add my CreateAPIView to router.register it rise TypeError exception:
File "/home/denys/.virtualenvs/buddha_test/lib/python3.5/site-packages/rest_framework/routers.py", line 281, in get_urls
view = viewset.as_view(mapping, **route.initkwargs)
TypeError: as_view() takes 1 positional argument but 2 were given
But if I add url directly to urlpatterns it works!
The resone is that I whant to see link in API Root:
enter image description here
So quation is can I write something like this:
urls.py
from django.conf.urls import url, include
from . import views
from rest_framework import routers
router = routers.DefaultRouter()
router.register(r'clients-list', views.ClientList)
router.register(r'managers-list', views.ManagerList)
router.register(r'clients', views.CleintCreate, base_name='create')
urlpatterns = [
url(r'^', include(router.urls)),
]
views.py
from .models import Client, Manager
from .serializers import ClientSerializer, ManagerSerializer
from rest_framework import generics
from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated, AllowAny
from rest_framework.generics import CreateAPIView
from rest_framework.decorators import detail_route
class ClientList(viewsets.ModelViewSet):
permission_classes = (IsAuthenticated, )
queryset = Client.objects.all()
serializer_class = ClientSerializer
class ManagerList(viewsets.ReadOnlyModelViewSet):
permission_classes = (IsAuthenticated, )
#
queryset = Manager.objects.all()
serializer_class = ManagerSerializer
class CleintCreate(CreateAPIView):
model = Client
serializer_class = ClientSerializer
permission_classes = (AllowAny,)
Instead of implementing CreateAPIView, you can create GenericViewSet and also inherit CreateModelMixin:
views.py:
# ...
class ClientCreate(CreateModelMixin, GenericViewSet):
model = Client
serializer_class = ClientSerializer
permission_classes = (AllowAny,)
And then in your urls.py it's just the same:
from django.conf.urls import url, include
from rest_framework import routers
from . import views
router = routers.DefaultRouter()
router.register(r'clients-list', views.ClientList)
router.register(r'managers-list', views.ManagerList)
router.register(r'clients', views.ClientCreate, base_name='create')
urlpatterns = [
url(r'^', include(router.urls)),
]

How to accommodate APIView & ViewSet views in urls.py

How would one write a urls.py file to accommodate views created from APIView and ViewSet.
entity.views.py
from .models import Entity
from .serializers import EntitySerializer
class EntityViewSet(DefaultsMixin, ListCreateRetrieveUpdateViewSet):
"""
"""
queryset = Entity.objects.all()
serializer_class = EntitySerializer
filter_fields = ('id', 'entity_number')
class PersonProfileList(APIView):
"""
person profile
"""
def get(self, request, format=None):
pass
entity.urls.py
from django.conf.urls import url, include
from rest_framework.routers import DefaultRouter
from rest_framework.urlpatterns import format_suffix_patterns
from . import views
entity_router = DefaultRouter()
entity_router.register(r'entity', views.EntityViewSet)
urlpatterns = [
url(r'profile/$', views.PersonProfileList.as_view(), name='profile_list'), # Is this correct?
url(r'profile/(?P<pk>[0-9]+)/$', views.PersonProfileList.as_view(), name='profile_detail'),
]
urlpatterns = format_suffix_patterns(urlpatterns)
main urls.py
from django.conf.urls import include, url
from django.contrib import admin
from entities.urls import entity_router, urlpatterns
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^entities/', include(entity_router.urls)), #This I know works
url(r'^entities/', include(urlpatterns.url)), # This throws errors
]
What is the best way to accommodate both types of Views in the same URL file and have them appear under one /entity unlike now when am getting two /entity entries. Also, once I get into the /entity page in the browsable API, how do I make the /entity/profile viewable since now it only shows /entity. See images for guide.
Root Page
Entities Page

django, name 'IndexView' is not defined

I am following this tutorial. At the moment I am at this point but when I start my server with python manage.py runserver 0.0.0.0:8000 and open the url in my browser, I receive following Error:
name 'IndexView' is not defined
This is my urls.py
from django.conf.urls import include, url
from django.contrib import admin
from django.conf.urls import patterns
from rest_framework_nested import routers
from authentication.views import AccountViewSet
router = routers.SimpleRouter()
router.register(r'accounts', AccountViewSet)
urlpatterns = patterns(
'',
url(r'^admin/', include(admin.site.urls)),
url(r'^api/v1/', include(router.urls)),
url('^.*$', IndexView.as_view(), name='index'),
)
I don't know how to solve this problem, since I never saw myself even declaring this IndexView somewhere. It would be awesome if you guys could give me some suggestions on this one.
Edit:
my views.py
from django.shortcuts import render
# Create your views here.
from rest_framework import permissions, viewsets
from authentication.models import Account
from authentication.permissions import IsAccountOwner
from authentication.serializers import AccountSerializer
class AccountViewSet(viewsets.ModelViewSet):
lookup_field = 'username'
queryset = Account.objects.all()
serializer_class = AccountSerializer
def get_permissions(self):
if self.request.method in permissions.SAFE_METHODS:
return (permissions.AllowAny(),)
if self.request.method == 'POST':
return (permissions.AllowAny(),)
return (permissions.IsAuthenticated(), IsAccountOwner(),)
def create(self, request):
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
Account.objects.create_user(**serializer.validated_data)
return Response(serializer.validated_data, status=status.HTTP_201_CREATED)
return Response({
'status': 'Bad request',
'message': 'Account could not be created with received data.'
}, status = status.HTTP_400_BAD_REQUEST)
You have to create that IndexView and import it in your urls.py.
Currently the interpreter complains since in the urls.py IndexView is unknown.
To create a new view you should create a new class in views.py, something like:
from django.views.generic.base import TemplateView
class IndexView(TemplateView):
template_name = 'index.html'
ps: please read the official Django docs, which is very good!
The IndexView class is in the boilerplate project's views file.
C:\...\thinkster-django-angular-boilerplate-release\thinkster_django_angular_boilerplate\views
Copy and paste that content into your project's views file.
from django.views.decorators.csrf import ensure_csrf_cookie
from django.views.generic.base import TemplateView
from django.utils.decorators import method_decorator
class IndexView(TemplateView):
template_name = 'index.html'
#method_decorator(ensure_csrf_cookie)
def dispatch(self, *args, **kwargs):
return super(IndexView, self).dispatch(*args, **kwargs)
in your urls.py
from .views import IndexView
url('^.*$', IndexView.as_view(), name='index'),
(.views or yourProject.views)
in your views.py
do what daveoncode wrote