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.
Related
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
So I have this view, I passed the request through the context to the serializer so I can use it to get the user
def create(self, request, *args, **kwargs):
""" Handle member creation from invitation code. """
serializer = AddMemberSerializer(
data=request.data,
context={'circle': self.circle, 'request': request}
)
serializer.is_valid(raise_exception=True)
member = serializer.save()
data = self.get_serializer(member).data
return Response(data, status=status.HTTP_201_CREATED)
In the serializer I did this but it does not work,I get "KeyError: 'user'", I ran a debugger and when I tried to call the get_user method it says "TypeError: get_user() missing 1 required positional argument: 'obj'"
user = serializers.SerializerMethodField()
def get_user(self, obj):
request = self.context.get('request', None)
if request:
return request.user
So what am I missing? I looked up other implementations of this field and none of them seem very different of mine, so I would really apreciate it if someone explains to me why it is not working.
Also if there is a more efective way to get the user into a field (Need it to run a user_validate method on it)
Try:
user = serializers.SerializerMethodField()
def get_user(self, obj):
return obj.user_id
I'm using Django 2.2 and Django REST Framework.
I have an endpoint to update existing record, extending UpdateAPIView
class FakeOrderPaymentView(generics.UpdateAPIView):
serializer_class = OrderPaymentSerializer
permission_classes = (IsAuthenticated,)
def get_object(self):
log.debug(self.request.data)
order_id = self.request.data.get('order_id', None)
if not order_id:
raise Http404
return self.serializer_class.Meta.model.objects.filter(pk=order_id).first()
def perform_update(self, serializer):
return serializer.save()
def update(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
instance = self.perform_update(serializer)
instance_serializer = OrderListSerializer(instance)
return Response(instance_serializer.data)
and the OrderPaymentSerializer
class OrderPaymentSerializer(serializers.ModelSerializer):
order_id = serializers.IntegerField(write_only=True, required=False)
order_status = serializers.CharField(write_only=True, required=True)
class Meta:
model = Order
fields = [
'order_status',
'order_id'
]
def create(self, validated_data):
log.debug(validated_data)
return super().create(validated_data)
def update(self, instance, validated_data):
status = validated_data.pop('order_status', None)
order_id = validated_data.pop('order_id', None)
instance.status = Order.STATUS.COMPLETED
return super().update(instance, validated_data)
The order_id and order_status are not model fields. They are just to carry data.
If order_id is not in the PUT data or order_id is blank. It should raise 404 response.
But, it's not calling get_object() method before update().
Also, even if I pass order_id in the PUT data. The perform_update() method is calling serializer's create() method instead of update() method.
When is serializer's update() method called?
Why get_object() is not calling automatically?
When is serializer's update() method called?
As the documentation states:
Calling .save() will either create a new instance, or update an existing instance, depending on if an existing instance was passed when instantiating the serializer class
Since you are not providing an instance when instantiating the serializer, calling save will translate into create instead of update.
Why get_object() is not calling automatically?
Because you view's update method is overridden and you didn't call get_object. Default DRF update is:
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
# forcibly invalidate the prefetch cache on the instance.
instance._prefetched_objects_cache = {}
return Response(serializer.data)
I have a Django viewset, where I have a function, that sends an email, whenever the viewset, is used.
I override the create function, to send the email, where I have a function, that sends the email to the user.
I want to pass in some arguments, to the function, to display in the email (done with Django's template engine)
class ContactRequestViewSet(viewsets.ModelViewSet):
queryset = ContactRequest.objects.all()
permission_classes = [
permissions.AllowAny
]
serializer_class = ContactRequestSerializer
def create(self, request, *args, **kwargs):
response = super(ContactRequestViewSet, self).create(request, *args, **kwargs)
send_email()
return response
#function to send email
def send_email():
htmly = get_template('email.html')
d = {'company_name': 'dodo'} #i want this dictionary, to contain the attributes from the viewset
send_mail(
subject='Test email',
message='',
from_email='test#email.com',
recipient_list=['test#email.com'],
html_message= htmly.render(d)
)
right now I just have a sample placeholder as d but here I want to pass in attributes from the serializers/model, that the user provided, I tried passing in the serializer, and accessing its attributes, but I don't know how to do this the proper way
You can access response.data after this line
response = super(ContactRequestViewSet, self).create(request, *args, **kwargs)
which will hold the serializer's data. Yet if you want an actual instance of your model this will not be sufficient and you will need to hack your way around a little bit. The CreateModelMixin that is used in ModelViewSet of django-rest-framework has the following methods:
class CreateModelMixin:
# ... more stuff here
def create(self, request, *args, **kwargs):
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)
def perform_create(self, serializer):
serializer.save()
so you could override the perform_create method to save the instance into an attribute of your ContactRequestViewSet like this:
def perform_create(self, serializer):
self.instance = serializer.save()
then in the create method you could do something like this:
def create(self, request, *args, **kwargs):
response = super(ContactRequestViewSet, self).create(request, *args, **kwargs) # This calls perform_create internally
send_email(self.instance)
return response
I'm trying to implement REST API that implements HATEOAS using Django REST Framework (DRF). I know that DRF itself doesn't support HATEOAS and I didn't find any examples of such implementation. Therefore I'm not sure on which level of DRF (Serializers / Views / Renderers) should I implement this functionality. Do you have some experiences, thoughts, insights or examples which could help me to start? Thank you.
Here is my solution implemented on level of Viewset:
from rest_framework import viewsets
from rest_framework import generics
from serializers import EventSerializer, BandSerializer
from rest_framework.response import Response
from rest_framework import status
from collections import OrderedDict
class LinksAwarePageNumberPagination(PageNumberPagination):
def get_paginated_response(self, data, links=[]):
return Response(OrderedDict([
('count', self.page.paginator.count),
('next', self.get_next_link()),
('previous', self.get_previous_link()),
('results', data),
('_links', links),
]))
class HateoasModelViewSet(viewsets.ModelViewSet):
"""
This class should be inherited by viewsets that wants to provide hateoas links
You should override following methodes:
- get_list_links
- get_retrieve_links
- get_create_links
- get_update_links
- get_destroy_links
"""
pagination_class = LinksAwarePageNumberPagination
def get_list_links(self, request):
return {}
def get_retrieve_links(self, request, instance):
return {}
def get_create_links(self, request):
return {}
def get_update_links(self, request, instance):
return {}
def get_destroy_links(self, request, instance):
return {}
def get_paginated_response(self, data, links=None):
assert self.paginator is not None
return self.paginator.get_paginated_response(data, links)
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data, links=self.get_list_links())
serializer = self.get_serializer(queryset, many=True)
return Response(OrderedDict([
('results', serializer.data),
('_links', self.get_list_links(request))
]))
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance)
data = serializer.data
data['_links'] = self.get_retrieve_links(request, instance)
return Response(data)
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
data = serializer.data
data['_links'] = self.get_create_links(request)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
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)
data = serializer.data
data['_links'] = self.get_update_links(request, instance)
return Response(serializer.data)
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
data = {'_links': self.get_destroy_links(request, instance)}
self.perform_destroy(instance)
return Response(data, status=status.HTTP_204_NO_CONTENT)
Example of usage:
class EventViewSet(HateoasModelViewSet):
queryset = Event.objects.all()
serializer_class = EventSerializer
def get_list_links(self, request):
return {
'self': {'href': request.build_absolute_uri(request.path)},
'related_link1': {'href': '...'},
}
I know it is a relevant old thread, but for those who came to this now, there is a library django-rest-framework-json-api, which uses the json:api specification. This specification is completely compatible with the HATEOAS implementation.
I am also quite new to this; here is a tutorial that I used as a guide by the Columbia university which I found really helpful.
On the plus side I find it is compatible with api documentation using swagger, using the drf-yasg and drf-yasg-json-api libraries.