Updating a model using Django Rest Framework and ViewSets - python

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.

Related

Optimizing DRF post request handling by preventing full response serialization back

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

Is it complusory to pass instance to serializer in DRF?

Suppose I have a serializeDeviceGroup and a APIView class for posting devices into the group.
The serializer for DeviceGroup is
class DeviceGroupSerializer(serializers.ModelSerializer):
id = serializers.UUIDField(source='token', format='hex', read_only=True)
class Meta:
model = DeviceGroup
fields = ['id','name']
class DevicesGroupsAPIView(APIView):
permission_classes = (permissions.IsAuthenticated,)
def post(self, request, token=None, format=None):
print('reqquest', request.data)
print('token', token)
device_group_instance = DeviceGroup.objects.get(token=token)
for device_token in request.data['devices']:
device = Device.objects.get(token=device_token, owner=request.user)
device.group = device_group_instance
device.save()
In above post function, is it compulsory to create a instance of serializer and check if serializer is valid then return the response.
The relation between Device and DeviceGroup is a device can be on only one group and a group can have multiple devices(list of device ids)
How should the post function be if i need to use DeviceGroupSerializer to post the list of devices? I did not understand this serializer and view part clearly.
Django REST framework is loosely coupled so you can bypass serializers.
However, depending on what you are doing, this may requires some work. Note that for POST you may perform some checks by yourself instead.
Tom Christies post on Django REST framework performances optimization illustrate how you can remove parts of the framework.

Django rest framework create-only serializer field

I'm have a Django model that serves as a request description. It is created to issue a request by a REST client, serves to record the tasks current status, and record historical requests received by clients.
This model has a few fields that are used to fine-tune and control the requested task (say, a target object and the type of action). Obviously, I'd like the client to control those fields on object creation but not afterwards (you can't change the object once the task started running).
I was hoping for something similar to serializers.ReadOnlyField, so I could have something similar to this:
class TaskSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')
task_id = serializers.ReadOnlyField()
target_object = serializers.CreateOnlyField()
but couldn't find it in the documentation or google.
Just to expand on Wim's answer, this is a way to select a different serialiser based on the incoming request method:
class RequestViewSet(viewsets.ModelViewSet):
serializer_class = RequestModelSerializer
model = Request
def get_serializer_class(self):
serializer_class = self.serializer_class
if self.request.method == 'POST':
serializer_class = SerializerWithoutCertainFields
return serializer_class
The answer of #fabio.sussetto put me on the right track. I think my answer is slightly prettier; I don't specify the serializer on the class directly but only in get_serializer_class(). Also, I do not switch it based on the HTTP type (i.e. POST) but rather on the action, update, which I think is more declarative.
class RequestViewSet(viewsets.ModelViewSet):
model = Request
def get_serializer_class(self):
if self.action == 'update':
return serializer_class = SerializerWithoutCertainFields
return RequestModelSerializer
This can be achieved with one serializer by using to_internal_value method
class TaskSerializer(serializers.ModelSerializer):
# Field settings here
def to_internal_value(self, data):
data = super().to_internal_value(data)
# Remove target_object if serializer is updating object
if self.instance:
data.pop('target_object', None)
return data
class Meta:
model = Task
fields = ('owner', 'task_id', 'target_object')
could also be done with a combination of required=False and dropping the field value when updating like in this example:
class SectionSerializer(serializers.ModelSerializer):
# do not require field lesson when updating
lesson = serializers.PrimaryKeyRelatedField(queryset=Lesson.objects.all(), required=False)
# do not allow changing the lesson field
def update(self, instance, validated_data):
validated_data.pop("lesson", None)
return super().update(instance, validated_data)

Django filtering and deleting

I have this modelViewSet
class LikeViewSet(viewsets.ModelViewSet):
queryset = Likes.objects.all()
serializer_class = LikeSerializer
filter_fields = ('user','post')
def delete(self, request, pk, format=None):
post = Likes.objects.get(pk=pk)
post.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
I'm trying to filter using the url such as:
http://localhost:8000/likes/?user=anon&post=1
And then delete that specific result that I get from django but django keeps on giving me
delete() takes at least 3 arguments (2 given)
I can't really figure out why. Can anyone help please? Thanks! I'm using Django Rest Framework
EDIT: This is the model for the LikeViewSet:
class Likes(models.Model):
user = models.ForeignKey(Profile, related_name='liker')
post = models.ForeignKey(Post, related_name=' post' )
created = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ('created',)
The idea is, it's a model table for a relationship between a user model and a post model so the filtering has to be done in the url that way
When you're using a ViewSet, you should use the destroy() method rather than delete().
See documentation here:
A ViewSet class is simply a type of class-based View, that does not
provide any method handlers such as .get() or .post(), and instead
provides actions such as .list() and .create().
Based on your code, it doesn't look like you're doing anything unique in the destroy/delete method. Are you fine with just using the default destroy function?

How Can I Use Two Different Model Serializers With the Same Model?

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'})),

Categories