Is it complusory to pass instance to serializer in DRF? - python

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.

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

Updating a model using Django Rest Framework and ViewSets

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.

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)

How to use different Django Rest Framework serializers for the same request in Generic Views?

I'm using Django Rest Framework in an API project and am trying to figure out if there's a way to use two different serializers with the generic views (e.g. CreateAPIView). I want to use one serializer for deserializing a POST request, and a different one for serializing the resulting response.
Here's what I'm trying to do; I'll illustrate using the Album/Track examples from the docs:
The model that I'm working with has a ForeignKey relationship. In the API, I'd like to just be able to include the FK in the request when assigning the relationship, so in the serializer I'm using a PrimaryKeyRelatedField, similar to how the AlbumSerializer handles the relationship to Tracks:
class CreateAlbumSerializer(serializers.ModelSerializer):
tracks = serializers.PrimaryKeyRelatedField(many=True)
class Meta:
model = Album
fields = ('album_name', 'artist', 'tracks')
However, on the response, I'd like to include a full representation of the Album using a ModelSerializer, not just the PK, slug, etc., something like this:
class AlbumSerializer(serializers.ModelSerializer):
tracks = TrackSerializer(many=True, read_only=True)
class Meta:
model = Album
fields = ('album_name', 'artist', 'tracks')
class TrackSerializer(serializers.ModelSerializer):
class Meta:
model = Album
fields = ('order', 'title', 'duration')
The generic DRF views allow you to either specify the serializer_class or override the get_serializer_class method, but I can't figure out how to use that to accomplish what I'm after.
Is there something obvious that I'm missing? This seems like a reasonable thing to want to do, but I can't seem to grok how to get it done.
Approach #1
Overwrite the generic mixins in the DRF ViewSet. For example:
class MyViewSet(CreateModelMixin, MultipleSerializersViewMixin, ViewSet):
serializer_class = CreateAlbumSerializer
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
saved = self.perform_create(serializer)
serializer = self.get_serializer(instance=saved, serializer_class=AlbumSerializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def perform_create(self, serializer):
return serializer.save()
MultipleSerializersViewMixin is taken from django-rest-framework-braces.
Approach #2
Customize to_representation of the CreateAlbumSerializer. For example:
class MyViewSet(CreateModelMixin, ViewSet):
serializer_class = CreateAlbumSerializer
class CreateAlbumSerializer(serializers.ModelSerializer):
tracks = serializers.PrimaryKeyRelatedField(many=True)
class Meta:
model = Album
fields = ('album_name', 'artist', 'tracks')
def to_representation(self, instance):
data = super(CreateAlbumSerializer, self).to_representation(instance)
data['tracks'] = TrackSerializer(instance=instance.tracks).data
return data
Comparison
I personally like approach #1 instead of #2 even though it is more verbose since it does not leak any of the custom create/response logic to serializers. I think serializer should just know how to serialize and all custom requirements to pick different serializers for the job should be done in the views.

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?

Categories