how to write DRF serializers for the desired output? - django

need to get the following output using the below given model structure
{
"User": [
{
"id": 1,
"name": "XYZ",
"Task": [
{
"id": "1",
"task_name": "task 1"
},
{
"id": "2",
"task_name": "task 2"
}
]
},
{
"id": 2,
"name": "ABC",
"Task": [
{
"id": "1",
"task_name": "task 1"
}
]
}
]
}
This is how the model is designed and I want the above output without dealing with extra database query for each record (n+1). Can this be achieved using select_related or prefetch related or something else?
class User(models.Model):
name = models.Charfield()
class Task(models.Model)
task_name = models.Charfield()
class UserTask(models.Model):
user = models.Foreignkey(User, related_name = 'user')
task = models.Foreignkey(Task, related_name = 'tasks')

You can proceed this way
class TaskSerializer(ModelSerializer):
class Meta:
model=Task
fields = '__all__'
class UserTaskSerializer(ModelSerialzier):
id = serializer.CharField(source ='user.id')
name = serializer.CharField(source ='user.username') # might be username,fullname or name only change in your own way
task = TaskSerializer(many=true)
class Meta:
model=UserTask
fields = ('id','name','task')
Just make sure you are using many=True in your view for UserSerializer class

Related

Django - Retrieve nested fields multiple levels deep using foreign keys

I'm struggling to write a Django GET that returns the following looking response:
{
"lists": [
{
"id": "123",
"list_order": [
{
"id": "123_1",
"order": 1,
"list_id": "123",
"item_id": 9876,
"item": {
"id": 9876,
"name": "item1",
"location": "California"
}
},
{
"id": "123_2",
"order": 2,
"list_id": "123",
"item_id": 2484,
"item": {
"id": 2484,
"name": "item2",
"location": "California"
}
}
],
"updated_date": "2018-03-15T00:00:00Z"
}
]
}
Given a list_id, the response returns the basic information on the list ("id", "updated_date"), as well as the order of items in the list. Inside each item in the list order, it also grabs the related item details (nested in "item"). I'm able to get this response without the "item" details ("id", "name", "location" fields) and with no error:
{
"lists": [
{
"id": "123",
"list_order": [
{
"id": "123_1",
"order": 1,
"list_id": "123",
"item_id": 9876
},
{
"id": "123_2",
"order": 2,
"list_id": "123",
"item_id": 2484
}
],
"updated_date": "2018-03-15T00:00:00Z"
}
]
}
Again there is no error, and I can retrieve the first nested level without any issue. The problem is getting the "item" information to show within each "list_order". Below are my models, serializers, and views.
models.py
class Lists(models.Model):
id = models.CharField(null=False, primary_key=True, max_length=900)
updated_date = models.DateTimeField(blank=True, null=True)
class Meta:
managed = False
db_table = 'tbl_lists'
class Items(models.Model):
id = models.BigIntegerField(primary_key=True)
name = models.TextField(blank=True, null=True)
location = models.TextField(blank=True, null=True)
class Meta:
managed = False
db_table = 'tbl_items'
class ListOrder(models.Model):
id = models.CharField(null=False, primary_key=True, max_length=900)
list_id = models.ForeignKey(Lists, db_column='list_id', related_name='list_order')
item_id = models.ForeignKey(Items, db_column='item_id', related_name='items')
order = models.BigIntegerField(blank=True, null=True)
class Meta:
managed = False
db_table = 'tbl_list_order'
serializers.py
class ItemsSerializer(serializers.ModelSerializer):
class Meta:
model = Items
fields = '__all__'
class ListOrderSerializer(serializers.ModelSerializer):
item = ItemsSerializer(many=False, read_only=True)
class Meta:
model = ListOrder
fields = '__all__'
class ListsSerializer(serializers.ModelSerializer):
list_order = ListOrderSerializer(many=True, read_only=True)
class Meta:
model = Lists
fields = '__all__'
views.py
class ListsViewSet(generics.ListCreateAPIView):
"""
API endpoint that returns a list with its meta-information
"""
queryset = Lists.objects.all()
serializer_class = ListsSerializer
def get_queryset(self):
list_id = self.kwargs['list_id']
filters = [Q(id=list_id)]
return Lists.objects.filter(*filters)
def list(self, request, list_id):
queryset = self.get_queryset()
list_serializer = ListsSerializer(queryset, many=True)
return Response({ 'lists': list_serializer.data })
I'm pretty new to Django and like what it offers so far, though maybe I'm thinking of doing this in too much of a "SQL" way. I've read about select_related() and prefetch_related(), but not sure how I would apply it to this case. Any assistance is greatly appreciated and let me know if there's any other information I can provide.
In your ListOrderSerializer you are trying to serialize item. while in ListOrder model you used the field name item_id
Solution:
In ListOrderSerializer:
class ListOrderSerializer(serializers.ModelSerializer):
item_id = ItemsSerializer(many=False, read_only=True)
...

