Reverse foreign key with cross reference table - Django Rest Framework - django

I'm trying to create an API endpoint which will provide summary data about a user and the groups they are in.
My current models have User and UserGroup connected with a Membership Model.
My current problem is I can't seem to get the list of group members to work.
Am I on the right track here? Or is there a better way to handle serialization/querying of models with many to many relationships?
Models:
class UserGroup(models.Model):
group_id = models.AutoField(primary_key=True)
name = models.CharField(max_length=30)
description = models.CharField(max_length=200)
deleted = models.BooleanField(default=False)
class Membership(models.Model):
group = models.ForeignKey(UserGroup, on_delete=models.CASCADE, related_name="members")
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="user_groups")
isAdmin = models.BooleanField(default=False)
Serializers:
class MemberSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('first_name', 'last_name')
class MembershipSerializer(serializers.HyperlinkedModelSerializer):
name = serializers.ReadOnlyField(source='group.name')
description = serializers.ReadOnlyField(source='group.description')
members = MemberSerializer(source='group.members', read_only=True, many=True)
class Meta:
model = Membership
fields = ('name', 'description', 'members')
class UserSerializer(serializers.ModelSerializer):
groups = MembershipSerializer(source='user_groups', read_only=True, many=True)
class Meta:
model = User
fields = ('username', 'groups')
Current Output:
{
"username": "User 1",
"groups": [
{
"name": "Group 1",
"description": "Test Group",
"members": [
{}
]
},
{
"name": "Group 2",
"description": "Test Group 2",
"members": [
{}
]
}
]
}
Expected:
'members' contains a list of users who are in the UserGroup.

I think your code is too complex.
Models:
from django.contrib.auth import models as dj_models
class Group(models.Model):
group_id = models.AutoField(primary_key=True)
name = models.CharField(max_length=30)
description = models.CharField(max_length=200)
deleted = models.BooleanField(default=False)
members = models.ManyToManyField(dj_models.User, through='Membership', related_name='user_groups')
class Membership(models.Model):
group = models.ForeignKey(Group, on_delete=models.CASCADE)
user = models.ForeignKey(dj_models.User, on_delete=models.CASCADE)
isAdmin = models.BooleanField(default=False)
Serializers:
from django.contrib.auth import models as dj_models
class MemberSerializer(serializers.ModelSerializer):
class Meta:
model = dj_models.User
fields = ('username',)
class GroupSerializer(serializers.ModelSerializer):
members = MemberSerializer(many=True, read_only=True)
class Meta:
model = models.Group
fields = ('name', 'description', 'members')
class UserSerializer(serializers.ModelSerializer):
groups = GroupSerializer(many=True, read_only=True, source='user_groups')
class Meta:
model = dj_models.User
fields = ('username', 'groups')
Output:
[
{
"username":"Account 01",
"groups":[
{
"name":"Group 01",
"description":"Test Group",
"members":[
{
"username":"Account 01"
}
]
},
{
"name":"Group 02",
"description":"Test Group 2",
"members":[
{
"username":"Account 01"
}
]
}
]
}
]

Related

DRF serialization - many-to-many field with through

How to serialize data from many-to-many field with through parameter?
I have 3 models:
class Ingredient(models.Model):
name = models.CharField(
max_length=200,
)
measurement_unit = models.CharField(
max_length=200,
)
class Recipe(models.Model):
name = models.CharField(
max_length=200,
)
ingredients = models.ManyToManyField(
Ingredient,
through='RecipeIngredientsDetails',
)
class RecipeIngredientsDetails(models.Model):
recipe = models.ForeignKey(
Recipe,
on_delete=models.CASCADE,
)
ingredient = models.ForeignKey(
Ingredient,
on_delete=models.CASCADE,
)
amount = models.FloatField()
My serializers:
class IngredientSerializer(ModelSerializer):
# amount = ???
class Meta:
model = Ingredient
fields = ["id", "name", "amount", "measurement_unit"]
class RecipeSerializer(ModelSerializer):
class Meta:
model = Recipe
fields = ["name", "ingredients"]
depth = 1
Now, I get:
{
"name": "Nice title",
"ingredients": [
{
"id": 1,
"name": "salt",
"measurement_unit": "g"
},
{
"id": 2,
"name": "sugar",
"measurement_unit": "g"
}
]
}
I need amount-value in every ingredient. How can I implement it?
You need to change IngredientSerializer to use the RecipeIngredientsDetails model, and also explicitly set the related serializer inside the RecipeSerializer:
from rest_framework.serializers import ReadOnlyField
class IngredientDetailSerializer(ModelSerializer):
id = ReadOnlyField(source='ingredient.id')
ingredient = ReadOnlyField(source='ingredient.name')
measurement_unit = ReadOnlyField(source='ingredient.measurement_unit')
class Meta:
model = RecipeIngredientsDetails
fields = ["id", "ingredient", "measurement_unit", "amount",]
class RecipeSerializer(ModelSerializer):
ingredients = IngredientDetailSerializer(source="recipeingredientsdetails_set", many=True)
class Meta:
model = Recipe
fields = ["name", "ingredients"]
depth = 1

