django rest framework: include related model fields in same path - django

class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = ('id','product_id','sku', 'title','price','images')
class WishListSerializer(serializers.ModelSerializer):
product = ProductSerializer()
class Meta:
model = WishList
fields = ('wishlist_id','product',)
I have two serializers. Wishlist and Product. I want to list all wishlist products. It works fine now. But the product details is in "product" key element. Can I remove that product key, and show the product details along with the wishlist_id ?
Present result:
{
"count": 2,
"next": null,
"previous": null,
"results": [
{
"wishlist_id":1,
"product": {
"id": 1460,
"product_id": "04396134-3c90-ea7b-24ba-1fb0db11dbe5",
"sku": "bb4sd817",
"title": "Trinity Belt",
}
},
{
"wishlist_id":2,
"product": {
"id": 596,
"product_id": "52588d22-a62c-779b-8044-0f8d9892e853",
"sku": "ml346",
"title": "Martina Liana",
}
}
]
}
Expected result:
{
"count": 2,
"next": null,
"previous": null,
"results": [
{
"wishlist_id":1,
"id": 1460,
"product_id": "04396134-3c90-ea7b-24ba-1fb0db11dbe5",
"sku": "bb4sd817",
"title": "Trinity Belt",
},
{
"wishlist_id":2,
"id": 596,
"product_id": "52588d22-a62c-779b-8044-0f8d9892e853",
"sku": "ml346",
"title": "Martina Liana",
}
]
}

This is a very bad practice and you need a lot of effort to implement serialization and deserialization, especially in case of Post, Update etc.
I can think of 2 ways.
1) You could use in the WishListSerializer the missing fields as SerializerMethodField
example
product_id = serializers.SerializerMethodField()
def get_product_id(self, obj):
return obj.get_product().product_id
2)
class WishListSerializer(serializers.HyperlinkedModelSerializer):
product_id = serializers.CharField(source='product.product_id')
.......
class Meta:
model = WishList
fields = (product_id, ......)

You could "unnest" items in to_representation
docs on it https://www.django-rest-framework.org/api-guide/fields/#custom-fields
...
class WishListSerializer(serializers.ModelSerializer):
product = ProductSerializer()
class Meta:
model = WishList
fields = ('wishlist_id', 'product',)
# add
def to_representation(self, instance):
data = super().to_representation(instance)
flat_data = dict(wishlist_id=data['wishlist_id'], **data['product']) # construct (new dict) flat data
# or you could restructure same dict: data.update(data.pop('product'))
return flat_data

Related

Defining a custom Serializer Field for related models gives TypeError

My question is similar to this unanswered question.
I'm trying to implement a serializer field to my project. I'm hitting a roadblock where I need to nest other models in a single request, but it throws a TypeError: Object of type ____ is not serializable.
Here's what my models.py code's pseudocode looks like:
(GameIndexPage has many GameBlogPage has one Game)
(I omitted things I think is irrelevant)
#register_snippet
class Game(models.Model):
title = models.CharField(max_length=150)
def __str__(self):
return self.title
class GameBlogPage(Page):
parent_page_type = ['blog.GameIndexPage']
introduction = models.TextField
game = models.ForeignKey 'web_api.Game'
album_image = models.ForeignKey 'wagtailimages.Image'
body = StreamField([
('heading', blocks.CharBlock(form_classname="full title")),
('paragraph', blocks.RichTextBlock()),
('image', ImageChooserBlock()),
])
published_date = models.DateTimeField()
class GameIndexPage(Page):
subpage_types = ['GameBlogPage']
parent_page_type = ['home.HomePage']
introduction = RichTextField
image = models.ForeignKey 'wagtailimages.Image'
#property
def children(self):
return self.get_children().specific().public().live()
...
api_fields = [
APIField('introduction'),
APIField('image'),
APIField('children', serializer=GameBlogPageField())
]
Now, here's my implementation of the serializer field in fields.py:
class GameBlogPageField(Field):
def to_representation(self, queryset):
pages = []
for child in queryset:
post_dict = {
"id": child.id,
"title": child.title,
"introduction": child.introduction,
"game": child.game # where it all goes wrong
#"blog_authors": child.blog_authors,
#"published_date": child.published_date,
#"album_image": child.album_image,
}
pages.append(post_dict)
return pages
Removing the game value gives me this result. I commented where I want the nested data to go.
"items": [
{
"id": 6,
"meta": {
"type": "blog.GameIndexPage",
...
},
"title": "My Favorite Games of All Time",
"introduction": "...",
"image": {
"url": "/media/original_images/_hkdd3HTVIc.jpg",
"title": "FavGameBanner",
"width": 1920,
"height": 1080
},
"children": [
{
"id": 7,
"title": "...",
"introduction": "...",
"game": [] <--- I don't know of a clean way to serialize the Game object here.
},
{
"id": 9,
"title": "...",
"introduction": "...",
"game": [] <--- I don't know of a clean way to serialize the Game object here.
}
]
}
]
My dirty solution is to manually create an object in fields.py and append values in them:
class GameBlogPageField(Field):
def to_representation(self, queryset):
...
post_dict = {
"id": child.id,
"title": child.title,
"introduction": child.introduction,
"game": {
"id": child.game.id,
"title": child.game.title
}
#"blog_authors": child].blog_authors,
#"published_date": child.published_date,
#"album_image": child.album_image,
}
pages.append(post_dict)
return pages
But, I can't see this working for one-to-many and many-to-many relations. Unfortunately, the only thing that I researched is the unanswered question I linked above.
What should I do?

