Filtering serializer response data - python

I have a ManyToMany relation with tag and items:
class Tag(BaseModel):
name = models.CharField(max_length=255) # ToDo Change max length
description = models.TextField(blank=True, null=True)
class Item(BaseModel):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
image = models.ImageField(upload_to='items', blank=True)
title = models.TextField(blank=False, null=True)
message = models.TextField(blank=True, null=True)
fav_count = models.IntegerField(default=0)
tags = models.ManyToManyField(Tag, related_name='tags')
I need all fields to be serialized, but i wish to only limit the response values.
Example:
What I'm receiving now:
{
"user": 2,
"image": null,
"title": "test3",
"message": "testmessage",
"fav_count": 0,
"tags": [
{
"id": 7,
"name": "tag1",
"description": null
},
{
"id": 8,
"name": "tag2",
"description": null
}
]
}
But i only wish to receive the tag ids not the name and description...
My simple view:
if request.method == 'GET':
items = Item.objects.all()
serializer = ItemSerializer(items, many=True)
return Response(serializer.data)
Would i need to rebuild my response data to include/exclude or is there a better way to do this? (or if iv missed the terminology)

use PrimaryKeyRelatedField DRF field in your serializer
Example
class ItemSerializer(serializers.ModelSerializer):
tags = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
class Meta:
model = Item
fields = ('tags', 'image',.....other fields)
Response
{
'image': 'image1',
...........
'tags': [
89,
90,
91,
...
]
..........
}
In you want to do it dynamically based on a request parameter.
class ItemSerializer(serializers.ModelSerializer):
tags = serializers.SerializerMethodField()
def get_tags(self, obj):
if self.request.get('some_condition'):
data_tags = TagSerializer(obj.tags, many=True).data
data = map(data.pop('field_to_remove') for data in data_tags)
return list(data)
else:
return TagSerializer(obj.tags, many=True).data
Then, pass request to your serializer when you init it in your view.
serializer = ItemSerializer(data, context={'request':self.request})

You probaply using serializer for Tag model and declare it in ItemSerializer so view is showing full TagSerializer info.
If you want to show only pk field, just use default representation, don't declare special serializer for Tag in ItemSerializer

Related

Can I use a ModelViewSet for POST and GET of a parent of self in a model in Django REST?

Hello I have the following structure:
class Category(models.Model):
model.py
"""Class to represent the category of an Item. Like plants, bikes..."""
name = models.TextField()
description = models.TextField(null=True)
color = models.TextField(null=True)
# This will help to anidate categories
parent_category = models.ForeignKey(
'self',
on_delete=models.SET_NULL,
null=True,
)
Then I serialize it:
serializers.py:
class CategorySerializer(serializers.ModelSerializer):
"""Serializer for Category."""
class Meta: # pylint: disable=too-few-public-methods
"""Class to represent metadata of the object."""
model = Category
fields = ['id', 'name', 'description', 'color', 'parent_category']
And I Create my endpint
views.py:
class CategoryViewset(viewsets.ModelViewSet): # pylint: disable=too-many-ancestors
"""API Endpoint to return the list of categories"""
queryset = Category.objects.all()
serializer_class = CategorySerializer
pagination_class = None
Well this seems to work as expected to make a post request, for example sending this:
{
"name": "Plants",
"description": null,
"color": "#ef240d",
"parent_category": 1
}
But when I make a request of this I want to see the parent category and not have to do two requests. So I found from other questions that I could use an external library:
serializer.py
from rest_framework_recursive.fields import RecursiveField
class CategorySerializer(serializers.ModelSerializer):
"""Serializer for Category."""
parent_category = RecursiveField(many=False)
class Meta: # pylint: disable=too-few-public-methods
"""Class to represent metadata of the object."""
model = Category
fields = ['id', 'name', 'description', 'color', 'parent_category', 'category_name']
And then It seems to work:
{
"id": 2,
"name": "Flowers",
"description": null,
"color": "#ef240a",
"parent_category": {
"id": 1,
"name": "Plants",
"description": "something",
"color": "#26def2",
"parent_category": null,
},
},
But when I try to post now it will not work as It seems to expect an object instead of just the ID which is what I would have available in my frontend:
{
"parent_category": {
"non_field_errors": [
"Invalid data. Expected a dictionary, but got int."
]
}
}
Is it possible to mix somehow this two approaches in my ModelSerializer?
You can customise your ModelViewSet to use two serializers instead of one. For example
class CategoryViewset(viewsets.ModelViewSet):
queryset = Category.objects.all()
serializer_class = CategorySerializer
pagination_class = None
def create(self, request):
new_category = CategoryCreateSerializer(data=request.data)
if new_category.is_valid:
return Response(CategoryRetrieveSerializer(new_category).data)