Getting serialized grandparent from ManyToMany relation DRF

I'm struggling to serialize the grandparent(s) of a ManyToMany relation in my models. In the product serializer, i want to list the top level Category based on the SubCategory selected on the Product. My code is structured like this:
models.py
class Category(models.Model):
...
name = models.CharField(
_('category name'),
max_length=255,
unique=False
)
...
class SubCategory(models.Model):
parent = models.ForeignKey(
Category,
on_delete=models.CASCADE,
related_name='children',
)
name = models.CharField(
_('category name'),
max_length=255,
unique=False
)
...
class Product(models.Model):
name = models.CharField(
_('product name'),
max_length=255,
unique=True
)
category = models.ManyToManyField(
SubCategory,
related_name='products'
)
...
serializers.py
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ['name']
class ProductsSerializer(serializers.ModelSerializer):
...
category = serializers.StringRelatedField(read_only=True, many=True)
parent_category = CategorySerializer(read_only=True, source='category.parent', many=True)
...
class Meta:
model = Product
fields = (
...
'parent_category',
'category',
...
)
Currently the field parent_category does not show up in the json-response.
Edit:
serializers.py:
class ProductCategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ['name']
class ProductSubCategorySerializer(serializers.ModelSerializer):
class Meta:
model = SubCategory
fields = ['name']
class ProductsSerializer(serializers.ModelSerializer):
...
subcategory = ProductSubCategorySerializer(many=True, source='category')
category = ProductCategorySerializer()
...
class Meta:
model = Product
fields = (
...
'category',
'subcategory',
...
)
Current output:
[
{
"subcategory": [
{
"name": "sub category name",
"category": {
"name": "main category name"
}
},
],
}
]
wished output:
[
{
"category": [
{
"name": "main category name"
},
...
],
"subcategory": [
{
"name": "sub category name",
},
...
],
}
]
The full serializers setup would have to be like this:
class ProductCategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ['name']
class ProductSubCategorySerializer(serializers.ModelSerializer):
category = ProductCategorySerializer(source="parent")
class Meta:
model = SubCategory
fields = ['name', 'category']
class ProductsSerializer(serializers.ModelSerializer):
...
subcategory = ProductSubCategorySerializer(many=True, source='category')
...
class Meta:
model = Product
fields = (
...
'subcategory',
...
)

Python Django Display Nested Objects