Django Rest Framework: Modify Serializer to return dictionary using a field as the key instead of an array of objects

Currently I have an API View that returns a dictionary that looks like this:
[
{
"id": 1,
"name": "Fly in the sky",
"thumbnail": "games/fly_in_the_sky",
"maps": [
{
"id": 1,
"name": "Blue Sea",
"thumbnail": "maps/blue_sea.jpg"
},
{
"id": 2,
"name": "Red Mountain",
"thumbnail": "maps/red_mountain.jpg"
}
],
"characters": [
{
"id": 1,
"name": "Steve",
"thumbnail": "characters/steve.jpg"
},
{
"id": 2,
"name": "Peter",
"thumbnail": "characters/peter.jpg"
}
]
}
]
I would like it to look like this instead, moving the names of all items as keys. (I would make sure ahead of time that all the names are unique so there is no key conflict)
{
"Fly in the sky": {
"id": 1,
"thumbnail": "games/fly_in_the_sky",
"maps": {
"Blue Sea": {
"id": 1,
"thumbnail": "maps/blue_sea.jpg"
},
"Red Mountain": {
"id": 2,
"thumbnail": "maps/red_mountain.jpg"
}
},
"characters": {
"Steve": {
"id": 1,
"thumbnail": "characters/steve.jpg"
},
"Peter",{
"id": 2,
"thumbnail": "characters/peter.jpg"
}
}
}
}
Is there any way of telling Django to return elements as a dictionary using a field as keys instead of an array?
My views and serializer currently look like this:
class GamesView(APIView):
def get(self, request):
game_objects = Game.objects.all()
game_serializer = GameListSerializer(game_objects, many=True)
return Response(game_serializer.data)
class MapListSerializer(ModelSerializer):
class Meta:
model = Map
fields = ('id', 'name', 'thumbnail')
class CharacterListSerializer(ModelSerializer):
class Meta:
model = Character
fields = ('id', 'name', 'thumbnail')
class GameListSerializer(ModelSerializer):
maps = MapListSerializer(many=True)
characters = CharacterListSerializer(many=True)
class Meta:
model = Game
fields = ('id', 'name', 'thumbnail', 'maps', 'characters')
When you give the parameter many=True to serializer, it returns a list and you cannot change it. This is better because more than one object can return. I wouldn't suggest it, but if you want to return the dictionary when there is only one element, you can write a condition like below. The reason I don't recommend it that it can be difficult to parse JSON for the client.
class GamesView(APIView):
def get(self, request):
game_objects = Game.objects.all()
game_serializer = GameListSerializer(game_objects, many=True)
serializer_data = game_serializer.data
if len(serializer_data) == 1:
serializer_data = serializer_data[0]
return Response(serializer_data)
Maybe this should help.
But i'm not sure why you would do that. xD
class GamesView(APIView):
def get(self, request):
game_objects = Game.objects.all()
game_serializer = GameListSerializer(game_objects, many=True)
modified_response = {}
if game_serializer.data:
for game in game_serializer:
name = game.pop("name")
modified_response[name] = game
return Response(modified_response)

How can i display detailed data from many to many field instead of objects in django rest API , currently im getting only objects of it