Add extra field in response output in DRF 3.0

I have the following models
class Restaurant(models.Model):
name_of_the_restaurant = models.CharField(max_length=30, blank=True)
opening_time = models.TimeField(auto_now=False, auto_now_add=False)
closing_time = models.TimeField(auto_now=False, auto_now_add=False)
And
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
bio = models.TextField(max_length=500, blank=True)
city = models.CharField(max_length=30, blank=True)
country = models.CharField(max_length=30, blank=True)
postal_code = models.CharField(max_length=30, blank=True)
birth_date = models.DateField(null=True, blank=True)
favourite_restaurant = models.ManyToManyField(Restaurant,
blank=True,
related_name='favourite_restaurant',
related_query_name='favourite_restaurant')
I have defined a serializer for Restaurant model which is mainly :
class RestaurantSerializer(serializers.ModelSerializer):
class Meta:
model = Restaurant
fields = '__all__'
Now in my ViewSet logic I am doing the following :
class RestaurantListView(generics.ListAPIView):
serializer_class = RestaurantSerializer
def get_queryset(self):
queryset = {'Error': 'Please pass valid url parameters'}
city = self.request.query_params.get('city', None)
postal_code = self.request.query_params.get('postalcode', None)
country = self.request.query_params.get('country', None)
if city is not None or postal_code is not None:
queryset = Restaurant.objects.filter(
Q(city=city) | Q(pincode=postal_code))
if country and city is not None and postal_code is None:
queryset = Restaurant.objects.filter(country=country, city=city)
return queryset
def get(self, request, format=None):
restaurant_qs = self.get_queryset()
ids_list = [restaurant.id for restaurant in restaurant_qs]
favourite_restaurant = is_favourite_restaurant(ids_list, self.request.user)
serializer = RestaurantSerializer(restaurant_qs, many=True)
return Response(serializer.data)
where is_favourite_restaurant is a custom function function which returns queryset of FAVOURITE restaurant(s) of a user. Now in the output for this GET request I am getting result as :
[
{
"id": 2,
"name_of_the_restaurant": "Aniket",
"opening_time": "14:08:33.413402",
"closing_time": "22:08:33.413414"
},
{
"id": 3,
"name_of_the_restaurant": "Aniket-1",
"opening_time": "14:13:37.656385",
"closing_time": "22:13:37.656397"
}
]
Whereas the desired output I want is to append an extra field is_favourite:true to that restaurant which user has previously marked favourite. And hence the output should be
[
{
"id": 2,
"name_of_the_restaurant": "Aniket",
"opening_time": "14:08:33.413402",
"closing_time": "22:08:33.413414",
"is_favourite": true,
},
{
"id": 3,
"name_of_the_restaurant": "Aniket-1",
"opening_time": "14:13:37.656385",
"closing_time": "22:13:37.656397"
}
]
EDIT :
Definition of is_favourite_restaurant function :
def is_favourite_restaurant(restaurant_qs, user):
favourite_restaurant_qs = Profile.objects.get(user=user).favourite_restaurant.filter(
pk__in=restaurant_qs.values_list('id', flat=True))
return favourite_restaurant_qs
You can use SerializerMethodField. SerializerMethodField allows add extra field which is read only as you want.
class RestaurantSerializer(serializers.ModelSerializer):
is_favorite = serializers.SerializerMethodField()
class Meta:
model = Restaurant
fields = ('your', 'fields', 'is_favorite')
def get_is_like(self, obj):
return is_favourite_restaurant(obj.id, self.context['request'].user)
Normally, ListAPIView add context to serializer. As you use your create method, you should add manually.
serializer = RestaurantSerializer(restaurant_qs, many=True, context={'request': self.request})
Context allows access some data which is we send from the view.
As you did not shown your is_favourite_restaurant, i can't say that what should you do in that function. I guess you should change ids parameter from array to one id.
Your response looks like
[
{
"id": 2,
"name_of_the_restaurant": "Aniket",
"opening_time": "14:08:33.413402",
"closing_time": "22:08:33.413414",
"is_favourite": True,
},
{
"id": 3,
"name_of_the_restaurant": "Aniket-1",
"opening_time": "14:13:37.656385",
"closing_time": "22:13:37.656397",
"is_favourite": False,
}
]
def is_favourite_restaurant(restaurant_id, user):
favourite_restaurant_qs = Profile.objects.get(user=user).favourite_restaurant.filter(
pk=restaurant_id).exists()
return favourite_restaurant_qs

