API call to update a field of an object - python

So I have an model serializer which consists of
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'name', 'description')
This is my ViewSet
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
This is my URLs.py file:
from django.conf.urls import include, url
from rest_framework import routers
import views
router = DefaultRouter()
router.register('user', views.UserViewSet)
urlpatterns = [
url(r'^', include(router.urls)),
url(r'^login/', include('rest_framework.urls', namespace='rest_framework'))
]
Using the serializer, I can make it print out the objects inside my database. If I have the object PK/ID, I want to be able to update the field id or name of the object. Is there a way I can do that with a patch/post request using the serializer? I'm new to this so I'd love it if someone can help me out with this.
I'm thinking of just doing a POST request, then have it do this:
user = User.objects.get(id=id)
user.name = "XXXXX"
user.save()
But I want to do this using the serializer, using a PATCH request.

the below code will help to you,
**filename : views.py**
from user.models import User
from users.serializers import UserSerializer
from django.http import Http404
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
class UserList(APIView):
"""
List all users, or create a new user.
"""
def get(self, request, format=None):
users = User.objects.all()
serializer = UserSerializer(users, many=True)
return Response(serializer.data)
def post(self, request, format=None):
serializer = UserSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class UserDetail(APIView):
"""
Retrieve, update or delete a user instance.
"""
def get_object(self, pk):
try:
return User.objects.get(pk=pk)
except User.DoesNotExist:
raise Http404
def get(self, request, pk, format=None):
user = self.get_object(pk)
serializer = UserSerializer(user)
return Response(serializer.data)
def put(self, request, pk, format=None):
user = self.get_object(pk)
serializer = UserSerializer(user, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk, format=None):
user = self.get_object(pk)
user.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
**filename : urls.py**
from django.conf.urls import url
from rest_framework.urlpatterns import format_suffix_patterns
from users import views
urlpatterns = [
url(r'^users/$', views.UserList.as_view()),
url(r'^user/(?P<pk>[0-9]+)/$', views.UserDetail.as_view()),
]
urlpatterns = format_suffix_patterns(urlpatterns)
Reference : http://www.django-rest-framework.org/tutorial/3-class-based-views/

