Multiple Models in Django Rest Framework? - python

I am using Django Rest framework. I want to serialize multiple models and send them as a response. Currently I can send only one model per view (like CartView below sends only Cart object). Following models (unrelated) can be there.
class Ship_address(models.Model):
...
class Bill_address(models.Model):
...
class Cart(models.Model):
...
class Giftwrap(models.Model):
...
I tried using DjangoRestMultipleModels, it works ok but has some limitations. Is there any in-built way? Can't I append to the serializer that's created in the following view?
from rest_framework.views import APIView
class CartView(APIView):
"""
Returns the Details of the cart
"""
def get(self, request, format=None, **kwargs):
cart = get_cart(request)
serializer = CartSerializer(cart)
# Can't I append anything to serializer class like below ??
# serializer.append(anotherserialzed_object) ??
return Response(serializer.data)
I really like DRF. But this use-case (of sending multiple objects) makes me wonder if writing a plain-old Django view will be better suited for such a requirement.

You can customize it, and it wouldn't be too weird, because this is an APIView (as opposed to a ModelViewSet from which a human being would expect the GET to return a single model) e.g. you can return several objects from different models in your GET response
def get(self, request, format=None, **kwargs):
cart = get_cart(request)
cart_serializer = CartSerializer(cart)
another_serializer = AnotherSerializer(another_object)
return Response({
'cart': cart_serializer.data,
'another': another_serializer.data,
'yet_another_field': 'yet another value',
})

Related

Django rest framework serializer fields using single API call

I have a route that uses a ViewSet
router.register(
r'mapping_details',
GlobalMappingDetailsViewSet,
base_name='global-store-product-mappings'
)
This view set contains a get_queryset method and a serializer.
class GlobalMappingDetailsViewSet(viewsets.ModelViewSet):
serializer_class = GlobalMappingDetailsSerializer
def get_queryset(self):
return models.Mappings.objects.all().select_related('my_column')
class GlobalMappingDetailsSerializer(serializers.ModelSerializer):
class Meta:
model = models.Mappings
fields = ('column1', 'column2')
I want to add 2 fields that are populated using a single API call in the response of the request. I could use serializers.Field but will have to make separate calls for both the fields.
Does someone know the right way to handle this use case?
The way to do this is by overriding the Django Viewset actions. In my case I needed to just populate the response in the fields, I overrode the list and retrieve actions and added the fields to the response.
Some sample code:
def retrieve(self, request, *args, **kwargs):
response = super(GlobalMappingDetailsViewSet, self).retrieve(request, *args, **kwargs)
data = response.data
api_response = MyService.make_api_call(data.get('id'))
data['my_new_field'] = api_response.get('response_field', '')
return response

Django - Correct method of consuming my own REST API internally in the views.py?

I created a Django REST API using serializers, viewsets and routers. My end points looks something like this:
http://www.website.com/api/items
http://www.website.com/api/items/available
serializer.py (omitting imports)
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = '__all__'
viewsets.py (omitting imports)
class ItemViewSet(viewsets.ModelViewSet):
queryset = Item.objects.all()
serializer_class = ItemSerializer
#action(methods=['GET'], detail=False)
def most_expensive(self, request):
query = self.get_queryset().order_by('price').last()
serialized = self.serializer_class(query)
return Response(serialized.data)
Now I want to able to access this API from my views.py to render the HTML with the available items:
This is the way im doing it right now:
views.py (omitting imports)
class ProductListView(View):
template = 'store/product_list.html'
def get(self, request):
items = requests.get('http://127.0.0.1:8000/api/items/available')
context = {'items': items}
return render(request, self.template, context=context)
Using the requests modules I have a couple of concerns, after measuring I noticed there is a 0.015 second delay for that request to go through and if I ever change the API endpoint I would have to adjust it here since its hard coded.
I can get my items using:
Item.objects.filter(available=True)
Which gives me the result pretty much instantly but I'm writing all the queries twice (once in my API and once in my views.py)
Is there a better way of doing this like calling the viewset class directly and getting the data from there?
Many thanks!
Calling the API endpoint in the same app is not considered a good practise.
An option would be to call your viewset method directly, like in https://stackoverflow.com/a/51149806/290036
The other one that I recommend is to use the same codebase for your API and for the view.
def get_avaialble_items():
items = Item.objects.filter(available=True)
...
return items
# Use get_avaialble_items both in ItemViewSet and ProductListView

How to manually populate a relational model in Django REST