Queryset in one to many relation

I am trying to get a Json of elements with their related elements
I had two tables, Service and Room. One service have many rooms. I would like to get the service where have room_id = x.
Models
class Service(models.Model):
name = models.CharField(max_length=255, blank=True, null=True)
class Meta:
managed = True
db_table = 'Service'
class Room(models.Model):
name = models.CharField(max_length=255, blank=True, null=True)
service = models.ForeignKey(Service, models.DO_NOTHING, blank=True,
null=True)
class Meta:
managed = True
db_table = 'Room'
Serializer
class ServiceSerializer(serializers.ModelSerializer):
room_set = RoomSerializer(many=True, read_only=True)
class Meta:
model = Service
fields = ('name','room_set')
class RoomSerializer(serializers.ModelSerializer):
class Meta:
model = Room
fields = '__all__'
View
queryset = Service.objects.filter(room__id=1)
serializer = ServiceSerializer(queryset, many=True)
return JsonResponse(serializer.data, safe=False)
I expect a Json like this:
{
"name": "Hotel1",
"room_set": [
{
"id": 1,
"name": "Room1"
},
But I get this:
{
"name": "Hotel1",
"room_set": [
{
"id": 1,
"name": "Room1",
},
{
"id": 2,
"name": "Room2",
},
{
"id": 3,
"name": "Room3",
}
}
Is it possible to get a json like the one I'm expecting?
You can patch the set by adding a custom Prefetch object [Django-doc] with a filtered queryset, like:
from django.db.models import Prefetch
queryset = Service.objects.filter(
room__id=1
).prefetch_related(
Prefetch('room_set', queryset=Room.objects.filter(id=1), to_attr='room_set1')
)
serializer = ServiceSerializer(queryset, many=True)
return JsonResponse(serializer.data, safe=False)
and let the Serializer parse the new related manager:
class ServiceSerializer(serializers.ModelSerializer):
room_set = RoomSerializer(many=True, read_only=True, source='room_set1')
class Meta:
model = Service
fields = ('name','room_set1')
class RoomSerializer(serializers.ModelSerializer):
class Meta:
model = Room
fields = '__all__'
You can pass the room id via the serializer context and filter accordingly inside a SerializerMethodField()
class ServiceSerializer(serializers.ModelSerializer):
rooms = serializers.SerializerMethodField()
class Meta:
model = Service
fields = ('name','rooms')
get_rooms(self,service):
room_id = self.get_context('room')
if room_id:
queryset = service.rooms_set.filter(id=room_id)
return RoomSerializer(queryset,many=True).data
return RoomSerializer(service.rooms_set.all(),many=True).data
serializer = ServiceSerializer(queryset, many=True,context={'room':1})
return JsonResponse(serializer.data, safe=False)
This's how to do it via the serializer and it's highly customizable , Willem Van Onsem's answer is brief enough , but it also requires two queries the same as of mine.

How to include foreign key field when serialising search query result from django haystack

After googling a lot, I cant seem to find a solution to this:
I have created a new endpoint for searching items from my Product Index
Using Django oscar with django oscar api, here are the models:
class Product(AbstractProduct):
title = models.TextField(blank = True)
class ProductImage(AbstractProductImage):
product = models.ForeignKey('catalogue.Product', on_delete=models.CASCADE)
display_order = models.PositiveIntegerField()
remote_image = models.URLField(blank=True)
The views.py
class ItemSearchViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
serializer_class = ItemSearchSerializer
def get_queryset(self):
request = self.request
queryset = EmptySearchQuerySet()
if request.GET.get('q', ''):
query = request.GET.get('q', '')
queryset = SearchQuerySet().filter(content=query)
return queryset
Serializers.py
from oscarapi.serializers import product
class ItemSearchSerializer(serializers.ModelSerializer):
text = serializers.CharField()
title = serializers.CharField()
price = serializers.HyperlinkedIdentityField(view_name='product-price')
image = product.ProductImageSerializer(many=True, required=False)
class Meta():
model = Product
fields = ('image', 'price', 'text', 'title')
When I hit the endpoint with localhost:8000/api/search/?q=bmw, it seems works for all other fields but does not return images, here is sample:
[
{
"image": null,
"price": "http://localhost:8000/api/products/2339/price/",
"text": "2339\nBMW 316 SE Automatic",
"title": "BMW 316 SE Automatic"
},
{
"image": null,
"price": "http://localhost:8000/api/products/0029/price/",
"text": "0029\nBmw 330d\nLoverly car in excellent condition",
"title": "Bmw 330d"
}]
Another thing, as there maybe several images for a given product, I would also like to just return the first image, which will have a display_order value of 1.

Adding foreign key data to serializer?

I'm trying to use the Django Rest-Framework to produce some JSON that shows all the user's posts, but also shows the images for that post. Image is a foreign key to Post. Here are the models:
models.py
class Post(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
status = models.CharField(max_length=200)
class Image(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE)
img = models.CharField(max_length=120)
views_count = models.IntegerField(default=0)
views.py
class GetPosts(ListAPIView):
serializer_class = PostSerializer
def get_queryset(self):
requested_user = get_requested_user(self)
return Post.objects.filter(user=requested_user).order_by('-created_at')
def get_requested_user(self):
filter_kwargs = {'username': self.kwargs['username']}
return get_object_or_404(User.objects.all(), **filter_kwargs)
serializers.py
class PostSerializer(serializers.ModelSerializer):
image_img = serializers.RelatedField(source='Image', read_only=True)
class Meta:
model = Post
fields = ('status', 'image_img ')
In the serializers.py, I'd like to show all of the fields for Image (img, views_count) What I get with my current code is this:
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"status": "I am number 1"
}
]
}
Which contains the user's posts, but not the user's posts and each post's images. Note: Query url looks like this: /api/posts/user/
You should use Nested serializer here,
class ImageSerializer(serializers.ModelSerializer):
class Meta:
model = Image
fields = ('img',)
class PostSerializer(serializers.ModelSerializer):
image_img = ImageSerializer(source='image_set', many=True)
class Meta:
model = Post
fields = '__all__'
Hence the response will be like,
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"status": "I am number 1",
"image_img": [
{"img": "image_url"},
{"img": "image_url"},
....
]
}
]
}
How to display all field of model class in serializer?
From the doc,
You can also set the fields attribute to the special value '__all__' to indicate that all fields in the model should be used.
Reference
1. DRF- Nested Realtions
2. source argument
3. Specifying which fields to include

Categories

Resources