I have the following view:
class PersonalInfos(generics.RetrieveUpdateAPIView):
serializer_class = ClientSerializer
permission_classes = [IsAuthenticated]
def get_queryset(self):
"""
:return: A QuerySet Object
"""
return Client.objects.get(user=self.request.user)
def get(self, *args):
"""
:param args: Handled by rest_framework views.dispatch
:return: JSON object containing User Personal Data
"""
queryset = self.get_queryset()
serializer = ClientSerializer(queryset)
return Response(data=serializer.data)
def patch(self, request):
"""
:param request: request object is sent by the client
:return: Json response with the data sent of the body
"""
client = self.get_queryset()
serializer = ClientSerializer(client, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response(data=serializer.data, status=200)
return Response(data="Unexpected Parameters", status=400)
Everything works fine in the view, but the problem is that I am using drf-spectacular and it is showing me a PUT method in the documentation that we won't be needing in the API. My questions is, how can I customize drf-spectacular to not include a PUT method in the documentation?
You may use the #extend_schema decorator to exclude one or more methods from the schema generated, as shown below.
#extend_schema(methods=['PUT'], exclude=True)
I solved this using RetrieveAPIView instead of UpdateRetrieveAPIView and I have extended it with to include a PATCH method. RetrieveAPIView will handle a PATCH method perfectly and would not show automatically an UPDATE request in the API documentation. Here is the new code:
class PersonalInfos(generics.RetrieveAPIView):
serializer_class = ClientSerializer
permission_classes = [IsAuthenticated]
def get_queryset(self):
"""
:return: A QuerySet Object
"""
return Client.objects.get(user=self.request.user)
def get(self, *args):
"""
:param args: Handled by rest_framework views.dispatch
:return: JSON object containing User Personal Data
"""
queryset = self.get_queryset()
serializer = ClientSerializer(queryset)
return Response(data=serializer.data)
def patch(self, request):
"""
:param request: request object is sent by the client
:return: Json response with the data sent of the body
"""
client = self.get_queryset()
serializer = ClientSerializer(client, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response(data=serializer.data, status=200)
return Response(data="Unexpected Parameters", status=400)
this should exclude the put method in drf-yasg
class MyView(generics.RetrieveUpdateAPIView):
#swagger_auto_schema(auto_schema=None)
def put(self, request, *args, **kwargs):
return
source: https://drf-yasg.readthedocs.io/en/stable/custom_spec.html#excluding-endpoints
Related
I am making a test that partial updates or patch some fields in my Product model. I should be enable to update any fields in my Product model. In my serializer, I have a url builder from the request object. However, request is not passed on my serializer or it is a None type. Thus, I am getting an error: AttributeError: 'NoneType' object has no attribute 'build_absolute_uri'
How do I pass a request to my serializer?
def test_patch_products(self):
data = {
'description': 'This is an updated description.',
'type': 'New'
}
...
...
response = self.client.patch('/data/products/1/',
data=data, format='json')
In my view:
def partial_update(self, request, pk=None):
product= get_object_or_404(Product, pk=pk)
serializer = ProductSerializer(product,
data=self.request.data, partial=True,
)
if serializer.is_valid():
serializer.save(uploaded_by=self.request.user)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
In my serializer:
class ProductSerializer(serializers.ModelSerializer):
uploaded_by= serializers.StringRelatedField()
thumbnail = serializers.SerializerMethodField()
class Meta:
model = CaptureGroup
fields = ('__all__')
def get_thumbnail(self, object):
request = self.context.get('request') # request is None here
thumbnail = object.thumbnail.url
return request.build_absolute_uri(thumbnail) # here is the url builder
In your view, you can pass some additional context to your serializer. That's useful to manipulate your request.
# views.py
def partial_update(self, request, pk=None):
product= get_object_or_404(Product, pk=pk)
serializer = ProductSerializer(product,
data=request.data, partial=True,
context={'request': request}, # you're missing this line
)
# ...
And then you can manipulate it your serializer with self.context.get('request') but I see you already got that part in your code.
In case you would like more information, here is the link to DRF documentation about this.
I am a beginner learning django rest framework and I just encounter this error and I can't seem to find a way around it. Here is the permissions.py sample code
from rest_framework import permissions
class UpdateOwnProfile(permissions, BaseException):
"""Allow user to edit their own profile"""
def has_object_permission(self, request, view, obj):
"""Check if user is trying to update their own profile"""
if request.method in permissions.SAFE_METHODS:
return True
return obj.id == request.user.id
Here is also a sample of the views.py sample codes
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework import viewsets
from rest_framework.authentication import TokenAuthentication
from profiles_api import serializers
from profiles_api import models from profiles_api import permissions
class HelloApiView(APIView): """Test Api view""" serializer_class = serializers.HelloSerializer
def get(self, request, format=None):
"""Returns a list of Api features"""
an_apiview = [
'Uses HTTP methods as function (get, post, patch, put, delete)',
'Is similar to a traditional Django view',
'Gives you the most control over your application logic',
'Is mapped manually to URLs',
]
return Response({'message': 'Hello', 'an_apiview': an_apiview})
def post(self, request):
"""Create a hello message with our name"""
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
name = serializer.validated_data.get('name')
message = f'Hello {name}'
return Response({'message': message})
else:
return Response(
serializer.errors,
status = status.HTTP_400_BAD_REQUEST
)
def put(self, request, pk=None):
"""Handling updates of objects"""
return Response({'method': 'PUT'})
def patch(self, request, pk=None):
"""Handle a partial update of an object"""
return Response({'method': 'PATCH'})
def delete(self, request, pk=None):
"""Delete an object"""
return Response({'method': 'DELETE'})
class HelloViewset(viewsets.ViewSet): """Test API Viewset""" serializer_class = serializers.HelloSerializer
def list(self, request):
"""Return a hello message"""
a_viewset = [
'Uses actions (list, create, retrieve, update, partial update'
'Automatically maps to URLs using router'
'provides more functionality with less code'
]
return Response({'message': 'Hello', 'a_viewset': a_viewset})
def create(self, request):
"""Create a new hello message"""
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
name = serializer.validated_data.get('name')
message = f'Hello {name}!'
return Response({'message': message})
else:
return Response(
serializer.errors,
status=status.HTTP_400_BAD_REQUEST
)
def retrieve(self, request, pk=None):
"""Handle getting an object by its ID"""
return Response({'http_method': 'GET'})
def update(self, request, pk=None):
"""Handle updating an object"""
return Response({'http_method': 'PUT'})
def partial_update(self, request, pk=None):
"""Handle updating of an object"""
return Response({'http_method': 'PATCH'})
def destroy(self, request, pk=None):
"""Handle removing an object"""
return Response({'http_method': 'DELETE'})
class UserProfileViewSet(viewsets.ModelViewSet): """Handle creating and updating profiles""" serializer_class = serializers.UserProfileSerializer queryset = models.UserProfile.objects.all() authentication_classes = (TokenAuthentication,) permission_classes = (permissions.UpdateOwnProfile,)
And while running the development server I get this error:
class UpdateOwnProfile(permissions, BaseException): TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
permissions (rest_framework.permissions) is of type module (whose type is type FWIW), but the type of BaseException is type (like all regular classes). So you have a metaclass conflict as expected.
Presumably, you meant to use permissions.BasePermission class from the module:
class UpdateOwnProfile(permissions.BasePermission, BaseException):
...
...
You can also import and refer the class directly:
from rest_framework.permissions import BasePermission
class UpdateOwnProfile(BasePermission, BaseException):
...
...
I am using Django 1.11 and DRF 3.6.2 and just started developing an API...
I am trying to check what are the changes to be performed in the database with the data being sent.
class IndividualViewSet(viewsets.ModelViewSet):
"""Individual ViewSet."""
serializer_class = serializers.IndividualSerializer
queryset = models.Individual.objects.all()
def update(self, request, equipment_serial, pk=None):
queryset = models.Individual.objects.get(pk=pk)
serializer = serializers.IndividualSerializer(queryset, data=request.data["entities"][0])
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status.HTTP_200_OK)
return Response(status.HTTP_400_BAD_REQUEST)
def perform_update(self, serializer):
old_obj = self.get_object()
new_data_dict = serializer.validated_data
if old_obj.name != new_data_dict['name']:
# logic for different data
# ...
serializer.save()
However, with the code as above, the perform_update function is never being called by serializer.save() on the update function.
According to the docs, ModelViewSet is inherited from GenericAPIView and it has UpdateModelMixin, which should automatically calls the perform_update function before saving
My questions surrounds on why it is happen and how should I do in order to accomplish the desired behaviour.
This is because you are overriding the update method in your custom viewset. This is the original code for the UpdateModelMixin that the ModelViewSet mixes in:
class UpdateModelMixin(object):
"""
Update a model instance.
"""
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
if getattr(instance, '_prefetched_objects_cache', None):
# If 'prefetch_related' has been applied to a queryset, we need to
# refresh the instance from the database.
instance = self.get_object()
serializer = self.get_serializer(instance)
return Response(serializer.data)
def perform_update(self, serializer):
serializer.save()
def partial_update(self, request, *args, **kwargs):
kwargs['partial'] = True
return self.update(request, *args, **kwargs)
In this original version, perform_update is called whenever in the update method. If you override that method and still want to call perform_update, you need to put it there.
I'm using the DjangoRestFramework. I have a UserSerialzer in my serializers.py file:
from django.contrib.auth.models import User
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('username', 'password', 'email', )
This is my urls.py file:
urlpatterns = [
url(r'^$', views.HomePageView.as_view()),
url(r'^users$', views.user_list.as_view()),
url(r'^users/(?P<pk>[0-9]+)$', views.user_detail.as_view()),
]
and this is my views.py file:
class HomePageView(TemplateView):
template_name = "home.html"
def get_context_data(self, **kwargs):
context = super(HomePageView, self).get_context_data(**kwargs)
# context['users'] = User.objects.all()
return context
class user_list(APIView):
"""
List all users, or create a new user.
"""
serializer_class = UserSerializer
def get(self, request):
users = User.objects.all()
serializer = UserSerializer(users, many=True)
return Response(serializer.data)
def post(self, request):
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 user_detail(APIView):
"""
Get, update or delete a specific user.
"""
serializer_class = UserSerializer
def get_object(self, pk):
try:
return User.objects.get(pk=pk)
except User.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
def get(self, request, pk):
user = self.get_object(pk)
serializer = UserSerializer(user)
return Response(serializer.data)
def put(self, request, pk):
user = self.get_object(pk)
serializer = UserSerializer(user, data=request.DATA)
if serialzier.is_valid():
serializier.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk):
user = self.get_object(pk)
user.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
When I go to 127.0.0.1:8000/users, DjangoRestFramework has the API page which shows a list of users (JSON objects) and it also has a form which has "username", "password" and "email". This form seems to be validating correctly (checks if the email is a real email, and checks if username is unique and less than 30 characters). Is there a way for me to pass this form to the frontend when a user goes to 127.0.0.1:8000 (calling HomePageView)?
I'm in the process of using AngularJS on the frontend (not sure if this information helps or not).
Well here are a few things I think we might need to point out. Django forms are normally what you would use to create a new user, with a post request just like you are trying to do through DRF. Now you can do this through DRF, but thats not really what it is for, django forms would be more appropriate for what you are doing. Unless of course you are building an API that you want API users to be able to use to create new users on your platform, in which case continue onward. I am linking a tutorial I used when I first started using DRF with Angular, maybe you will find it helpful.
http://blog.kevinastone.com/getting-started-with-django-rest-framework-and-angularjs.html
I want to make sure the request.user can only issue a POST request to create a forum topic in which they are the auther. With PUT and DELETE I'm able to achieve that by using the has_object_permission but with POST I'm not able to do that, I'm guessing because the object hasn't been created yet.
class TopicPermission(IsAuthenticatedOrReadOnly):
"""
Any user should be able to read topics but only authenticated
users should be able to create new topics. An owner or moderator
should be able to update a discussion or delete.
"""
def has_object_permission(self, request, view, obj):
if request.method in SAFE_METHODS:
return True
# Instance must have an attribute named `author` or moderator
return obj.author == request.user or request.user.forum_moderator
How would I go about verifying request.user == obj.author in POST requests?
I ended up doing the validation in the viewset instead of the serializer:
class TopicViewSet(viewsets.ModelViewSet):
permission_classes = (TopicPermission, )
queryset = Topic.objects.all()
serializer_class = TopicSerializer
def create(self, request, *args, **kwargs):
"""
verify that the POST has the request user as the obj.author
"""
if request.data["author"] == str(request.user.id):
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=201, headers=headers)
else:
return Response(status=403)