I'm still new to Django and DRF. I have 2 models (Policy and Rescue) and Rescue is related to Policy by the Foreign Key policy_id. I have no issue to POST a JSON message and get Policy populated with the request data by CreateView. However, the 2nd model Rescue needs be populated based on some calculations from the JSON data POSTed to the Policy. Rescue cannot be POSTed beforehand. I tried hard but had no clue to do so.
Is this something to do with nested serializer or something else?
I've tried to
Can I try this way: inside the class CreateView:
class CreateView(generics.CreateAPIView):
def create(self, request, *args, **kwargs):
my_serializer = self.get_serializer(data=request.data)
...
# get a policy object based on 'policy_id' against serializer
my_policy = Policy.objects.get(policy_id=my_serializer.data['policy_id'])
...
... # some calculations to work out a rescue id, and will be returned and saved.
Rescue.objects.create(rescue_id='QD1234', policy=my_policy)
you can use a generic CreateAPIView and override the perform_create method.
def perform_create(self, serializer):
my_policy = serializer.save()
# you custom calculation for rescue_id
rescue_obj = Rescue.objects.create(rescue_id='QD1234', policy=my_policy)
perform create method is documented here: https://www.django-rest-framework.org/api-guide/generic-views/#methods

Django Rest Framework partial update

I'm trying to implement partial_update with Django Rest Framework but I need some clarification because I'm stuck.
Why do we need to specify partial=True?
In my understanding, we could easily update Demo object inside of partial_update method. What is the purpose of this?
What is inside of serialized variable?
What is inside of serialized variable in partial_update method? Is that a Demo object? What function is called behind the scenes?
How would one finish the implementation here?
Viewset
class DemoViewSet(viewsets.ModelViewSet):
serializer_class = DemoSerializer
def partial_update(self, request, pk=None):
serialized = DemoSerializer(request.user, data=request.data, partial=True)
return Response(status=status.HTTP_202_ACCEPTED)
Serializer
class DemoSerializer(serializers.ModelSerializer):
class Meta:
model = Demo
fields = '__all__'
def update(self, instance, validated_data):
print 'this - here'
demo = Demo.objects.get(pk=instance.id)
Demo.objects.filter(pk=instance.id)\
.update(**validated_data)
return demo
I when digging into the source code of rest_framework and got the following findings:
For question 1. Why do we need to specify partial=True?
This question is related to HTTP verbs.
PUT: The PUT method replaces all current representations of the target resource with the request payload.
PATCH: The PATCH method is used to apply partial modifications to a resource.
Generally speaking, partial is used to check whether the fields in the model is needed to do field validation when client submitting data to the view.
For example, we have a Book model like this, pls note both of the name and author_name fields are mandatory (not null & not blank).
class Book(models.Model):
name = models.CharField('name of the book', max_length=100)
author_name = models.CharField('the name of the author', max_length=50)
# Create a new instance for testing
Book.objects.create(name='Python in a nut shell', author_name='Alex Martelli')
For some scenarios, we may only need to update part of the fields in the model, e.g., we only need to update name field in the Book. So for this case, client will only submit the name field with new value to the view. The data submit from the client may look like this:
{"pk": 1, name: "PYTHON IN A NUT SHELL"}
But you may have notice that our model definition does not allow author_name to be blank. So we have to use partial_update instead of update. So the rest framework will not perform field validation check for the fields which is missing in the request data.
For testing purpose, you can create two views for both update and partial_update, and you will get more understanding what I just said.
Example:
views.py
from rest_framework.generics import GenericAPIView
from rest_framework.mixins import UpdateModelMixin
from rest_framework.viewsets import ModelViewSet
from rest_framework import serializers
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
class BookUpdateView(GenericAPIView, UpdateModelMixin):
'''
Book update API, need to submit both `name` and `author_name` fields
At the same time, or django will prevent to do update for field missing
'''
queryset = Book.objects.all()
serializer_class = BookSerializer
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
class BookPartialUpdateView(GenericAPIView, UpdateModelMixin):
'''
You just need to provide the field which is to be modified.
'''
queryset = Book.objects.all()
serializer_class = BookSerializer
def put(self, request, *args, **kwargs):
return self.partial_update(request, *args, **kwargs)
urls.py
urlpatterns = patterns('',
url(r'^book/update/(?P<pk>\d+)/$', BookUpdateView.as_view(), name='book_update'),
url(r'^book/update-partial/(?P<pk>\d+)/$', BookPartialUpdateView.as_view(), name='book_partial_update'),
)
Data to submit
{"pk": 1, name: "PYTHON IN A NUT SHELL"}
When you submit the above json to the /book/update/1/, you will got the following error with HTTP_STATUS_CODE=400:
{
"author_name": [
"This field is required."
]
}
But when you submit the above json to /book/update-partial/1/, you will got HTTP_STATUS_CODE=200 with following response,
{
"id": 1,
"name": "PYTHON IN A NUT SHELL",
"author_name": "Alex Martelli"
}
For question 2. What is inside of serialized variable?
serialized is a object wrapping the model instance as a serialisable object. and you can use this serialized to generate a plain JSON string with serialized.data .
For question 3. How would one finish the implementation here?
I think you can answer yourself when you have read the answer above, and you should have known when to use update and when to used partial_update.
If you still have any question, feel free to ask. I just read part of the source code of the rest framework, and may have not understand very deeply for some terms, and please point it out when it is wrong...
For partial update - PATCH http method
For full update - PUT http method
When doing an update with DRF, you are supposed to send request data that includes values for all (required) fields. This is at least the case when the request is via the PUT http method. From what I understand, you want to update one or at least not all model instance fields. In this case make a request with the PATCH http method. Django rest framework (DRF) will take care of it out of the box.
Example (with token auth):
curl -i -X PATCH -d '{"name":"my favorite banana"}' -H "Content-Type: application/json" -H 'Authorization: Token <some token>' http://localhost:8000/bananas/
So simple, just override init method of your serializer like that:
def __init__(self, *args, **kwargs):
kwargs['partial'] = True
super(DemoSerializer, self).__init__(*args, **kwargs)
Just a quick note as it seems that nobody has already pointed this out:
serialized = DemoSerializer(request.user, data=request.data, partial=True)
The first argument of DemoSerializer should be a Demo instance, not a user (at least if you use DRF 3.6.2 like me).
I don't know what you are trying to do, but this is a working example:
def partial_update(self, request, *args, **kwargs):
response_with_updated_instance = super(DemoViewSet, self).partial_update(request, *args, **kwargs)
Demo.objects.my_func(request.user, self.get_object())
return response_with_updated_instance
I do the partial update and then I do other things calling my_func and passing the current user and the demo instance already updated.
Hope this helps.
I had an issue where my multi-attribute/field validation in a rest_framework serializer was working with a POST /resources/ request but failing with a PATCH /resources/ request. It failed in the PATCH case because it was only looking for values in the supplied attrs dict and not falling back to values in self.instance. Adding a method get_attr_or_default to do that fallback seems to have worked:
class EmailSerializer(serializers.ModelSerializer):
def get_attr_or_default(self, attr, attrs, default=''):
"""Return the value of key ``attr`` in the dict ``attrs``; if that is
not present, return the value of the attribute ``attr`` in
``self.instance``; otherwise return ``default``.
"""
return attrs.get(attr, getattr(self.instance, attr, ''))
def validate(self, attrs):
"""Ensure that either a) there is a body or b) there is a valid template
reference and template context.
"""
existing_body = self.get_attr_or_default('body', attrs).strip()
if existing_body:
return attrs
template = self.get_attr_or_default('template', attrs)
templatecontext = self.get_attr_or_default('templatecontext', attrs)
if template and templatecontext:
try:
render_template(template.data, templatecontext)
return attrs
except TemplateRendererException as err:
raise serializers.ValidationError(str(err))
raise serializers.ValidationError(NO_BODY_OR_TEMPLATE_ERROR_MSG)
I don't know why, but for me, the only way to solve it was to override the validate method in the Serializer class.
Maybe it's related to the fact that I'm using MongoDB with Djongo
class DemoSerializer(serializers.ModelSerializer):
def validate(self, attrs):
self._kwargs["partial"] = True
return super().validate(attrs)
You forgot serializer.save()
You can finish it the following way . . .
class DemoViewSet(viewsets.ModelViewSet):
serializer_class = DemoSerializer
def partial_update(self, request, pk=None):
serializer = DemoSerializer(request.user, data=request.data, partial=True)
serializer.save()
serializer.is_valid(raise_exception=True)
return Response(serializer.data)
Also, you shouldn't need to override the update method in the serializer.

