Create corresponding models on ViewSet save - python

I have a simple model that is serialized and created. The Viewset for this is as follows:
class OrderViewset(viewsets.ModelViewSet):
depth = 1
serializer_class = OrderSerializer
...
def perform_create(self, serializer):
serializer.save(user=self.request.user)
populate_ing(serializer)
Once the user saves and creates the model, I aim to shoot of and call 'populate_ing(xxx)' which takes the model (in this case an order) and creates a number of related objects using a foreign key relationship.
Is it possible to handle this on save? Believe, as above, by overriding the perform_create I should do so. And, most importantly, how can I access the model which has just been created?
For more explicit of what I am after, I would hope to do the following:
Create 'Order' using ViewSet above
Pass 'Order' (or its id, etc depending whats possible) to function populate_ing
populate_ing does its magic and creates other models
Return 'Order'
My serializer is as follows:
class OrderSerializer(serializers.ModelSerializer):
class Meta:
model = Order
fields = '__all__'
In a normal Djnago view with a form, I would handle it something to the effect of:
def view_create_order(request):
form = OrderForm(request.POST or None)
if form.is_valid():
new_order = form.save()
populate_ing(new_order)
context = {"form": form}
template = "order/order-update.html"
return render(request, template, context)

The created instance will be available in instance attribute, so it can be pass to the populate_ing() function as,
class OrderViewset(viewsets.ModelViewSet):
# depth = 1
serializer_class = OrderSerializer
...
def perform_create(self, serializer):
serializer.save(user=self.request.user)
populate_ing(serializer.instance)

Related

Serializing model queryset showing empty [OrderedDict()]

I am building a blog app with React and Django and I am serializing model's instances saved by particular user, First I am just trying to test with .all() then I am planning to filter by specific user But when I serialize queryset with Serializer like:
class BlogSerializerApiView(viewsets.ModelViewSet):
serializer_class = BlogSerializer
def get_queryset(self, *args, **kwargs):
queryset = Blog.objects.all()
output_serializer = BlogSerializer(queryset, many=True)
print(output_serializer.data)
return "Testing"
It is showing in console:
[OrderedDict(), OrderedDict()]
and when I access it like
print(output_serializer)
Then it is showing:
BlogSerializer(<QuerySet [<Blog: user_1 - Blog_title>, <Blog: user_2 - second_blog_title>]>, many=True):
serializer.py:
class BlogSerializer(serializers.Serializer):
class Meta:
model = Blog
fields = ['title']
models.py:
class Blog(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=30, default='')
def __str__(self):
return f"{self.user} - {self.title}"
What I am trying to do:
I am trying to serialize queryset to show on page in react frontend, I will relate with specific user later.
I have tried many times by changing CBV serialization method by generics.ListAPIView instead of viewsets.ModelViewSet but still same thing.
There is a concept error here. The get_queryset function is not supposed to return serialized data. It must return a QuerySet of model objects.
To achieve what you want you can just do:
class BlogSerializerApiView(viewsets.ModelViewSet):
serializer_class = BlogSerializer
def get_queryset(self, *args, **kwargs):
return Blog.objects.all()
The Django Rest Framework will take care of serializing data.
In fact, you can even do it way more simple. Defining the view's queryset field like this:
class BlogSerializerApiView(viewsets.ModelViewSet):
queryset = Blog.objects.all()
serializer_class = BlogSerializer
Additional:
You said you will relate to current user later. You could achieve that in fact in the get_queryset method filtering aginst the user
class BlogSerializerApiView(viewsets.ModelViewSet):
serializer_class = BlogSerializer
def get_queryset(self, *args, **kwargs):
return Blog.objects.filter(user_id=USER_ID)
Hope this helps!
I was using
class BlogSerializer(serializers.Serializer):
.......
so it was showing empty results (no idea why, I think its deprecated)
After replaceing it with
class BlogSerializer(serializers.HyperlinkedModelSerializer):
It worked

serializing only certain fields from a queryset in django serializer class

I have a queryset which I obtain from get_queryset(). What we know is, the returns of queryset gives the list of objects which contains all the fields of the model. Now I don't want to serialize all the fields from the model and show all of them in the response. I want to serialize only few fields and show in the api response.
for eg:
def get_queryset(self):
"""""
filtering happens here on the query parameters.
"""
abc = self.request.GET.get('abc',None)
Now I have a defualt list function where I have to call serializer class only with the specific fields.
def list(self, request, *args, **kwargs):
queryset = self.get_queryset()
# data ={
# "name":queryset.
# }
# serializer = ExampleSerializer(data,many=True)
#serializer = serializers.serialize("json",queryset=queryset,fields=['id','name','address'])
return Response(serializer, status=status.HTTP_200_OK)
When I do print queryset it gives complex queryset and when I do print(type(queryset)),it gives the following
<class 'django.db.models.query.QuerySet'>
Now how to serialize name and address fields only to the exampleserializer class?? I did some digging and tried to do the following
#serializer = serializers.serialize("json",queryset=queryset,fields=['id','name','address'])
but it does not give the output in the required format not like regular json. Also it gives model: Example in the response of every object.
Did you try this?
queryset = self.get_queryset().values('name', 'address')
I'm not sure I fully understand what you are trying to fetch as your code is incomplete, but it seems that what you need is a ModelSerializer.
get_queryset() should be used to retrieve a queryset of objects that will be used by the serializer thanks to DRF inheritance & mixins system:
# Serializer
class ExampleSerializer(serializers.ModelSerializer):
class Meta:
model = Example
fields = ('id', 'name', 'address')
# View
class ExampleList(ListAPIView):
serializer_class = ExampleSerializer
def get_queryset(self):
return Example.objects.filter(...)

