I'm making a REST API with Django Rest Framework (DRF) which has the following endpoints:
/users/
/users/<pk>/
/items/
/items/<pk>/
but I'd like to add the endpoint:
/users/<pk>/items/
which would of course return the items that belong (have a foreign key) to that user.
Currently my code is:
#########################
##### myapp/urls.py #####
#########################
from django.conf.urls import url, include
from rest_framework.routers import DefaultRouter
from rest_framework.decorators import api_view, renderer_classes
from rest_framework import response, schemas
from rest_framework_swagger.renderers import OpenAPIRenderer, SwaggerUIRenderer
from myapp.views import ItemViewSet, UserViewSet
# Create a router and register our viewsets with it.
router = DefaultRouter()
router.register(r'users', UserViewSet)
router.register(r'items', ItemViewSet)
#api_view()
#renderer_classes([OpenAPIRenderer, SwaggerUIRenderer])
def schema_view(request):
generator = schemas.SchemaGenerator(title='My API')
return response.Response(generator.get_schema(request=request))
urlpatterns = [
url(r'^', include(router.urls)),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
]
##########################
##### myapp/views.py #####
##########################
from django.contrib.auth import get_user_model
from rest_framework import viewsets, permissions
from myapp.serializers import MyUserSerializer, ItemSerializer
from myapp.models import Item
class UserViewSet(viewsets.ReadOnlyModelViewSet):
queryset = get_user_model().objects.all()
serializer_class = MyUserSerializer
permission_classes = (permissions.IsAuthenticated,)
class ItemViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Item.objects.all()
serializer_class = ItemSerializer
permission_classes = (permissions.IsAuthenticated,)
################################
##### myapp/serializers.py #####
################################
from rest_framework import serializers
from django.contrib.auth import get_user_model
from myapp.models import Item
class MyUserSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = ('pk', 'email',)
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = ('pk', 'name',)
Is there a good way to add this endpoint in DRF, given how I'm using DRF?
I could just add a function view in urls.py like so:
from myapp.views import items_for_user
urlpatterns = [
url(r'^', include(router.urls)),
url(r'^users/(?P<pk>[0-9]+)/items/$', items_for_user),
]
but I want to leverage DRF, get the browsable API, and make use of ViewSets instead of coding one-off function views like this.
Any ideas?
Took me a while to figure this out. I've been using view sets, so I'll give this answer within that setting.
First, URLConf and registered routes remain unchanged, i.e.,
router = DefaultRouter()
router.register(r'users', UserViewSet)
router.register(r'items', ItemViewSet)
urlpatterns = [
url(r'^', include(router.urls)),
url(r'^api-auth/',
include('rest_framework.urls', namespace='rest_framework')
),
]
Your items will still be at /items/<pk>/, with permissions crafted for each of them depending on who requests them, by creating custom permissions, for example:
class IsItemOwnerPermissions(permissions.DjangoObjectPermissions):
"""
The current user is the owner of the item.
"""
def has_object_permission(self, request, view, obj):
# A superuser?
if request.user.is_superuser:
return True
# Owner
if obj.owner.pk == request.user.pk:
return True
return False
Next, for /user/<pk>/items/ you need to define #detail_route, like this:
class UserViewSet(viewsets.ReadOnlyModelViewSet):
# Your view set properties and methods
#detail_route(
methods=['GET', 'POST'],
permission_classes=[IsItemOwnerPermissions],
)
def items(self, request, pk=None):
"""
Returns a list of all the items belonging to `/user/<pk>`.
"""
user = get_user_model().objects.get(pk=pk)
items = user.items.all()
page = self.paginate_queryset(items)
if page is None:
serializer = ItemSerializer(
objs, context={'request': request}, many=True
)
return Response(serializer.data)
else:
serializer = ItemSerializer(
page, context={'request': request}, many=True
)
return self.get_paginated_response(serializer.data)
A detailed route named xyz corresponds to the route user/<pk>/xyz. There are also list routes (#list_route); one named xyz would correspond to user/xyz (for example user/add_item).
The above structure will give you: /user, /user/<pk>, user/<pk>/items, /items, and /items/<pk>, but not (as I wrongly tried to achieve) user/<user_pk>/items/<items_pk>. Instead, user/<pk>/items will give you a list of user's items, but their individual properties will still be accessible only via /items/<pk>.
I just got this to work on my project, and the above code is a quick adaptation to your case. I hope it helps you, but there might still be problems there.
Update: What you wanted can be done using custom hyperlinked fields. I didn't try it (yet), so I cannot say how to use these, but there are nice examples on that link.
Related
In my views, queryset is returning all the users when I want it to be only returning the user that is currently logged. I have a get self method which has the serializer set to the user but it is not being used. When I tried get_queryset, self.request.user still doesn't return the user.
views.py:
from rest_framework import viewsets
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
from rest_framework import status
from rsm_app.api.v1 import serializer as serializers
from rsm_app.users.models import User
class CurrentUserView(viewsets.ModelViewSet):
permission_classes = (IsAuthenticated,)
serializer_class = serializers.UserSerializer
#queryset = User.objects.filter(name=request.user.name)
def get_queryset(self):
return self.request.user
def put(self, request):
serializer = serializers.UserSerializer(
request.user, data=request.data)
if request.data and serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response({}, status=status.HTTP_400_BAD_REQUEST)
Url.py:
from rest_framework import routers
from django.urls import path, re_path, include
from graphene_django.views import GraphQLView
from rsm_app.api.v1 import views
app_name = "api.v1"
# Routers provide an easy way of automatically determining the URL conf.
router = routers.DefaultRouter()
router.register(r"user", views.CurrentUserView, basename="user")
# Wire up our API using automatic URL routing.
# Additionally, we include login URLs for the browsable API.
urlpatterns = [
path("graphql", GraphQLView.as_view(graphiql=True)),
re_path(r"^", include(router.urls)),
re_path(r"user/", views.CurrentUserView, name='user'),
re_path(r"^api-auth/", include("rest_framework.urls",
namespace="rest_framework")),
]
Edit FIXED: It was a session token not being saved issue.
They mentioned in the docs that you could access the current user by user = self.get_object()
You could see the full example from the official docs.
Can you try with:
queryset = User.objects.filter(name=request.user.name).first()
or
queryset = User.objects.get(name=request.user.name)
I am writting an application in Django that uses the Django Rest framework. A GET request to the API URL works, but when I add a query to it, such as '?id=1845' it does not actually perform a query, it still returns the full list of results.
In the below code, I am trying to query the 'Indicator List'
views.py
from django.shortcuts import render
from django.http import JsonResponse
from rest_framework.decorators import api_view
from rest_framework.response import Response
from .serializers import IndicatorSerializer
from data_platform.models import Indicator
#api_view(['GET'])
def apiOverview(request):
api_urls = {
'Indicator List':'/indicator-list/',
'Indicator Detail':'/indicator-detail/<str:pk>/',
}
return Response(api_urls)
#api_view(['GET'])
def indicatorList(request):
indicators = Indicator.objects.all()
serializer = IndicatorSerializer(indicators, many=True)
#filterset_fields = ('id')
#filter_backends = [DjangoFilterBackend]
return Response(serializer.data)
#api_view(['GET'])
def indicatorDetail(request, pk):
indicators = Indicator.objects.get(id=pk)
serializer = IndicatorSerializer(indicators, many=False)
return Response(serializer.data)
urls.py
from django.urls import include, path
from . import views
urlpatterns = [
path('', views.apiOverview, name="api-overview"),
path('indicator-list/', views.indicatorList, name="indicator-list"),
path('indicator-detail/<str:pk>/', views.indicatorDetail, name="indicator-detail"),
]
serializers.py
from rest_framework import serializers
from data_platform.models import Indicator
class IndicatorSerializer(serializers.ModelSerializer):
class Meta:
model = Indicator
fields = '__all__'
I figured it out! I had to use a class view rather then a function view
class indicatorList(generics.ListAPIView):
queryset = Indicator.objects.all()
serializer_class = IndicatorSerializer
filter_backends = [django_filters.rest_framework.DjangoFilterBackend]
filter_fields = ('id',)
and add 'django_filters', to INSTALLED_APPS
This question is based on the one here. I am setting up a Django REST Framework for my web app and am trying to set up User accounts. Based on the REST documentation, they put all of their account code in their example in the main project directory and a separate application so did that as well. Here is what I have:
urls.py
from django.contrib import admin
from django.urls import include, path
from django.conf.urls import url
from rest_framework import routers
from . import views
router = routers.DefaultRouter()
router.register('users', views.UserViewSet)
urlpatterns = [
path('admin/', admin.site.urls),
url('', include(router.urls)),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
]
serializers.py
from django.contrib.auth.models import User
from rest_framework import serializers
class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
def create(self, validated_data):
user = User.objects.create(
username=validated_data['username']
)
user.set_password(validated_data['password'])
user.save()
return user
class Meta:
model = User
# Tuple of serialized model fields (see link [2])
fields = ( "id", "username", "password", )
views.py
from rest_framework import viewsets, permissions
from rest_framework.generics import CreateAPIView
from django.contrib.auth.models import User
from .serializers import UserSerializer
# Create your views here.
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = [permissions.IsAuthenticated]
class CreateUserView(CreateAPIView):
model = User
permission_classes = [
permissions.AllowAny
]
serializer_class = UserSerializer
I have tried using the Boomerang REST Client in Chrome to POST data to this API, but it always returns a 403 Error saying "Invalid username/password." Specifically I am POSTing to http://127.0.0.1:8000/users/create/ with a Query String and 2 parameters: username and password. I also tried sending it as JSON and it returned the same. Any help would be appreciated.
It doesn't look like CreateUserView was registered in your urls.py. You should be able to register it and access it normally. I think this should work for you:
urlpatterns = [
...
url(r'^users/create/', views.CreateUserView.as_view()),
]
That said, I'd like to suggest adding an extra action for your UserViewSet instead:
# Create your views here.
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = [permissions.IsAuthenticated
#action(methods=['post'], detail=False, permission_classes=[permissions.AllowAny])
def register(self, request, *args, **kwargs):
# This logic was taken from the `create` on `ModelViewSet`. Alter as needed.
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
Then you should be able to post via /users/register/. You can also specify your own url name and path on the decorator.
Maybe you are posting in the wrong url, try POST the same on http://127.0.0.1:8000/users/,
because ModelViewSet adds POST, PATCH, PUT, DELETE and GET methods automatically.
Also because you are asking for authentication (permission_classes = [permissions.IsAuthenticated]), you should send the headers for this in the request. There is a tutorial for this in the DRF site (https://www.django-rest-framework.org/tutorial/4-authentication-and-permissions/)
based on django-rest-framework documents it's better to use viewset for create user api. therefor you need to send a POST request to http://127.0.0.1:8000/api-auth/users and no need to CreateUserView function.
But if you want to have a custom user create api do you need something like below:
class UserViewSet(viewsets.ModelViewSet):
"""
A viewset that provides the standard actions
"""
queryset = User.objects.all()
serializer_class = UserSerializer
#action(detail=True, methods=['post'], permission_classes=[permissions.AllowAny])
def create_user(self, request, pk=None):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
To have custom serializers in your ViewSet you can specify them in get_serializer_class function in your ViewSet like this:
class UserViewSet(viewsets.ModelViewSet):
# example viewset
def get_serializer_class(self):
if self.action == 'list':
return ListUserSerializer
elif self.action == 'create':
return CreateUserSerializer
elif self.action == 'update':
return UpdateUserSerializer
return DetailUserSerializer
I am trying to create a Restful Api for following methods to run jenkins jobs to run on saucelabs. I wanna queue jobs using restful API. I am using Django Restful Framework.
CreateMethod :
Accepts two fileds: ProjectName and URL
and returns a Token ID.
VerifyStatus:
Accepts Token ID and returns three fields. TokenID, running:True/False and
no_of_jobs: integervalue (0 if Not specified)
relseaseMethod:
Accepts release token call and returns true if success.
I am new to Restful API with, I am trying to run Jenkins job on sauce lab and queue them using a restful api on python Djano restframework. Guide me through.
Views.py
class RegisterTestMethodView(APIView):
authentication_classes = [SessionAuthentication, TokenAuthentication, BasicAuthentication]
permission_classes = [IsAuthenticated] #No access (not even read if not authorized)
def post(self, request, format=None):
serializer = RegisterTestMethodSerializers(data=request.data)
if serializer.is_valid():
serializer.save()
return Response({'tokenid':serializer.data['id']}, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class CheckStatusView(APIView):
def get_object(self, pk):
try:
return Jobs.objects.get(pk=pk)
except Jobs.DoesNotExist:
raise Http404
def get(self, request, pk, format=None):
snippet = self.get_object(pk)
serializer = RegisterTestMethodSerializers(snippet)
return Response({"tokenid":serializer.data["id"], "Runtestnow" : False, "VMcount" : 0 })
class ReleaseTokenView(APIView):
def get_object(self, pk):
try:
return Jobs.objects.get(pk=pk)
except Jobs.DoesNotExist:
raise Http404
def delete(self, request, pk, format=None):
snippet = self.get_object(pk)
snippet.delete()
return Response(data={'Deleted':True}, status=status.HTTP_204_NO_CONTENT)
Serailizers.py
rom rest_framework import serializers
from .models import Jobs
from random import random
RegisterTestMethodSerializers(serializers.HyperlinkedModelSerializer):
class Meta:
model = Jobs
fields = ('id','name','url')
Models.py
from django.db import models
# Create your models here.
class Jobs(models.Model):
name = models.CharField(max_length=100)
url = models.URLField()
def __str__(self):
return self.name
Urls.py
from django.urls import path, include
from . import views
from .views import (RegisterTestMethodView,
RegisterTestMethodViewDetail,
CheckStatusView,
ReleaseTokenView
)
from rest_framework import routers
from rest_framework.authtoken.views import obtain_auth_token
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
urlpatterns = [
path('', include(router.urls)),
path('registertestmethod/',RegisterTestMethodView.as_view()),
path('registertestmethod/<int:pk>/',
RegisterTestMethodViewDetail.as_view()),
path('checkstatus/<int:pk>', CheckStatusView.as_view()),
path('releasetoken/<int:pk>', ReleaseTokenView.as_view()),
]
I have addded my Code here. I have other classes and function in my project as well.I tried to delete all of those. You might see extra import as a result of that. My code does following things.
POST --> http://localhost:8000/registertestmethod/
{
"name": "Name",
"url": "https://www.google.com"
}
returns
{
"tokenid": 32 #unique token ID return
}
This tokenid should be long I am using id as of now.
GET --> http://localhost:8000/checkstatus/32
is returning
{
"tokenid": 32, #unique tokenid refering to register info
"Runtestnow": false, #if job is running
"VMcount": 0 # number of VM used in sauce lab by the Jobs
}
DELETE --> http://localhost:8000/releasetoken/32 #should delete the jobs after done.
is deleting and returning
{
"Deleted": true
}
I want it to be dynamic and store info in database. Token should return everything on checkstatus.
You can use the ModelViewSet approach for this, this here is a very simple example for an API endpoint.
views.py
from rest_framework.viewsets import ModelViewSet
from api.serializers import SaucelabsSerializer
from rest_framework.response import Response
class SaucelabModelViewSet(ModelViewSet):
serializer_class = SaucelabSerializer
queryset = Sauselab.objects.all()
http_method_names = ['get', 'head', 'options', 'post']
def create(self, request):
pnam = request.data.get('project_name', None)
url = request.data.get('url', None)
if pnam and url:
# do something here
return Response({'success': 'Your success message'}, status=status.HTTP_200_OK)
else:
return Response({"error": "Your error message"}, status=status.HTTP_400_BAD_REQUEST)
serializers.py
from rest_framework.serializer import ModelSerializer
from appname.models import Saucelab
class SaucelabSerializer(ModelSerializer):
class Meta:
model = Saucelab
fields = '__all__'
appname/models.py
from django.db import models
class Saucelab(models.Model)
project_name = models.CharField(max_length=255)
url = models.URLField()
urls.py
from rest_framework.routers import DefaultRouter
from api import views
router = DefaultRouter()
router.register('your-endpoint-name', views.SaucelabModelViewSet, basename='your-url-name')
urlpatterns = []
urlpatterns += router.urls
This is a very basic example, where you create a model called saucelab which has the two fields you require, ie.project_name and url.
We create a app called api with two files inside it that aren't auto generated, serializers.py and urls.py. We create the most basic model serializer and ask it to serialize all fields of model Saucelab. Then we create a simple modelviewset which out of the box gives you a CRUD functionality. You can override the create method if you need to run some specific conditions otherwise dont override any methods and just make request to the endpoint with appropriate HTTP methods.
Here are a few links you can read
https://www.django-rest-framework.org/api-guide/viewsets/#modelviewset
https://www.django-rest-framework.org/api-guide/serializers/#modelserializer
To generate a random token
from django.utils.crypto import get_random_string
print(get_random_string(length=25))
output
u'rRXVe68NO7m3mHoBS488KdHaqQPD6Ofv'
I am using the Django REST Framework toolkit with Django 1.11 and I am trying to filter the results against the url. Here is my setup:
models.py:
from django.db import models
class Package(models.Model):
name = models.CharField(max_length=255, unique=True)
def __str__(self):
return self.name
serializers.py:
from rest_framework import serializers
from .models import Package
class PackageSerializer(serializers.ModelSerializer):
class Meta:
model = Package
fields = ('name',)
views.py:
from rest_framework import viewsets
from .models import Package
from .serializers import PackageSerializer
class PackageViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = PackageSerializer
queryset = Package.objects.all()
urls.py
from django.conf.urls import url, include
from django.contrib import admin
from rest_framework import routers
router = routers.DefaultRouter()
router.register(r'package', views.PackageViewSet)
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^api/v1/', include(router.urls)),
]
Currently when I use this I can filter the results by the id field:
http://127.0.0.1:8000/api/v1/package/1/
I am hoping to filter the results by the name field of my package model instead by using this:
http://127.0.0.1:8000/api/v1/package/basic/
How would I be able to accomplish this?
Set the lookup_field attribute in the viewset, see the documentation.
class PackageViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = PackageSerializer
queryset = Package.objects.all()
lookup_field = 'name'
Use filter_fields in views.
filter_fields = ('name',)
lookup field is used to set lookup by default it would be model pk
if you wish to make your URL,
my_url/filter_field/
set lookup_field = "name"
if you want search from URL like ,
my_url/?name=something
you need to set filter_fields in views.