Custom function which performs create and update on DRF modelViewSet

Hi there I want to create a custom method in a modelviewset which needs to perform a save and an update logic in a single post request.
Here is my breeding.viewsets.py
class BreedingViewSet(viewsets.ModelViewSet):
queryset = Breeding.objects.all()
serializer_class = BreedingSerializer
Since the above method has a higher level of abstraction and is
actually providing or performing automatic CRUD functions.
Now the problem here is i dont have any control for a multiple queries like saving an object and updating another object in a single post request.
e.g
def save_and_update(self, request):
// do save an object here.
// do update an object here.
How can we achieve such powerful functionalities? Did i missed something? I found this documentation but i dont know how to implement the given instruction.
UPDATE
This is what im looking for How do I create multiple model instances with Django Rest Framework?
But the answer can only save a multiple instances in a single post request of that same model. But Im hoping also that we can perform queries for a different models in that single function.
Well, from the comments, it looks like you want to update some unrelated model when you create your breeding model. This should be easy.
class BreedingViewSet(viewsets.ModelViewSet):
queryset = Breeding.objects.all()
serializer_class = BreedingSerializer
def create(self, request):
# do your thing here
return super().create(request)
Use this to create or update using POST
class BreedingViewSet(viewsets.ModelViewSet):
queryset = Breeding.objects.all()
serializer_class = BreedingSerializer
def get_object(self):
if self.action == 'create':
queryset = self.filter_queryset(self.get_queryset())
filter_kwargs = {self.lookup_field: self.request.data.get('id')}
obj = get_object(queryset, **filter_kwargs)
self.check_object_permissions(self.request, obj)
return obj
else:
return super(BreedingViewSet, self).get_object()
def create(self, request, *args, **kwargs):
if request.data.get('id'):
return super(BreedingViewSet, self).update(request, *args, **kwargs)
else:
return super(BreedingViewSet, self).create(request, *args, **kwargs)

Categories

Resources