Django rest framework comes with some pre-defined concrete generic views such as UpdateAPIView, RetrieveUpdateAPIView.
First you need to create a view for user which uses one of the views which can update. The update views provides handlers for patch method on the view.
RetrieveUpdateAPIView
Used for read or update endpoints to represent a single model instance.
Provides get, put and patch method handlers.
Now, use this to create a view:
class UserDetail(generics.RetrieveUpdateAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
To access this view you need to have a url which uses user's primary key to access the user:
url(r'users/(?P<pk>\d+)/$', UserDetail.as_view(), name='api-user-detail'),
Then using PATCH call you can update the user's name.

Since you're using a ModelViewset, this capability should be built in. If you use the browsable API to navigate to /user/<pk>/, you'll see the operations you can perform on that object. By default, a ModelViewset provides list(), retrieve(), create(), update(), and destroy() capability.
http://www.django-rest-framework.org/api-guide/viewsets/#modelviewset
You can also override any or all of the provided methods, however an update of a single object is built in to DRF ModelViewsets. Use curl to try a PATCH to /user/<pk>/ with the information you'd like to update.

Related

"detail": "Method \"GET\" not allowed." Django Rest Framework

I know this question maybe a duplicate, but I have tried many solutions and could not understand any. I have followed this tutorial exactly and yet I get this error on the 'userlist' page. Everything else works just fine. Can somebody point out what the error is ?
class UserList(APIView):
"""
Create a new user. It's called 'UserList' because normally we'd have a get
method here too, for retrieving a list of all User objects.
"""
permission_classes = (permissions.AllowAny,)
http_method_names = ['get', 'head']
def post (self, request, format=None):
self.http_method_names.append("GET")
serializer = UserSerializerWithToken(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
EDIT:
urls.py
from django.urls import include, path
from classroom.views.classroom import current_user, UserList
from .views import classroom, suppliers, teachers
urlpatterns = [path('', classroom.home, name='home'),
path('current_user/', current_user),
path('users/', UserList.as_view()),
Edit:
Still getting this error,
You need to add GET endpoint url to your urls.py in order to use GET requests. GET url is missing in your urls.py, simply edit your urls.py like:
# urls.py
from django.urls import include, path
from classroom.views.classroom import current_user, UserList
from .views import classroom, suppliers, teachers
urlpatterns = [
path('', classroom.home, name='home'),
path('current_user/', current_user),
path('users/', UserList.as_view()),
path('users/<int:pk>/', UserList.as_view()),
]
And you need to implement get method in yourUserList view such as:
# views.py
class UserList(APIView):
"""
Create a new user. It's called 'UserList' because normally we'd have a get
method here too, for retrieving a list of all User objects.
"""
permission_classes = (permissions.AllowAny,)
http_method_names = ['get', 'head']
def get(self, request, format=None):
users = User.objects.all()
serializer = UserSerializerWithToken(users, many=True)
return Response(serializer.data)
def post(self, request, format=None):
self.http_method_names.append("GET")
serializer = UserSerializerWithToken(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Basically the problem is that, there is not functionality defined for GET requests in your view. So either you can add it, like this:
class UserList(APIView):
permission_classes = (permissions.AllowAny,)
http_method_names = ['get', 'head', 'post']
def get(self, request, *args, **kwargs):
serializer = UserSerializerWithToken(User.objects.all(), many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
def post (self, request, format=None):
self.http_method_names.append("GET")
serializer = UserSerializerWithToken(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Or you can subclass UserList View from ListAPIView.
FYI, permission_classes won't work with APIView. You need to use GenericAPIView or any other generic views to have those functionalities.
I had the same issue but I noticed that in my views.py, I had
renderer_class = api_settings.DEFAULT_RENDERER_CLASSES
I corrected it to :
renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
and it worked for me.
maybe you are calling 'post' from where ever you are calling, example as 'postman'
but your function is
def get:
so call as 'get' or change your function to
def post:

Validate user on update request in Django REST framework

I want to have an API where a user can update his own listings. Currently any authenticated user can update any listing which I found using Postman. I want to validate the user so that the API returns an error as a response if the user is not trying to update his own listing. Here is my code:
# serializers.py
class ListingSerializer(serializers.ModelSerializer):
class Meta:
model = Listing
fields = '__all__'
# api.py
class ListingViewSet(ModelViewSet):
permission_classes = [IsAuthenticatedOrReadOnly]
serializer_class = ListingSerializer
def get_queryset(self):
return Listing.objects.all()
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
# urls.py
router = routers.DefaultRouter()
router.register('api/listings', ListingViewSet, 'listings')
urlpatterns = router.urls
You just need to overwrite the perform_update function:
def perform_update(self, serializer):
obj = self.get_object()
if self.request.user != obj.created_by: # Or how ever you validate
raise PermissionDenied('User is not allowed to modify listing')
serializer.save()
You will need:
from django.core.exceptions import PermissionDenied
You can limit the object for all method change the main queryset. In this case if an inapropiate user try to access an invalid object the api return 404.
def get_queryset(self):
return Listing.objects.filter(owner=self.request.user)

Bulk Update data in Django Rest Framework

I am creating a Notification apps by Django Rest Framework which users can MARK AS READ ALL notification by using PATCH API in frontend. How can I Bulk Update data can do this task.
This serializer and viewset below just for PATCH only one notification object, but I want to do it all with Notifications which have field is_read = False
Edited with the right way
My Serializers:
class NotificationEditSerializer(ModelSerializer):
class Meta:
model = Notification
fields = (
'id',
'is_read'
)
My Viewset:
from rest_framework.response import Response
class NotificationListAPIView(ReadOnlyModelViewSet):
queryset = Notification.objects.all()
permission_classes = [AllowAny]
serializer_class = NotificationEditSerializer
lookup_field = 'id'
#list_route(methods=['PATCH'])
def read_all(self, request):
qs = Notification.objects.filter(is_read=False)
qs.update(is_read=True)
serializer = self.get_serializer(qs, many=True)
return Response(serializer.data)
My URL:
from rest_framework import routers
router.register(r'notifications/read_all', NotificationListAPIView)
You can try to use list_route for example:
from rest_framework.response import Response
from rest_framework.decorators import list_route
class NotificationListAPIView(ReadOnlyModelViewSet):
#YOUR PARAMS HERE
#list_route()
def read_all(self, request):
qs = Notification.objects.filter(is_read=False)
qs.update(is_read=True)
serializer = self.get_serializer(qs, many=True)
return Response(serializer.data)
the api is available by ^YOUCURRENTURL/read_all/$ more details marking-extra-actions-for-routing
NOTE! since DRF 3.10 #list_route() decorator was removed, you should use #action(detail=False) instead, I used #action(detail=False, methods=['PATCH']) to bulk patch, for example Thank you #PolYarBear

Django REST Framework - Adding 2 PKs in URL

I am designing a REST API for an application that has both companies and departments. Some number of users can be members of a company. This leads to the following API structure:
/companies/ - Can GET, POST.
/companies/<pk>/ - Can GET, POST, PUTCH, PATCH, DELETE.
/companies/<pk>/membership/ - Can GET (gives all users that are members of a company), POST.
/companies/<pk>/membership/<pk>/ - Can DELETE.
I've managed to implement the first 3 of the endpoints, but am having trouble with implementing the last one -- how do I implement an endpoint that has multiple <pk> values in the URL? Here's what I have so far:
Currently have a urls.py file in the api app that looks as follows:
...
url(r'^company', include(company_urls.company_router.urls,
namespace="company")),
...
urls.py in the company application.
from rest_framework import routers
from .views import CompanyViewSet
company_router = routers.DefaultRouter()
company_router.register(r'^', CompanyViewSet)
serializers.py file:
from rest_framework import serializers
from .models import Company, CompanyMembership
from My_App.users.models import Profile
class CompanySerializer(serializers.ModelSerializer):
class Meta:
model = Company
fields = ('pk', 'name', 'departments', 'members')
read_only_fields = ('pk', 'departments', 'members')
class CompanyMembershipSerializer(serializers.Serializer):
user = serializers.PrimaryKeyRelatedField(queryset=Profile.objects.all())
def create(self, validated_data):
pass
def delete(self, instance, validated_data):
pass
And the views.py file:
from .models import Company, CompanyMembership
from .serializers import CompanySerializer, CompanyMembershipSerializer
from My_Appc.users.models import Profile
class CompanyViewSet(viewsets.ModelViewSet):
queryset = Company.objects.all()
serializer_class = CompanySerializer
#decorators.detail_route(methods=['get', 'post', 'delete'])
def membership(self, request, pk):
company = self.get_object()
if request.method == 'GET':
serializer = CompanyMembershipSerializer(company)
elif request.method == 'POST':
serializer = CompanyMembershipSerializer(data=request.data)
if serializer.is_valid():
try:
user = Profile.objects.get(pk=request.data.get('user'))
user_company_membership = CompanyMembership(user=user,
company=company)
user_company_membership.save()
return Response({'status': 'User added to Company.'},
status=status.HTTP_201_CREATED)
except IntegrityError:
result = {
'status': 'Failed to add user to Company.',
'reason': 'User already part of Company.'
}
status=settings.ADDITIONAL_HTTP_STATUS_CODES[
'422_UNPROCESSABLE_ENTITY']
return Response(result, status)
else:
return Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
Use different names for parameters in you url:
/companies/<company_pk>/membership/<membership_pk>/
And in you ViewSet add lookup_field and lookup_url_kwarg to point to company pk field/parameter:
class CompanyViewSet(viewsets.ModelViewSet):
lookup_field = 'pk'
lookup_url_kwarg = 'company_pk'
get_object method uses this two lookups to filter queryset so you will get company based on first pk in url.
In your membership method and custom logic to manage membership object, you can access membership pk with:
membership_pk = self.kwargs.get('membership_pk', None)

Django-REST: Get User by username instead of primary-key

I am using the basic UserViewSet derived from ModelViewSet.
Retrieving a user with a primary-key via api/users/<pk> works fine.
But I also want to be able to retrieve a User by Username.
I have added a new detail route but I always get 404 on my server when I try to get the user with the url /api/users/retrieve_by_username/altoyr.
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
#detail_route(methods=['get'])
def retrieve_by_username(self, request, username=None):
try:
user = User.objects.get(userName=username)
return Response(user)
except User.DoesNotExist:
return Response("No user with username found!", status=status.HTTP_400_BAD_REQUEST)
The Urls are registered via a router:
router = DefaultRouter()
router.register(r'users', views.UserViewSet)
# The API URLs are now determined automatically by the router.
# Additionally, we include the login URLs for the browsable API.
urlpatterns = [
url(r'^', include(router.urls)),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]
I think I am missing an important part of building rest urls.
You can do this by adding a list route like:
#list_route(methods=['get'], url_path='retrieve_by_username/(?P<username>\w+)')
def getByUsername(self, request, username ):
user = get_object_or_404(User, username=username)
return Response(UserSerializer(user).data, status=status.HTTP_200_OK)
and the url will be like:
/api/users/retrieve_by_username/altoyr
You can override the retrieve() ViewSet action. You'll find more detail here: https://www.django-rest-framework.org/api-guide/viewsets/
from django.shortcuts import get_object_or_404
from rest_framework.response import Response
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
def retrieve(self, request, pk=None):
queryset = User.objects.filter(username=pk)
contact = get_object_or_404(queryset, pk=1)
serializer = ContactSerializer(contact)
return Response(serializer.data)
The answer from Anush Devendra is right but need a little update due to deprecations on v3.9.
action decorator replaces list_route and detail_route
Both list_route and detail_route are now deprecated in favour of the single action decorator. They will be removed entirely in 3.10.
The action decorator takes a boolean detail argument.
Replace detail_route uses with #action(detail=True).
Replace list_route uses with #action(detail=False).
...
from rest_framework.decorators import action
from rest_framework.response import Response
from django.shortcuts import get_object_or_404
from rest_framework import status
class UserViewSet(viewsets.ModelViewSet):
...
#action(methods=['get'], detail=False,
url_path='username/(?P<username>\w+)')
def getByUsername(self, request, username):
user = get_object_or_404(User, username=username)
data = UserSerializer(user, context={'request': request}).data
return Response(data, status=status.HTTP_200_OK)
I add context={'request': request} because I have url as HyperlinkedIdentityField in my serializer. If you don't have it, you probably don't need it.

Categories

Resources