DRF many-to-one reverse lookup

I have an API endpoint returning pets and their owners.
Each owner has a name and one or more pets
Each pet has a name and one owner
Example Django models:
class Owner(models.Model):
name = models.CharField(max_length=200)
class Pet(models.Model):
owner = models.ForeignKey(Owner, on_delete=models.CASCADE)
name = models.CharField(max_length=200)
I've configured my API to return JSON data like this:
[
{
"id": 2,
"name": "Scotch",
"owner": {
"id": 2,
"name": "Ben"
}
},
{
"id": 3,
"name": "Fluffy",
"owner": {
"id": 1,
"name": "Fred"
}
},
{
"id": 1,
"name": "Spot",
"owner": {
"id": 1,
"name": "Fred"
}
}
]
Example DRF serializers:
class OwnerSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Owner
fields = ("id", "name")
class PetSerializer(serializers.HyperlinkedModelSerializer):
owner = OwnerSerializer()
class Meta:
model = Pet
fields = ("id", "name", "owner")
While that's all fine and dandy, I'd actually like to have an endpoint that returns a list of owners and their pets. So I'd get this data instead:
[
{
"id": 1,
"name": "Fred",
"pets": [
{ "id": 1, "name": "Spot" },
{ "id": 3, "name": "Fluffy" }
]
},
{
"id": 2,
"name": "Ben",
"pets": [
{ "id": 2, "name": "Scotch" }
]
}
]
How can I achieve that output?
You need to add pet_set field to OwnerSerializer like this:
class PetSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Pet
fields = ("id", "name")
class OwnerSerializer(serializers.HyperlinkedModelSerializer):
pet_set = PetSerializer(many=True, read_only=True)
class Meta:
model = Owner
fields = ("id", "name", "pet_set")
This will work bacause many-to-one relation default reverse lookup name is <model>_set or pet_set in your case. You can change it by using related_name:
class Pet(models.Model):
owner = models.ForeignKey(Owner, related_name='pets', on_delete=models.CASCADE)
In this case you can use pets name inside serializer:
class OwnerSerializer(serializers.HyperlinkedModelSerializer):
pets = PetSerializer(many=True, read_only=True)
Now in OwnerListView you can use this new serializer:
class OwnerListView(ListAPIView):
queryset = Owner.objects.all()
serializer_class = OwnerSerializer
Change/ add your serializer.py as following
class PetSerializer(serializers.ModelSerializer):
class Meta:
model = Pet
fields = ("id", "name")
class OwnerNewSerializer(serializers.ModelSerializer):
pets = PetSerializer(many=True, source='pet_set')
class Meta:
model = Owner
fields = ('id', 'name', 'pets')
and views.py
class OwnerAPI(viewsets.ModelViewSet):
queryset = Owner.objects.all()
serializer_class = OwnerNewSerialize

django rest framework: customize nested serializer