Hi I'm new to django rest framework and I'm trying to serialize 3 nested models. The relationships are:
hotel_social_media_type has a one to many relationship to hotel_social_media
and hotel has one to many relationship to hotel_social_media. Right now I can only serialized hotel to hotel_social_media but I can't serialize hotel_social_media_type.
Here's my serializers:
class SocialMediaTypeSerializer(serializers.ModelSerializer):
"""Serializes social media type"""
class Meta:
model = models.SocialMediaType
fields = ('name', 'icon', 'url')
class HotelSocialMediaSerializer(serializers.ModelSerializer):
"""Serializes media files"""
hotel_social_media_type = SocialMediaTypeSerializer(many=False, read_only=True)
class Meta:
model = models.HotelSocialMedia
fields = ('url', 'hotel_social_media_type')
class HotelSerializer(serializers.ModelSerializer):
"""Serializes Restaurat, Bars, TouristInformation and Tourist Spots """
hotel_images = HotelImageSerializer(many=True, read_only=True)
hotel_social_media = HotelSocialMediaSerializer(many=True, read_only=True)
class Meta:
model = models.Hotel
fields = ('id', 'name', 'hotel_images', 'hotel_social_media')
Models:
class Hotel(models.Model):
"""Database model for hotels"""
name = models.CharField(max_length=50)
def __str__(self):
"""Return the model as a string"""
return self.name
class HotelImage(models.Model):
"""Image upload for hotel"""
name = models.CharField(max_length=50)
description = models.CharField(max_length=250)
path = models.ImageField(upload_to='images/')
created_on = models.DateTimeField(auto_now_add=True)
is_active = models.BooleanField(default=True)
hotel = models.ForeignKey(Hotel, related_name='hotel_images', on_delete=models.CASCADE, null=True)
def __str__(self):
"""Return the model as a string"""
return self.name
class SocialMediaType(models.Model):
"""Social Media Type eg: fb, twitter"""
name = models.CharField(max_length=50)
icon = models.ImageField(upload_to='images/')
url = models.CharField(max_length=50)
def __str__(self):
"""Return the model as a string"""
return self.name
class HotelSocialMedia(models.Model):
"""Social Media"""
hotel = models.ForeignKey(Hotel, related_name='hotel_social_media', on_delete=models.CASCADE, null=True)
type = models.ForeignKey(SocialMediaType, related_name='hotel_social_media_type', on_delete=models.CASCADE, null=True)
name = models.CharField(max_length=50)
url = models.CharField(max_length=50)
def __str__(self):
"""Return the model as a string"""
return self.name
Current result is:
{
"id": 1,
"name": "Alta Vista",
"hotel_images": [
{
"id": 1,
"name": "Alta Vista",
"path": "http://127.0.0.1:8000/media/images/hotel-1.jpg"
}
],
"hotel_social_media": [
{
"url": "https://www.facebook.com/abscbnNEWS"
}
]
}
What I want is:
{
"id": 1,
"name": "Alta Vista",
"hotel_images": [
{
"id": 1,
"name": "Alta Vista",
"path": "http://127.0.0.1:8000/media/images/hotel-1.jpg"
}
],
"hotel_social_media": [
{
"url": "https://www.facebook.com/abscbnNEWS",
"hotel_social_media_type": {
"name": "Facebook",
"icon": "http://127.0.0.1:8000/media/images/fb-1.jpg"
}
}
]
}
`hotel_social_media` must also display `hotel_social_media_type`
Based on your model, you must use type keyword instead of hotel_social_media_type in your HotelSocialMediaSerializer. Because your HotelSocialMedia has type field for relation with HotelSocialMediaType.To change keyword can solve your problem.
class HotelSocialMediaSerializer(serializers.ModelSerializer):
"""Serializes media files"""
type = SocialMediaTypeSerializer(many=False, read_only=True)
class Meta:
model = models.HotelSocialMedia
fields = ('url', 'type')
If you want to use hotel_social_media_type keyword, you can use SerializerMethodField like that:
class HotelSocialMediaSerializer(serializers.ModelSerializer):
"""Serializes media files"""
hotel_social_media_type = serializers.SerializerMethodField()
class Meta:
model = models.HotelSocialMedia
fields = ('url', 'hotel_social_media_type')
def get_hotel_social_media_type(self.obj):
serializer = SocialMediaTypeSerializer(obj.type)
return serializer.data

django rest framework return a custom object using ModelSerializer and ModelViewSet