How to override POST method for bulk adding - Django Rest Framework

I'm writing a REST API using Django Rest Framework and I would like that one of my routes accepts bulk adding on POST method, to create multiple objects. Others methods (GET, PUT, PATCH, DELETE) will still remain the same, accepting only one at a time.
What I have so far is below and it's currently working just fine for posting one at a time.
In my urls.py:
path('book', books.BookViewSet.as_view()),
books.py:
class BookViewSet(viewsets.ModelViewSet):
serializer_class = BookSerializer
queryset = Book.objects.all()
permission_classes = (IsAuthenticated, )
serializer.py:
class BookSerializer(serializers.ModelSerializer):
def create(self, validated_data):
# I assume this is the method to be overridden to get this
class Meta:
model = Book
fields = ('id', 'name', 'author_id', 'page_number', 'active')
Serializer create method, unfortunatelly creates data object by object.You can override create method of ModelViewSet and after validation use bulk_create method.
def create(self, request, *args, **kwargs):
many = True if isinstance(request.data, list) else False
serializer = BookSerializer(data=request.data, many=many)
serializer.is_valid(raise_exception=True)
author = request.user # you can change here
book_list = [Book(**data, author=author) for data in serializer.validated_data]
Book.objects.bulk_create(book_list)
return Response({}, status=status.HTTP_201_CREATED)

django rest's viewset calls default serializer's create method instead of overriden method

I have a nested serializer with overridden create method:
class OrderSerializer(serializers.ModelSerializer):
data_model=Order
user = UserSerializer(many=False)
class Meta:
model = Order
fields = ['uid', 'user','price']
def create(self, validated_data):
validated_data=validated_data.pop('user')
order=self.data_model.objects.create(**validated_data)
order.user=self.context['request'].user
order.save()
return order
class LifeOrderSerializer(OrderSerializer):
data_model =LifeOrder
class Meta(OrderSerializer.Meta):
model = LifeOrder
fields = OrderSerializer.Meta.fields + [ "birth_year",
"contract_duration",]
and in the views.py
class OrderViewSet(viewsets.ModelViewSet):
queryset_model = LifeOrder
serializer_class = LifeOrderSerializer
def get_queryset(self):
self.queryset_model.objects.all()
but when I send a post request to create , model serializers defualt create method gets called! what is the problem?
The serializer_class mentioned in the viewset is LifeOrderSerializer, I think it must be OrderSerializer, since that is the serializer where the create() is overridden.
In the case where LifeOrderSerializer is the child of OrderSerializer, please make sure that you have overridden the create() method of LifeOrderSerializer to make it work.
Something like:
def create(self, validated_data):
order = super(OrderSerializer, self).create(validated_data)
return order
It was ModelViewSet's CreateModelMixin that raised the exception before calling my own create function.
I ovveride create of modelview set and the problem was solved!

django rest framework add field when not in list view

I'm using the Django Rest Framework and I'd like to be able to add extra detail to the serializer when a single object is returned, which would be left out of the list view.
In the code below I add the celery_state field to the TestModelSerializer, but I'd only like this field to be added when its returning a single object, not when it's returning the list of TestModel data.
I've looked at the list_serializer_class option but it seems to just use the original model serializer so it will always still include the field even if I try to exclude from there.
What are my options?
class TestModelSerializer(serializers.HyperlinkedModelSerializer):
celery_state = serializers.CharField(source='celery_state', read_only=True)
class Meta:
model = TestModel
class TestModelViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows TestModels to be viewed or edited.
"""
authentication_classes = (SessionAuthentication, BasicAuthentication)
permission_classes = (IsAuthenticatedOrReadOnly,)
queryset = TestModel.objects.all()
serializer_class = TestModelSerializer
Since the serializer class (used by the viewsets) passes many argument, you can use that to control the fields output:
class TestModelSerializer(serializers.HyperlinkedModelSerializer):
# ...
def __init__(self, *args, **kwargs):
super(TestModelSerializer, self).__init__(*args, **kwargs)
if kwargs.get('many', False):
self.fields.pop('celery_state')
Inspired by #mariodev answer:
The other possibility is to override many_init static method in serializer. Acording comments in thie code (https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/serializers.py#L128 ) it is suggested variant.
from rest_framework import serializers
class ExtendedSerializer(serializers.Serializer):
...
#classmethod
def many_init(cls, *args, **kwargs):
kwargs['child'] = cls()
kwargs['child'].fields.pop('extractedFiled')
return serializers.ListSerializer(*args, **kwargs)
You can have an extra serializer called ExtendedTestModelSerializer which would contain the extra fields that you want.
After that, you can use the get_serializer_class method to decide which serializer is used based on request.action -
class TestModelViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows TestModels to be viewed or edited.
"""
authentication_classes = (SessionAuthentication, BasicAuthentication)
permission_classes = (IsAuthenticatedOrReadOnly,)
queryset = TestModel.objects.all()
# serializer_class = TestModelSerializer
get_serializer_class(self):
if self.request.action == 'list':
return TestModelSerializer
return ExtendedTestModelSerializer

Categories