I have the following Django model structure:
class TypeOfIngredient(models.Model):
name = models.CharField(max_length=200,unique=True,null=False)
slug = models.SlugField(unique=True)
class Ingredient(models.Model):
name = models.CharField(max_length=200,unique=True,null=False)
slug = models.SlugField(unique=True)
typeofingredient = models.ForeignKey(TypeOfIngredient, related_name='typeof_ingredient',null=True, blank=True,on_delete=models.PROTECT)
Serializer:
class IngredientListSerializer(ModelSerializer):
class Meta:
model = Ingredient
fields = '__all__'
With the above serializer i see the following api output:
"results": [
{
"id": 1,
"name": "adrak",
"slug": "adrak",
"typeofingredient": null
},
{
"id": 2,
"name": "banana",
"slug": "banana",
"typeofingredient": 1
},
How to get "typeofingredient": "fruit" where fruit is the name field of the typeofingredient. What i am getting is the id.
I tried nested:
class IngredientListSerializer(ModelSerializer):
class Meta:
model = Ingredient
fields = '__all__'
depth = 1
Then i get the api output as:
"results": [
{
"id": 1,
"name": "adrak",
"slug": "adrak",
"typeofingredient": null
},
{
"id": 2,
"name": "banana",
"slug": "banana",
"typeofingredient": {
"id": 1,
"name": "fruit",
"slug": "fruit"
}
},
Here is showing all the details of the typeofingredient. Rather than this can i have directly "typeofingredient": "fruit"
Use serializers.ReadOnlyField
class IngredientListSerializer(ModelSerializer):
typeofingredient = serializers.ReadOnlyField(source='typeofingredient.name')
class Meta:
model = Ingredient
fields = '__all__'
You can add str method on models.py
class TypeOfIngredient(models.Model):
name = models.CharField(max_length=200,unique=True,null=False)
slug = models.SlugField(unique=True)
def __str__(self):
return str(self.name)
class Ingredient(models.Model):
name = models.CharField(max_length=200,unique=True,null=False)
slug = models.SlugField(unique=True)
typeofingredient = models.ForeignKey(TypeOfIngredient, related_name='typeof_ingredient',null=True, blank=True,on_delete=models.PROTECT)

How can i Get foreign key's value instead of id , in django rest framework

Sorry for my bad English hope u can understand what i mean.
OUT PUT IM GETTING NOW:
[
{
"MainCatName": 1,
"Name": "harry potter",
"Image": "/media/101029496--sites-default-files-images-101029496-3176173-1748009911-hp.jp-1_MoxqrLp.jpg"
},
{
"MainCatName": 2,
"Name": "Princes Girl",
"Image": "/media/character_princess_rapunzel_8320d57a.jpeg"
},
{
"MainCatName": 3,
"Name": "sex in the city",
"Image": "/media/250px-SATC_Title.jpg"
},
{
"MainCatName": 4,
"Name": "who is dragon",
"Image": "/media/Reggio_calabria_museo_nazionale_mosaico_da_kaulon.jpg"
},
{
"MainCatName": 2,
"Name": "drama queen",
"Image": "/media/15241421_170761763390015_7913498865987146084_n.jpg"
}
]
WHAT I WANT :
I WANT TO TO RETURN THE FORIGN KEY'S (MainCatName) VALUE WHICH IS IN TABLE INSTEAD OF ID. ie value in the [CategoryName = models.CharField(max_length=50)] in my models.py
LIKE
[
{
"MainCatName": Story Books,
"Name": "harry potter",
"Image": "/media/101029496--sites-default-files-images-101029496-3176173-1748009911-hp.jp-1_MoxqrLp.jpg"
},
{
"MainCatName": Darama,
"Name": "Princes Girl",
"Image": "/media/character_princess_rapunzel_8320d57a.jpeg"
},
{
"MainCatName": Roamance,
"Name": "sex in the city",
"Image": "/media/250px-SATC_Title.jpg"
},
{
"MainCatName": sex,
"Name": "who is dragon",
"Image": "/media/Reggio_calabria_museo_nazionale_mosaico_da_kaulon.jpg"
},
{
"MainCatName": darama,
"Name": "drama queen",
"Image": "/media/15241421_170761763390015_7913498865987146084_n.jpg"
}
]
Here is my code :
veiws.py :
class GETCATVeiw(APIView):
def get(self, request):
data = Products.objects.only('MainCatName','Name','Image')
serializer = GETCAT(data, many=True)
return Response(serializer.data)
def post(self):
pass
models.py :
class Category(models.Model):
CategoryName = models.CharField(max_length=50)
class Meta:
verbose_name_plural = 'Categories'
def __str__(self):
return self.CategoryName
class Products(models.Model):
MainCatName = models.ForeignKey(Category, on_delete=models.CASCADE)
Name = models.CharField(max_length=50)
Image = models.ImageField()
Price = models.IntegerField()
DiscriptionHeading = models.CharField(max_length=100)
DiscriptionParagraph = models.TextField(max_length=1000)
class Meta:
verbose_name_plural = 'Products'
def __str__(self):
return str(self.MainCatName)+' - '+self.Name+' - '+str(self.Price)+' $'
serializers.py :
class GETCAT(serializers.ModelSerializer):
class Meta:
model = Products
fields = ('MainCatName', 'Name', 'Image')
You have the SlugRelatedField for that:
class GETCAT(serializers.ModelSerializer):
MainCatName = serializers.SlugRelatedField(read_only=True, slug_field='CategoryName')
class Meta:
model = Products
fields = ('MainCatName', 'Name', 'Image')
Edit:
I'm not sure it'll work with Products.objects.only('MainCatName','Name','Image') as you'll span a database relation. Also you'll likely want to use a select_related to avoid getting N+1 DB query.
The generic Django way is to define natural keys on your model, in this case Category:
Serializing by natural key: Add a natural_key(self) method to your Category class in which you return the category's CategoryName. It has to be unique!
def natural_key(self):
return self.CategoryName
Deserializing by natural key: You want to define a default manager for your Category model:
objects = CategoryManager()
and define the get_by_natural_key(self, name) method in your CategoryManager(models.Manager) class, which returns the category:
class CategoryManager(models.Manager):
def get_by_natural_key(self, name):
return self.get(CategoryName=name)

Django Rest Framework Serializer format

I have two serializers: one for the Restaurant model, another for the MainMenu model:
class RestaurantSerializer(serializers.ModelSerializer):
class Meta:
model = Restaurant
class MainMenuSerializer(serializers.ModelSerializer):
restaurant = RestaurantSerializer()
main_menu_items = serializers.StringRelatedField(many=True)
class Meta:
model = MenuMain
fields = ('id', 'restaurant', 'main_menu_items')
The current output of the MainMenuSerializer is
[
{
"id": 1,
"restaurant": {
"id": 1,
"name": "Restaurant A",
"location": "Street B"
},
"main_menu_items": [
"Fried Rice"
]
},
{
"id": 2,
"restaurant": {
"id": 1,
"name": "Restaurant A",
"location": "Street B",
},
"main_menu_items": [
"Noodles"
]
}
]
But I want the RestaurantSerializer to only output once, something like this:
[
{
"restaurant": {
"id": 1,
"name": "Restaurant A",
"location": "Street B"
}
},
[
{
"id": 1,
"main_menu_items": [
"Fried Rice",
"Meat Balls"
]
},
{
"id": 2,
"main_menu_items": [
"Noodles"
]
}
]
]
EDIT:
models used
class Restaurant(models.Model):
name = models.CharField(max_length=100, default='')
location = models.CharField(max_length=100, default='')
class MenuMain(models.Model):
price = models.IntegerField()
restaurant = models.ForeignKey(Restaurant, related_name='main_menus')
class MenuMainItems(models.Model):
menu_main = models.ForeignKey(MenuMain, related_name='main_menu_items')
item = models.CharField(max_length=150, default='')
The more "Django REST" way of doing this is to set up a url node for each restaurant that returns all the menu items in the restaurant. For instance,
urls.py
url(r'restaurant-menu/(?P<pk>[0-9]+)$', MenuItemViewset.as_view())
In your viewset class
class MenuItemViewset(viewsets.ModelViewSet):
serializer_class = MainMenuSerializer
def retrieve(self, request, pk=None):
return Restaurant.objects.get(pk=pk).menumain_set.all()[0].menumainitems_set.all()
This of course assumes one menu per restaurant. If there are multiple menus and you want to get all of them, it's probably best practice to break it up into two calls: one for the restaurant's menu, and to get a particular menu's items. Otherwise too many assumptions are being made about the organization of the data, and that's a rather fragile design pattern.
Link to docs on fetching related objects
If you still are intent on getting a response like you originally asked for, you just need to reverse the serializer nesting, i.e. add a menu field to the RestaurantSerializer.
Here is the very simplest approach I came with. I know this can be improved.
Please comment if you have any query or improvement suggestion.
class RestaurantSerializer(serializers.ModelSerializer):
menu = serializers.SerializerMethodField()
def get_menu(self, obj):
dict_l = {}
res = MainMenu.objects.all() # filter as per your requirement
'''
flds = MainMenu._meta.local_fields
for ins in res:
for f in flds:
print f.name
'''
for ins in res:
dict_l['id'] = ins.id
dict_l['name'] = ins.name
dict_l['restaurant'] = ins.restaurant_id
# return {1: 1, 2: 2}
return dict_l
class Meta:
model = Restaurant
fields = ('id', 'name', 'menu',)