I have the following serilizers
class AutoSerializer(serializers.ModelSerializer):
class Meta:
model = Auto
fields = ("nombre",)
class MarcaSerializer(WritableNestedModelSerializer):
autos = AutoSerializer(many=True)
class Meta:
model = Marca
fields = ("codigo", "descripcion", "autos")
ModelViewSet
class MarcaViewSet(viewsets.ModelViewSet):
queryset = Marca.objects.all()
serializer_class = MarcaSerializer
def list(self, request, *args, **kwargs):
queryset = self.queryset
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
Querys
How can you optimize access to the base, that is, make fewer inquiries
By using a .prefetch_related(..) that fetches the related Auto instances in one fetch:
class MarcaViewSet(viewsets.ModelViewSet):
queryset = Marca.objects.prefetch_related('autos').all()
serializer_class = MarcaSerializer
def list(self, request, *args, **kwargs):
queryset = self.queryset
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
This will first fetch the Marca objects, and then look with a JOIN for all the Auto objects that are related, and fetch all these objects with a single fetch into memory as well.
So the Auto objects are loaded in bulk instead of lazily each time fetch the Autos for a specific Marca object.
This kind of optization is documented in the article mentioned by #Jerin Peter George: "Optimizing slow Django REST Framework performance".
This article also discusses how to specify such prefetches at the side of the serializer, such that in case other tasks are done, the prefetch is not done. So we could for example write:
class AutoSerializer(serializers.ModelSerializer):
class Meta:
model = Auto
fields = ("nombre",)
class MarcaSerializer(WritableNestedModelSerializer):
autos = AutoSerializer(many=True)
#classmethod
def setup_eager_loading(cls, queryset):
return queryset.prefetch_related('autos')
class Meta:
model = Marca
fields = ("codigo", "descripcion", "autos")
and then write:
class MarcaViewSet(viewsets.ModelViewSet):
queryset = Marca.objects.all()
serializer_class = MarcaSerializer
def list(self, request, *args, **kwargs):
serializer = self.get_serializer
queryset = serializer.setup_eager_loading(self.queryset)
serializer = serializer(queryset, many=True)
return Response(serializer.data)
Related
I am trying to use PageNumberPagination and FilterSet in APIView.
But I have an error below in my code.
object of type 'ListSerializer' has no len()
How to implement this?
Here are the code:
class MySerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = '__all__'
class MyFilter(filters.FilterSet):
class Meta:
model = MyModel
fields = '__all__'
class MyAPIView(views.APIView, PageNumberPagination):
def get(self, request, *args, **kwargs):
filterset = MyFilter(request.query_params, queryset=MyModel.objects.all())
if not filterset.is_valid():
raise ValidationError(filterset.errors)
serializer = MySerializer(instance=filterset.qs, many=True)
paginate_queryset = self.paginate_queryset(serializer, request, view=self)
return Response(paginate_queryset.data)
Django 3.2.6
django-filter 22.1
djangorestframework 3.12.4
Python 3.8
You should not try to make your object inherit from APIView and PageNumberPagination, as one is a view, and the other one, a paginator.
If you want to keep your code as simple as possible, just use a ListAPIView:
class MyAPIView(ListAPIView):
queryset = MyModel.objects.all()
serializer_class = MySerializer
filterset_class = MyFilter
pagination_class = PageNumberPagination
If you want to get this pagination as the default one, you can even set it as the default pagination style once so you don't have to define it in each of the views that use it.
Apply Pagination on ApiView
views.py
from rest_framework.pagination import PageNumberPagination
class EventNewsItems(APIView, PageNumberPagination):
def get(self, request, pk, format=None):
event = Event.objects.get(pk=pk)
news = event.get_news_items().all()
results = self.paginate_queryset(news, request, view=self)
serializer = NewsItemSerializer(results, many=True)
return self.get_paginated_response(serializer.data)
I changed the code and it works
class MyAPIView(views.APIView, PageNumberPagination):
def get(self, request):
filterset = MyFilter(request.query_params, queryset=MyModel.objects.all())
results = self.paginate_queryset(filterset.qs, request, view=self)
serializer = Serializer(results, many=True)
return self.get_paginated_response(serializer.data)
I have a method field called followers. I get the list of followers in a SerializerMethodField :
followers = serializers.SerializerMethodField()
I want to format the result with a specific serializer called BaseUserSmallSerializer. How should I implement the method get_followers to achieve that ?
Try this;
followers = BaseUserSmallSerializer(source='get_followers', many=True)
OR
You can use serializer inside methodfield;
def get_followers(self, obj):
followers_queryset = #get queryset of followers
return BaseUserSmallSerializer(followers_queryset, many=True).data
If you prefer a more generic solution:
SerializerMethodNestedSerializer which works same as serializers.SerializerMethodField but wraps the result with the passed serializer and returns a dict
class SerializerMethodNestedSerializer(serializers.SerializerMethodField):
"""Returns nested serializer in serializer method field"""
def __init__(self, kls, kls_kwargs=None, **kwargs):
self.kls = kls
self.kls_kwargs = kls_kwargs or {}
super(SerializerMethodNestedSerializer, self).__init__(**kwargs)
def to_representation(self, value):
repr_value = super(SerializerMethodNestedSerializer, self).to_representation(value)
if repr_value is not None:
return self.kls(repr_value, **self.kls_kwargs).data
Usage
class SomeSerializer(serializers.ModelSerializer):
payment_method = SerializerMethodNestedSerializer(kls=PaymentCardSerializer)
def get_payment_method(self, obj):
return PaymentCard.objects.filter(user=obj.user, active=True).first()
class Meta:
model = Profile
fields = ("payment_method",)
class PaymentCardSerializer(serializers.ModelSerializer):
class Meta:
fields = ('date_created', 'provider', 'external_id',)
model = PaymentCard
The expected output of SerializerMethodNestedSerializer(kls=PaymentCardSerializer)
None or {'date_created': '2020-08-31', 'provider': 4, 'external_id': '123'}
I am new to DRF and I am trying to write custom view / serializer that I can use to update just one field of user object.
I need to make logic just to update the "name" of the user.
I wrote serializer:
class ClientNameSerializer(serializers.ModelSerializer):
class Meta:
model = ClientUser
fields = ('name',)
def update(self, instance, validated_data):
instance.name = validated_data.get('name', instance.name)
instance.save()
return instance
This method is never called. I tried setting breakpoint there and debug it, but it is never called, even if I use PUT, POST or PATCH methods. If I add create method it is being called when I use POST.
This is how my view looks like:
class UpdateName(generics.CreateAPIView):
queryset = ClientUser.objects.all()
serializer_class = ClientNameSerializer
permission_classes = (permissions.IsAuthenticated,)
Does anyone have some suggestion? Thanks!
My models.py looks like this
class ClientUser(models.Model):
owner = models.OneToOneField(User,unique=True,primary_key=True)
phone_number = models.CharField(validators=[PHONE_REGEX],max_length=20,unique=True)
name = models.CharField(max_length=100,blank=True)
status = models.IntegerField(default=1)
member_from = models.DateTimeField('member from',auto_now_add=True)
is_member = models.BooleanField(default=False)
The definition of what methods the endpoint can accept are done in the view, not in the serializer.
The update method you have under your serializer needs to be moved into your view so you'll have something like:
serializers.py
class ClientNameSerializer(serializers.ModelSerializer):
class Meta:
model = ClientUser
views.py
class UpdateName(generics.UpdateAPIView):
queryset = ClientUser.objects.all()
serializer_class = ClientNameSerializer
permission_classes = (permissions.IsAuthenticated,)
def update(self, request, *args, **kwargs):
instance = self.get_object()
instance.name = request.data.get("name")
instance.save()
serializer = self.get_serializer(instance)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
return Response(serializer.data)
Take note that you're overriding the UpdateModelMixin and you might need to change the above code a little bit to get it right.
If you use class UpdateName(generics.CreateAPIView), this will only call a create() method on the serializer.
You should subclass generics.UpdateAPIView instead. And that's it.
You do not have to move your method to the view as suggested in this answer (it is basically copying/duplicating the UpdateModelMixin's update method)
For more information how serializers work regarding saving/updating see the docs here:
One other approach might be the following one:
serializer.py
class ClientNameSerializer(serializers.ModelSerializer):
class Meta:
model = ClientUser
fields = ('name',)
def update(self, instance, validated_data):
instance.name = validated_data.get('name', instance.name)
instance.save()
return instance
views.py
class UpdateName(generics.UpdateAPIView):
queryset = ClientUser.objects.all()
serializer_class = ClientNameSerializer
permission_classes = (permissions.IsAuthenticated,)
def update(self, request, *args, **kwargs):
data_to_change = {'name': request.data.get("name")}
# Partial update of the data
serializer = self.serializer_class(request.user, data=data_to_change, partial=True)
if serializer.is_valid():
self.perform_update(serializer)
return Response(serializer.data)
I have a case where the values for a serializer field depend on the identity of the currently logged in user. I have seen how to add the user to the context when initializing a serializer, but I am not sure how to do this when using a ViewSet, as you only supply the serializer class and not the actual serializer instance.
Basically I would like to know how to go from:
class myModelViewSet(ModelViewSet):
queryset = myModel.objects.all()
permission_classes = [DjangoModelPermissions]
serializer_class = myModelSerializer
to:
class myModelSerializer(serializers.ModelSerializer):
uploaded_by = serializers.SerializerMethodField()
special_field = serializers.SerializerMethodField()
class Meta:
model = myModel
def get_special_field(self, obj):
if self.context['request'].user.has_perm('something.add_something'):
return something
Sorry if it wasn't clear, from the DOCs:
Adding Extra Context
Which says to do
serializer = AccountSerializer(account, context={'request': request})
serializer.data
But I am not sure how to do that automatically from the viewset, as I only can change the serializer class, and not the serializer instance itself.
GenericViewSet has the get_serializer_context method which will let you update context:
class MyModelViewSet(ModelViewSet):
queryset = MyModel.objects.all()
permission_classes = [DjangoModelPermissions]
serializer_class = MyModelSerializer
def get_serializer_context(self):
context = super().get_serializer_context()
context.update({"request": self.request})
return context
For Python 2.7, use context = super(MyModelViewSet, self).get_serializer_context()
For Function based views you can pass request or user as follows:
serializer = ProductSerializer(context = {"request": request}, data=request.data)
Your Serializer may look like:
class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = ["id"]
def create(self, validated_data):
user = self.context["request"].user
print(f"User is: {user}")
Feel free to inform if there is any better way to do this.
just use get_serializer() in your viewsets
def get_serializer(self, *args, **kwargs):
"""
Return the serializer instance that should be used for validating and
deserializing input, and for serializing output.
"""
serializer_class = self.get_serializer_class()
kwargs['context'] = self.get_serializer_context()
return serializer_class(*args, **kwargs)
Return parent context in overrided function get_serializer_context will make it easy to access request and its data.
class myModelViewSet(ModelViewSet):
queryset = myModel.objects.all()
permission_classes = [DjangoModelPermissions]
serializer_class = myModelSerializer
def get_serializer_context(self):
"""
pass request attribute to serializer
"""
context = super(myModelViewSet, self).get_serializer_context()
return context
This is very stable as every time we request viewset, it returns context as well.
the values for a serializer field depend on the identity of the currently logged in user
This is how I handle such cases in my ModelViewSet:
def perform_create(self, serializer):
user = self.request.user
if user.username == 'myuser':
serializer.data['myfield'] = 'something'
serializer.save()
Simply add this 2 line method in your class and you are good to go.
def get_serializer_context(self):
return {'request': self.request}
since the posted answers had partial correctness, summarizing here in the interest of completeness.
override get_serializer_context..AND
use get_serializer in your views instead of manually calling the serializer
In Django REST Framework API, list of database table records are not getting updated until the API restart or any code change in python files like model, serializer or view. I've tried the transaction commit but it didn't worked. Below is my view :
class ServiceViewSet(viewsets.ModelViewSet):
#authentication_classes = APIAuthentication,
queryset = Service.objects.all()
serializer_class = ServiceSerializer
def get_queryset(self):
queryset = self.queryset
parent_id = self.request.QUERY_PARAMS.get('parent_id', None)
if parent_id is not None:
queryset = queryset.filter(parent_id=parent_id)
return queryset
# Make Service readable only
def update(self, request, *args, **kwargs):
return Response(status=status.HTTP_400_BAD_REQUEST)
def destroy(self, request, *args, **kwargs):
return Response(status=status.HTTP_400_BAD_REQUEST)
Serializer looks like this :
class ServiceSerializer(serializers.ModelSerializer):
class Meta:
model = Service
fields = ('id', 'category_name', 'parent_id')
read_only_fields = ('category_name', 'parent_id')
and model looks like this :
class Service(models.Model):
class Meta:
db_table = 'service_category'
app_label = 'api'
category_name = models.CharField(max_length=100)
parent_id = models.IntegerField(default=0)
def __unicode__(self):
return '{"id":%d,"category_name":"%s"}' %(self.id,self.category_name)
This problem is occuring only with this service, rest of the APIs working perfectly fine. Any help will be appreciated.
Because you are setting up the queryset on self.queryset, which is a class attribute, it is being cached. This is why you are not getting an updated queryset for each request, and it's also why Django REST Framework calls .all() on querysets in the default get_queryset. By calling .all() on the queryset, it will no longer use the cached results and will force a new evaluation, which is what you are looking for.
class ServiceViewSet(viewsets.ModelViewSet):
queryset = Service.objects.all()
def get_queryset(self):
queryset = self.queryset.all()
parent_id = self.request.QUERY_PARAMS.get('parent_id', None)
if parent_id is not None:
queryset = queryset.filter(parent_id=parent_id)
return queryset