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

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

Related

Django Rest Framework - How to implement a view to return a javascript file?

I need to serve/return javascript files from a Django Rest Framework api, so in any other website client I can do:
<script src="https://myserveraddress.com/django-api/my-view/my-js-file.js"></script>
and import the content from it to my clients websites by coding this tag on them.
How can i do the view and the url pattern for it?
I want to use DRF because of the CORS policy.
Thanks in advance!
use django HttpResponse and return it
return HttpResponse("<script src="https://myserveraddress.com/django-api/my-view/my-js-file.js"></script>",content_type="application/x-javascript")
HttpResponse comes from django -> from django.http import HttpResponse
Thanks, #mehran heydarian for your contribution.
With your tip and looking inside the rest_framework views module, using views.APIView, I have solved my problem and implemented both the view and the url. Additionaly to the view I have implemented a filter, so I can pass an argument on the url to be filtered on a query:
# views.py
from django.http import HttpResponse
from rest_framework import views
from . models import MYMODEL,
class MYMODELApiView(views.APIView):
#classmethod
def get_extra_actions(cls):
return []
#api_view(('GET',))
def my_def(request, some_string):
"""
some code to implement a condition
for validation
"""
def get_queryset(some_string):
"""
This view returns a string.
"""
if condition:
res = MYMODEL.objects.filter(some_field_from_model=some_string).values('file')
the_file = open('/myserver/directory_before_static/' + res[0]['file'], 'r')
file_content = the_file.read()
the_file.close()
return file_content
else:
the_file = open('/myserver/directory_before_static/static/another_directory/another.js', 'r')
file_content = the_file.read()
the_file.close()
return file_content
file_content = get_queryset(some_string)
return HttpResponse(file_content, content_type="application/javascript", charset='utf-8')
# urls.py
from django.urls import re_path
from .views import MYMODELApiView
urlpatterns = [
...
re_path('^my_url/(?P<some_string>.+)/$', MYMODELApiView.my_def, name='my_url'),
...
]
This works fine to me.

reverse() in a test only returns relative URL in a test for django-rest-framework and that causes a 404

I am trying to test the endpoints for my API by using this guide. Specifically, this block is supposed to test the get request:
class GetAllPuppiesTest(TestCase):
""" Test module for GET all puppies API """
def setUp(self):
Puppy.objects.create(
name='Casper', age=3, breed='Bull Dog', color='Black')
Puppy.objects.create(
name='Muffin', age=1, breed='Gradane', color='Brown')
Puppy.objects.create(
name='Rambo', age=2, breed='Labrador', color='Black')
Puppy.objects.create(
name='Ricky', age=6, breed='Labrador', color='Brown')
def test_get_all_puppies(self):
# get API response
response = client.get(reverse('get_post_puppies'))
# get data from db
puppies = Puppy.objects.all()
serializer = PuppySerializer(puppies, many=True)
self.assertEqual(response.data, serializer.data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
When I try to adapt this to my own test, it looks like this:
from ..models import DemanderFeature, DemanderFeatureCollection
from rest_framework import status
from django.test import TestCase, Client
from django.urls import reverse
from ..serializers import DemanderFeatureCollectionSerializer
class GetAllDemanderFeatureCollections(TestCase):
def setUp(self):
DemanderFeatureCollection.objects.create(name='testdemanderfeaturecollection0')
DemanderFeatureCollection.objects.create(name='testdemanderfeaturecollection1')
def test_get_all_demandercollections(self):
# get API response
response = client.get(reverse('demandercollections-list'))
# get data from db
demanderfeaturecollections = DemanderFeatureCollection.objects.all()
serializer = DemanderFeatureCollectionSerializer(demanderfeaturecollections, many=True)
self.assertEqual(response.data, serializer.data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
The problem is however the reverse() method is only returning the relative URL, (/demandercollections/) and then client.get(reverse(...)) returns a 404. I don't understand how I can force it to use the actual explicit URL during testing.
I'm using Django 3.
My main urls.py looks like this:
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include("app.urls")),
path('api-auth/', include('rest_framework.urls')),
]
And my module urls.py looks like this:
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from app import views
# Create a router and register our viewsets with it.
router = DefaultRouter()
router.register(r'demanders', views.DemanderFeatureViewSet)
router.register(r'demandercollections', views.DemanderFeatureCollectionViewSet, basename="demandercollections")
router.register(r'producers', views.ProducerFeatureViewSet)
router.register(r'producercollections', views.ProducerFeatureCollectionViewSet)
router.register(r'pathfinderrunconfigurations', views.PathfinderRunConfigurationViewSet)
router.register(r'users', views.UserViewSet)
# The API URLs are now determined automatically by the router.
urlpatterns = [
path('', include(router.urls)),
]
The DemanderCollectionViewSet in views.py looks like this:
class DemanderFeatureCollectionViewSet(
mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.ListModelMixin,
viewsets.GenericViewSet
):
queryset = DemanderFeatureCollection.objects.all()
serializer_class = DemanderFeatureCollectionSerializer
lookup_field = 'name'
#action(detail=True, methods=["get"])
def geojson(self, request, *args, **kwargs):
demanders = DemanderFeature.objects.filter(demandercollection=self.get_object())
return Response(serialize('geojson', demanders, geometry_field='geom', fields=('name',)))
#action(detail=True, methods=["patch"])
def commit(self, request, *args, **kwargs):
demandercollection = self.get_object()
if not request.data["committed"]:
# User is trying to "uncommit", do not allow this
return Response("You may not un-commit a DemanderCollection. You must copy it and make modifications on the copy.", status=status.HTTP_400_BAD_REQUEST)
demandercollection.committed = True
demandercollection.save()
return Response(status=status.HTTP_204_NO_CONTENT)
def get_queryset(self):
user = get_object_or_404(User, username=self.request.user)
return DemanderFeatureCollection.objects.filter(deleted=False).filter(owner=user).order_by("name")
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
def destroy(self, request, *args, **kwargs):
demandercollection = self.get_object()
demandercollection.deleted = True
demandercollection.save()
return Response(f"Successfully deleted DemanderCollection.")
POST ANSWER EDIT
not only was the accepted answer indeed the culprit, but it also revealed that the DemanderFeatureCollection objects being created must also be created with an owner attribute, and the client object must call its login() method to a valid user credential pair.
The test class therefore had to be updated to look like this:
class GetAllDemanderFeatureCollections(TestCase):
""" Test module for GET all puppies API """
def setUp(self):
self.test_user = User.objects.create_user('test_user', 'a#b.com', 'test_user')
self.other_user = User.objects.create_user('other_user', 'a#b.com', 'other_user')
client.login(username="test_user", password="test_user")
DemanderFeatureCollection.objects.create(name='testdemanderfeaturecollection0', owner=self.test_user)
DemanderFeatureCollection.objects.create(name='testdemanderfeaturecollection1', owner=self.test_user)
DemanderFeatureCollection.objects.create(name='otherdemanderfeaturecollection0', owner=self.other_user)
def test_get_all_demandercollections_for_user(self):
# get API response
response = client.get(reverse('demandercollections-list'))
# get data from db
demanderfeaturecollections = DemanderFeatureCollection.objects.filter(owner=self.test_user).all()
serializer = DemanderFeatureCollectionSerializer(demanderfeaturecollections, many=True)
self.assertEqual(response.data, serializer.data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
In get_queryset() method of DemanderFeatureCollectionViewSet class you are filtering the model instances with owner field against the logged-in user.
In your test-cases, you are creating the DemanderFeatureCollection instances without linking the user and hence DRF raising an HTTP 404 error. So, attaching the user to the instance and making the request with the same user will give you a proper response from the API.

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

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

DRF - Get not allowed

I have the following function (showing imports just for the sake of completeness):
from django.utils import timezone
from rest_framework import status
from rest_framework.response import Response
from rest_framework.authtoken.models import Token
from rest_framework.authtoken.views import ObtainAuthToken
class RefreshAuthToken(ObtainAuthToken):
def post(self, request):
key = request.query_params.get('auth_token').strip()
try:
token = self.model.objects.get(key=key)
token.delete()
token = Token.objects.create(user=serializer.object['user'])
token.created = datetime.datetime.utcnow().replace(tzinfo=utc)
token.save()
return Response({'token': token.key})
except self.model.DoesNotExist:
return Response("Error", status=status.HTTP_400_BAD_REQUEST)
When I visit the url with /?auth_token=619f853ac32e171facb3068c990c6eded81a59c9, I get an error:
{"detail":"Method \"GET\" not allowed."}
How can I solve this?
You should request via POST or change the function name to get.

Resources