I'm new to Django,i tryed to list all invoices from my model Invoices and im getting items as only its object, to make the code efficient i need to get the whole data of items in a single query how can i get the details of items along with the data instead of item objects
Here is what i have tryed
models.py
class ItemsInvoice(models.Model):
invoiceId = models.CharField(max_length=20)
product_Id = models.CharField(max_length=20)
item_price = models.CharField(max_length=20)
class Invoices(models.Model):
customer = models.CharField(max_length=10,null=True)
total_amount = models.CharField(max_length=12,null=True)
items = models.ManyToManyField(ItemsInvoice,related_name="item_invoice")
views.py
class InvoiceView(ListAPIView):
serializer_class = InvoiceSerializers
def get_queryset(self):
# queryset = Invoices.objects.prefetch_related('items').all().order_by('-id')
queryset = Invoices.objects.all().order_by('-id')
return queryset
serializers.py
class InvoiceSerializers(serializers.ModelSerializer):
class Meta:
model = Invoices
fields = '__all__'
if i run it on my postman, its response will be like this
API response what i have
[
{
"id": 69,
"customer": "4",
"total_amount": "25000",
"items": [
66,
67,
68
]
}
]
but actually i want my output like this , ie the item field should list all data inside in it
API response what i want
[
{
"id": 69,
"customer": "4",
"total_amount": "25000",
"items": [
{
"id": 66,
"invoiceId": "69",
"product_Id": "3",
"item_price": "300",
},
{
"id": 67,
"invoiceId": "69",
"product_Id": "4",
"item_price": "200",
},
{
"id": 68,
"invoiceId": "69",
"product_Id": "4",
"item_price": "200",
}
]
}
]
how can i achieve it by using django-orm or raw query
You must define ItemInvoiceSerializer and set many=true.
class ItemInvoiceSerializer(serializers.ModelSerializer):
class Meta:
model = ItemsInvoice
fields = '__all__'
class InvoiceSerializers(serializers.ModelSerializer):
items = ItemInvoiceSerializer(many=True) # i changed true to True
class Meta:
model = Invoices
fields = '__all__'

How to return only the relevant related objects in Django REST Framework

I am trying to create an API such that when I give it a keyword it will return all the cinemas that have movies containing said keyword.
Right now I am using:
queryset = Cinema.objects.filter(movies__title__icontains = keyword)
My serializer is:
class CinemaSerializer(serializers.ModelSerializer):
class Meta:
model = Cinema
fields = ('id', 'name', 'city', 'movies')
depth = 1
If the keyword is "ghost" this returns the cinemas with all their movies.
{
"id": 1,
"name": "My Cinema",
"city": "London",
"movies": [
{
"id": 1,
"title": Ghosts
},
{
"id": 2,
"title": Ghostbusters
},
{
"id": 3,
"title": Star Wars
}
]
Is there a way to return the cinemas and only the relevant movies (Ghosts and Ghostbusters)?
You can work with a Prefetch object here:
from django.db.models import Prefetch
queryset = Cinema.objects.filter(movies__title__icontains=keyword).prefetch_related(
Prefetch(
'movies',
queryset=Movie.objects.filter(title__icontains=keyword),
to_attr='matching_movies'
)
)
In the serializer, we then serialize the matching_movies:
class CinemaSerializer(serializers.ModelSerializer):
movies = MovieSerializer(many=True, source='matching_movies')
class Meta:
model = Cinema
fields = ('id', 'name', 'city', 'movies')
Where you implement a MovieSerializer to serialize the matching movies.

Change the order of fields in normal serializer response

Using Django REST framework I want to change the serializer response fields order dynamically by a list of given field names.
Output from DRF is the following:
{
"items": [
{
"name": "John",
"age": 25,
"id": 3
},
{
"name": "Sam",
"age": 20,
"id": 8
}
]
}
My filed order list is:
order_list = ['id', 'age', 'name']
What I want is:
{
"items": [
{
"id": 3,
"age": 25,
"name": "John"
},
{
"id": 8,
"age": 20,
"name": "Sam"
}
]
}
Serializer code:
class ItemSerializer(serializers.Serializer):
name = serializers.CharField()
id = serializers.IntegerField()
age = serializers.IntegerField()
To change the ordering, you will need to change the field definition in the serializer meta class.
class ItemSerializer(serializers.Serializer):
name = serializers.CharField()
id = serializers.IntegerField()
age = serializers.IntegerField()
class Meta(object):
fields = ( # this needs to be ordered properly
'id',
'age',
'name',
)
You can loop over the serializer data and order the fields using OrderedDict.
The data in the serializer is already using OrderedDict, but we can use it to further order accordingly.
from collections import OrderedDict
data = serializer.data
ordered_data = []
for item in data:
item_dict = OrderedDict()
item_dict['id'] = item['id']
item_dict['age'] = item['age']
item_dict['name'] = item['name']
ordered_data.append(item_dict)