I'm using django-rest-framework. I have a model with a relation. I would like to just display the count of related items when a user hits the /modelname/ URL, but show the full related set when a user hits a specific model instance at /modelname/1/.
I can almost get what I want.
I have two serializers, like so:
class DataSetSerializer(serializers.ModelSerializer):
revisions = serializers.RelatedField(source='datasetrevision_set', many=True)
class Meta:
model = DataSet
fields = ('id', 'title', 'revisions')
class ShortDataSetSerializer(serializers.ModelSerializer):
class Meta:
model = DataSet
fields = ('id', 'title', 'revisions')
If I use the short version, I get the count of revisions (it's a calculated field). If I use the long version, I get the full list of related items as "revisions".
Short:
[{"id": 1, "title": "My Data Set", "revisions": 0}]
Long:
[{"id": 1, "title": "My Data Set", "revisions": ["Data Set v1", "Data Set v2"]}]
What I want to do is be able to switch between them based on query parameters (url). I tried to set the serializer_class to the ShortDataSetSerializer when the ID was not present, but it overrode all cases, not just the non-ID case.
class DataSetViewSet(viewsets.ModelViewSet):
serializer_class = DataSetSerializer
model = DataSet
def get_queryset(self):
try:
id = self.kwargs['id']
queryset = DataSet.objects.filter(id=id)
except KeyError:
queryset = DataSet.objects.all()
# We want to only list all of the revision data if we're viewing a
# specific set, but this overrides for all cases, not just the one
# we want.
self.serializer_class = ShortDataSetSerializer
return queryset
Is there a way I can make this work? I realize I may be approaching this in a totally ridiculous manner, but it seems like there should be an easy solution.
The data example I gave rather abbreviated compared to the real data I'm working with. The end goal is to show a subset of fields in list view, and every field in the GET for a specific ID. This is a read-only API, so I don't need to worry about POST/PUT/DELETE.
You could do it by overriding the get_serializer_class method:
class DataSetViewSet(viewsets.ModelViewSet):
model = DataSet
def get_queryset(self):
queryset = DataSet.objects.all()
if self.kwargs.get('id'):
queryset = queryset.filter(pk=self.kwargs.get('id'))
return queryset
def get_serializer_class(self):
return DataSetSerializer if 'id' in self.kwargs else ShortDataSetSerializer
I think one easy solution for this problem would be to use class based generic views instead of a viewset.
You can use a list create api view with serializer_class as ShortDataSetSerializer. So when you get the list of data it will have the count of revisions. Also if you want the post request to work on the same url you will then have to override the get_serializer_class method to set the serializer_class based on request type.
For the retrieve view you can use the serializer_class as DataSetSerializer. It will have a list of revisions instead of count.
Checkout generic views api guide on DRF docs website.
Also, you can override the list and retrieve methods on the viewset, but I would prefer to use class based views since DRF has a lot of additional functionalities attached to the request functions like get, put etc.(or list, detail) and it is better not to override them.
Thank you, Benjamin. That didn't do what quite I was looking for. Ultimately what I had to do was this (with the same serializers as above):
class DataSetViewSet(viewsets.ModelViewSet):
model = DataSet
def list(self, request):
queryset = DataSet.objects.all()
serializer = ShortDataSetSerializer(queryset, many=True)
return Response(serializer.data)
def detail(self, request, id=None):
queryset = DataSet.objects.get(id=id)
serializer = DataSetSerializer(queryset)
return Response(serializer.data)
And in the urls.py:
url(r'^sets/$', views.DataSetViewSet.as_view({'get': 'list'})),
url(r'^sets/(?P<id>\d+)/$', views.DataSetViewSet.as_view({'get': 'detail'})),
Related
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(...)
In a website built with Django I've got a model which has a CharField:
class Person(models.Model):
name = models.CharField(max_length=255)
age = models.IntegerField()
categories = models.CharField(max_length=255)
The CharField can contain a list of strings which are comma separated. For example: ADC,HJD,RTP
Using Django Rest Framework I created a POST endpoint to which people can post new records. That field is posted in json as an array though. So the json looks like this:
{
"name": "John",
"age": 25,
"categories": ["ADC", "HJD", "RTP"]
}
I wanted to simply join() the array in the Serializer create() method. But it never reaches that point because it gets filtered out by the validator. I guess the validation is done in the view, but I'm unsure where to start digging.
Does anybody know how I can make the endpoint accept an array and turn it into a comma separated string in a CharField?
[EDIT]
These are my ViewSets:
class DevicesViewSet(DatapuntViewSetWritable):
queryset = Device.objects.all().select_related('owner', 'contact').prefetch_related('types').order_by('id')
serializer_class = DeviceSerializer
serializer_detail_class = DeviceSerializer
http_method_names = ['post', 'list', 'get']
class ContactViewSet(CreateModelMixin, GenericViewSet):
queryset = Device.objects.none()
serializer_class = IotContactSerializer
pagination_class = None
Based on the edited question, here is what you can do. Inherit in your view viewsets.ViewSet and override the create method and write your concatenation logic. Check here
class CreatePersonViewSet (viewsets.ViewSet):
def create(self, request):
"""
Do your logic here... to create
"""
pass
I have defined AViewSet and ASerializer for my AModel:
class AModel(Model):
name = CharField(16)
text = TextField()
related = ForeignField('ARelated')
class AViewSet(ModelViewSet):
queryset = AModel.objects.all()
serializer_class = ASerializer
class ASerializer(Serializer):
class Meta(object):
model = AModel
fields = '__all__'
I wrote a RESTful client that posts a lot of data to that view/endpoint in multiple requests, creating many AModel records. I have noticed, however that a significant part of the server time is spent on generating the response, and upon googling for a bit I found several references to the nested relationship hazard which seems like a decent fix, but got me wondering:
I already know what I posted and I don't need the pks, so could I prevent that serialization response from happening entirely? Can I instead just serialize the number of rows inserted?
Taking a look at DRF's CreateModelMixin class:
class CreateModelMixin(object):
"""
Create a model instance.
"""
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)
I realized I could override the create method and reimplement it without returning the serializer.data as part of the response, so it'll look similar to this:
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({}, status=status.HTTP_201_CREATED, headers=headers)
I have two questions regarding this approach:
Does this practice of preventing the full serialization of objects created with a POST makes sense wrt RESTful design patterns, approach, ideology, etc?
Will this actually avoid selecting all the related data (as well as execute any SerializerMethodFields, etc?
Let's take the basic ModelViewset and ModelSerializer combination here :) It will be like,
# serializers.py
class SampleSerializer(serializers.ModelSerializer):
class Meta:
model = SampleModel
fields = '__all__'
# views.py
class SampleViewset(viewsets.ModelViewSet):
queryset = SampleModel.objects.all()
serializer_class = SampleSerializer
Why DRF returning all the data back to client ?
Here, the SampleViewset is using SampleSerializer everytime and it will serialilze all fields defined in the serializer class. As per the current configuration
What is the possible solution?
The possible solution is for this is stop serialization process of certain fields by some means :)
How to do?
As far as I knew, this can be done in two ways.
1. Use a minimal SampleSerializer class for POST method
2. override the to_representation() method of SampleSerializer on POST requests
Method-1 : Use different serializer
Define a new serializer class with fields which are you wish to send and retrive while POST request
class SampleSerializerMinimal(serializers.ModelSerializer):
class Meta:
model = SampleModel
fields = ('id', 'name', 'age')
Now, we've to tell viewsets to use this serializer for POST methods, it can be done in get_serializer_class() of viewset
class SampleViewset(viewsets.ModelViewSet):
queryset = SampleModel.objects.all()
serializer_class = SampleSerializer
def get_serializer_class(self):
if self.request.method == 'POST':
return SampleSerializerMinimal
return SampleSerializer
Method-2 : Override the to_representation() method
class SampleSerializer(serializers.ModelSerializer):
class Meta:
model = SampleModel
fields = '__all__'
def to_representation(self, instance):
try:
if self.context['view'].request.method == 'POST':
return {
"id": instance.id,
"name": instance.name,
"age": instance.age
}
except KeyError:
return super(SampleSerializer, self).to_representation(instance)
return super(SampleSerializer, self).to_representation(instance)
What is the best way ?
I felt Method-1 is more DRF way of doing things, but you can't add id only to the fields because, POST request may require more fields.
The Method-2 to is also good, but its not much clean if you want to return n fields, and writing it in your to_representation() method
UPDATE-1
Method-3: Combination of method-1 and method-2
# serializer.py
class SampleSerializerMinimal(serializers.ModelSerializer):
class Meta:
model = SampleModel
fields = ('id', 'name', 'age')
def to_representation(self, instance):
"""
we don't have to check the request method, because DRF routing only POSt requests to this serializer
"""
return {"id": instance.id}
# views.py
class SampleViewset(viewsets.ModelViewSet):
queryset = SampleModel.objects.all()
serializer_class = SampleSerializer
def get_serializer_class(self):
if self.action.method == 'POST':
return SampleSerializerMinimal
return SampleSerializer
UPDATE-2
Does this practice of preventing the full serialization of objects created with a POST makes sense wrt RESTful design patterns, approach, ideology, etc?
the .data is calling to_representation() method, which calls the related objects and all other fields in the serializer. ( Source code of data property of serializer ) So, if you can avoid that .data call, it would be nice!.
Since I've seen many API responses with single detail like {"status":true} after a POST request, I don't think your approch overkill the DRF patterns and other stuff
Will this actually avoid selecting all the related data (as well as execute any SerializerMethodFields, etc?
Yes. As I said above, It won't call the serializations process unless calling the .data
I am developing a hierarchical api for the models of my app, example:
https: //www.example.com/model/pk/submodel/pk2/subsubmodel/pk3 ...etc
so because of that I have to personalizate the generic view class adding the corresponding method for each action example:(def list(), def retrieve() ). The problem of that is things like pagination and filtering doesn't work, I have already solved the problem of pagination by adding a few lines of code in the list method but I have not found any way to solve the problem with the filters.
here's an example of my code:
the serializer:
class RegionSerializerList(serializers.ModelSerializer):
class Meta:
model = Region
fields = ('id', 'name')
The view:
class RegionList(generics.ListCreateAPIView):
queryset = Region.objects.all()
serializer_class = RegionSerializerList
pagination_class = PostPageNumberPagination
filter_backends = [SearchFilter, OrderingFilter]
search_fields = ['name', 'id']
def list(self, request, pk):
self.pagination_class = PostPageNumberPagination
queryset = Region.objects.filter(country=pk)
page = self.paginate_queryset(queryset)
#the following 3 lines fix the pagination problem
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = RegionSerializerList(queryset, many=True)
return Response(serializer.data)
the filters botom appears in the web but any function works, Neither the order nor the search works, It continues showing all the same data of the list, Using the URL filter (/?search=) doesn't, and neither using the get_queryset method.
I'm using filter_backends with SearchFilter, OrderingFilter and the search_fields that work perfectly without the personalised method list, but if i used that method doesn't works I don't know why and how to fix that. :(
I need the filter to work without having to change the personalized methods on which my api depends
Then I wonder if anyone here knows how to solve this problem and give me a hand, I would appreciate it a lot, thanks
I'm new to DRF, and I'm trying to build a webhook that gives out lists of model objects and also allows these objects to be updated. I followed this tutorial http://www.django-rest-framework.org/tutorial/quickstart/, and have the following serializer and view:
class Task(serializers.ModelSerializer):
class Meta:
model = Task
fields = ('user', 'task', 'unixTime')
View:
class RequestViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows reqests to be viewed or edited.
"""
queryset = Task.objects.filter(done = False).order_by('-unixTime')
serializer_class = Task
paginate_by = None
def list(self, request, *args, **kwargs):
self.object_list = self.filter_queryset(self.get_queryset())
serializer = self.get_serializer(self.object_list, many=True)
return Response({'results': serializer.data})
I'm pretty sure I have to include a def update under def list, but the online resources I found were a bit unclear on how to implement them and what they do. Any help is appreciated.
#hackerman, Hmm..., if you followed the next step,
http://www.django-rest-framework.org/tutorial/quickstart/#urls
You will get an api address, it may looks like http://localhost:8000/task/1/, assume here is a task obj (id=1) in your db. Please open it in your browser and check that api works or not.
And then, you need a http client (requests is a good choice) to create a PUT request with json string data.
Hope those can help.
May be you just need to rename the serializer.
class TaskSerializer(serializers.ModelSerializer):
And don't forget replace in the viewset
serializer_class = TaskSerializer
After it you can remove your list method, because it is standard.