I have three models, three serializers, one modelviewset below.
I am using django-rest-framework to make a rest api for android.
The restaurant model was created first. Then I created a star model and an image model.
What I want to do is to add star and image objects into restaurant objects.
finally I've got what I want result but I think my viewset code looks like wrong..
Is there another way not to use "for loop"?
Models
class Restaurant(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=255)
address = models.CharField(max_length=255)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
weather = models.ForeignKey(Weather, on_delete=models.CASCADE)
distance = models.ForeignKey(Distance, on_delete=models.CASCADE)
description = models.TextField('DESCRIPTION')
def __str__(self):
return self.name
class Star(models.Model):
restaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE)
rating = models.IntegerField('RATING')
def __str__(self):
return self.restaurant
class RestaurantImage(models.Model):
id = models.AutoField(primary_key=True)
restaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE)
path = models.CharField(max_length=255)
Serializer
class StarSerializer(serializers.ModelSerializer):
class Meta:
model = Star
fields = ('id', 'restaurant', 'user', 'rating', )
class RestaurantDetailSerializer(serializers.ModelSerializer):
category = CategorySerializer()
weather = WeatherSerializer()
distance = DistanceSerializer()
class Meta:
model = Restaurant
fields = ('id', 'name', 'address', 'category', 'weather',
'distance', 'description', )
class ImageSerializer(serializers.ModelSerializer):
class Meta:
model = RestaurantImage
fields = ('id', 'path', 'restaurant')
ViewSet
class RestaurantDetailInfoViewSet(viewsets.ModelViewSet):
queryset = Restaurant.objects.all()
serializer_class = RestaurantSerializer
def list(self, request, *args, **kwargs):
restaurant_list = Restaurant.objects.all()
restaurant_result = []
for restaurant in restaurant_list:
restaurantInfo = Restaurant.objects.filter(id=restaurant.pk)
restaurant_serializer = RestaurantDetailSerializer(restaurantInfo, many=True)
ratingAverageValue = Star.objects.filter(restaurant=restaurant.pk).aggregate(Avg('rating'))
images = RestaurantImage.objects.filter(restaurant=restaurant.pk)
image_serializer = ImageSerializer(images, many=True)
restaurant_dic = {
'restaurant': restaurant_serializer.data,
'ratingAverage': ratingAverageValue['rating__avg']
if ratingAverageValue['rating__avg'] is not None else 0,
'images': image_serializer.data
}
restaurant_result.append(restaurant_dic)
return Response(restaurant_result)
Result
[
{
"restaurant": [
{
"id": 1,
"name": "restaurant1",
"address": "address1",
"category": {
"c_id": 1,
"name": "foodtype1"
},
"weather": {
"w_id": 1,
"name": "sunny"
},
"distance": {
"d_id": 1,
"name": "inside"
},
"description": "description1"
}
],
"ratingAverage": 2.6667,
"images": [
{
"id": 1,
"path": "imagepath",
"restaurant": 1
}
]
},
Solution:
class RestaurantDetailSerializer(serializers.ModelSerializer):
category = CategorySerializer()
weather = WeatherSerializer()
distance = DistanceSerializer()
images = ImageSerializer(many=True, read_only=True)
ratingAverage = serializers.SerializerMethodField(read_only=True)
def get_ratingAverage(self, restaurant):
ratingAvgVal = Star.objects.filter(
restaurant=restaurant
).aggregate(Avg('rating'))['rating__avg']
return ratingAvgVal if ratingAvgVal is not None else 0
class Meta:
model = Restaurant
fields = ('id', 'name', 'address', 'category', 'weather',
'distance', 'description', 'images', 'ratingAverage', )
Explanation:
Here, I have nested the ImageSerializer in the RestaurantSerializer class, since you needed all the fields you've defined in ImageSerializer.
Then, for ratingAverage, I have used the SerializerMethodField which returns the value calculated (your logic) in the method I've defined for it, i.e. get_ratingAverage, which takes the Restaurant instance reference passed as an argument to the method for the field.

Reverse Nested Relationship Serialization in django rest framework

I am working on a deals/coupon selling website. I have following models, (excluding extra details).
class Order(models.Model):
email = models.EmailField(max_length=200, null=False)
phone_number = models.CharField(max_length=10, null=False)
shipping_address = models.TextField(blank=True,null=True)
coupon_code = models.CharField(max_length=20,null=True,blank=True)
gross_total = models.FloatField(default=0.0)
class Meta:
db_table = 'order'
class OrderDetail(models.Model):
order = models.ForeignKey(Order,related_name='order_details')
package = models.ForeignKey(Package)
quantity = models.IntegerField(null=False)
unit_price = models.FloatField(default=0.0)
class Meta:
db_table = 'order_detail'
class Coupon(models.Model):
order_detail = models.ForeignKey(OrderDetail,related_name='coupons')
code = models.CharField(max_length=200, null=False, unique=True)
maximum_usage_count = models.IntegerField(null=False)
used_count = models.IntegerField(default=0)
valid_from = models.DateTimeField(null=False)
valid_to = models.DateTimeField(null=False)
class Meta:
db_table = 'coupon'
My serializers for these are,
class CouponSerializer(serializers.Serializer):
class Meta:
model = Coupon
fields = ['id', 'code', 'maximum_usage_count', 'used_count', 'valid_from', 'valid_to', 'created_at',
'updated_at', 'is_active']
class OrderDetailSerializer(serializers.Serializer):
coupons = CouponSerializer(read_only=True)
class Meta:
model = OrderDetail
fields = ['id', 'package', 'quantity', 'unit_price', 'created_at', 'updated_at', 'is_active']
class OrderSerializer(serializers.ModelSerializer):
order_details = OrderDetailSerializer(read_only=True,many=True)
class Meta:
model = Order
fields = ['id','email', 'phone_number', 'shipping_address', 'coupon_code', 'gross_total','order_details']
In my listapiview, for fetching all orders, I have specified the order serializer. The api is working fine but is not able to serialize the reverse relation ship models. I am getting following response.
{
"id": 31,
"email": "ff#b.com",
"first_name": "ff",
"last_name": "ff",
"phone_number": "ff",
"shipping_address": "",
"coupon_code": "",
"gross_total": 1.0,
"payment_method": "ONLINE",
"order_status": "PLACED",
"created_at": "2016-10-01T17:26:00.432000",
"updated_at": "2016-10-01T17:48:50.797000",
"is_active": true,
"order_details": [
{
"coupons": {}
},
{
"coupons": {}
},
{
"coupons": {}
}
]
}
I think you should replace the inherited class with serializers.ModelSerializer in CouponSerializer and OrderDetailSerializer instead of just serializer.Serializer - just like you did in OrderSerializer.
After that you'll get some errors because your models (Coupon and OrderDetail) don't declare any of created_at, updated_at nor is_active fields. So you should add those fields to your models or remove them from the list in Meta in both serializer classes. But after that it works as expected.