Reverse Nested Relationship Serialization in django rest framework - django

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.

Related

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: omit dict where field is empty

I'm building a rest API for a media library.
Here are some of my python code, I have simplify them a bit, so only the necessary code is included, to have a better overview about the current problem.
models.py:
class MediaSeries(models.Model):
name = models.CharField(max_length=200)
description = models.TextField(blank=True)
class MediaSeriesEntries(models.Model):
serie = models.ForeignKey(MediaSeries, related_name='series',
db_column='seriesID', null=True,
on_delete=models.SET_NULL)
file = models.ForeignKey(MediaFiles, related_name='serie_set',
db_column='fileID', null=True,
on_delete=models.SET_NULL)
class MediaFiles(models.Model):
youtube = models.CharField(max_length=300, blank=True)
name = models.CharField(max_length=300)
screeningPause = models.BooleanField(default=0)
duration = models.BigIntegerField(default=0)
creatingDate = models.DateTimeField(default=now)
class Library(models.Model):
media = models.ForeignKey(MediaFiles, related_name='library_set',
db_column='fileID', null=True,
on_delete=models.SET_NULL)
visits = models.IntegerField(default=0)
likes = models.IntegerField(default=0)
views.py:
class LibraryMediaFilter(filters.FilterSet):
class Meta:
model = Library
fields = ['media__name', 'media__duration',
'media__youtube', 'media__creatingDate']
class LibraryMediaViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows the library to be viewed.
Example ordering:
http://127.0.0.1:8000/api/library-media/?ordering=media__creatingDate
"""
queryset = Library.objects.all() \
.exclude(media__youtube="").exclude(media__screeningPause=True)
serializer_class = LibraryMediaSerializer
pagination_class = LibraryResultsSetPagination
filter_backends = (filters.DjangoFilterBackend, OrderingFilter)
filterset_class = LibraryMediaFilter
ordering_fields = ['media__serie_set__serie__name', 'media__name',
'media__creatingDate', 'visits', 'likes']
serializers.py:
class LibraryMediaSerializer(serializers.ModelSerializer):
name = serializers.CharField(source='media.name', read_only=True)
duration = serializers.IntegerField(source='media.duration',
read_only=True)
youtube = serializers.CharField(source='media.youtube', read_only=True)
creatingDate = serializers.DateTimeField(source='media.creatingDate',
read_only=True)
serie_set = SeriesEntriesSerializer(source='media.serie_set',
read_only=True, many=True)
class Meta:
model = Library
fields = ('visits', 'likes', 'name', 'duration', 'youtube',
'creatingDate', 'serie_set')
When I call now the api with http://example.org/api/library-media/ I get this result:
{
"visits": 0,
"likes": 0,
"name": "example movie",
"duration": 9505000,
"youtube": "https://youtu.be/34345",
"creatingDate": "2013-01-01T01:00:00+01:00",
"serie_set": []
},
{
"visits": 0,
"likes": 0,
"name": "example serie",
"duration": 3436000,
"youtube": "https://youtu.be/z0c4rLCcyVE",
"creatingDate": "2013-01-01T01:00:00+01:00",
"serie_set": [
{
"name": "Das große Ganze",
"description": ""
}
]
},
Now I would like to filter against empty and not empty serie_set, with something like:
http://example.org/api/library-media/media__serie_set__isempty=true
or:
http://example.org/api/library-media/media__serie_set__isempty=false

Reverse foreign key with cross reference table - Django Rest Framework

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"
}
]
}
]
}
]

Override 'create' function in Django serializer

I wonder how to handle POST request to properly save the incoming data, having such models:
class Recipe(models.Model):
author = models.ForeignKey('auth.user', related_name='recipes', on_delete=models.CASCADE)
image = models.TextField(default='None')
name = models.CharField(max_length=100)
description = models.TextField(default='No description')
votes = models.IntegerField(default=0)
def __str__(self):
return self.name
class Ingredient(models.Model):
image = models.TextField(default='None')
name = models.CharField(max_length=100)
description = models.TextField(default='No description')
price = models.DecimalField(max_digits=8, decimal_places=3)
unit_price = models.DecimalField(max_digits=8, decimal_places=3)
unit_quantity = models.CharField(max_length=20)
def __str__(self):
return self.name
I wanted to avoid duplicating Ingredient objects, so to provide quantity of specific Ingredient in Recipe I've created a RecipesIngredient model that binds Ingredient with Recipe, but also contains a quantity of this Ingredient:
class RecipesIngredient(models.Model):
recipe = models.ForeignKey(Recipe, related_name='ingredients', on_delete=models.CASCADE)
ingredient = models.ForeignKey(Ingredient, on_delete=models.CASCADE)
quantity = models.CharField(max_length=100)
def __str__(self):
return self.quantity
I've also prepared some serializers for these models:
class IngredientSerializer(HyperlinkedModelSerializer):
class Meta:
model = Ingredient
fields = (
'url',
'image',
'name',
'description',
'price',
'unit_price',
'unit_quantity'
)
class RecipesIngredientSerializer(HyperlinkedModelSerializer):
ingredient_name = ReadOnlyField(source='ingredient.name')
ingredient_price = ReadOnlyField(source='ingredient.price')
ingredient_unit_price = ReadOnlyField(source='ingredient.unit_price')
ingredient_unit_quantity = ReadOnlyField(source='ingredient.unit_quantity')
class Meta:
model = RecipesIngredient
fields = (
'url',
'ingredient_name',
'quantity',
'ingredient_price',
'ingredient_unit_price',
'ingredient_unit_quantity'
)
class RecipeListSerializer(HyperlinkedModelSerializer):
author = ReadOnlyField(source='author.username')
author_url = ReadOnlyField(source='author.url')
class Meta:
model = Recipe
fields = (
'url',
'author',
'author_url',
'image',
'name',
'description',
'votes'
)
class RecipeDetailSerializer(HyperlinkedModelSerializer):
author = ReadOnlyField(source='author.username')
author_url = ReadOnlyField(source='author.url')
ingredients = RecipesIngredientSerializer(many=True)
class Meta:
model = Recipe
fields = (
'url',
'author',
'author_url',
'image',
'name',
'description',
'ingredients',
'votes'
)
But in this case, I have to first create a Recipe instance and save it to DB, then do the same with Ingredient to be able to "bind" them in RecipesIngredient. Is this possible to handle this case with only one POST request to view below?
#
# path('recipes/', views.RecipeList.as_view(), name='recipe-list')
#
class RecipeList(generics.ListCreateAPIView):
queryset = Recipe.objects.all()
serializer_class = RecipeListSerializer
def perform_create(self, serializer):
serializer.save(author=self.request.user)
#EDIT
I forgot 'bout this thread, but the problem is solved now. I've prepared another serializer for CREATE purposes only, and overridden the 'create' function of this serializer.:
class RecipeCreateSerializer(HyperlinkedModelSerializer):
#author = ReadOnlyField(source='author.username')
#author_url = ReadOnlyField(source='author.url')
recipes_ingredients = RecipesIngredientCreateSerializer(many=True)
def create(self, validated_data):
recipes_ingredients = validated_data.pop('recipes_ingredients')
recipe_instance = super().create(validated_data)
for recipe_ingredient in recipes_ingredients:
ingredient_data = recipe_ingredient.pop('ingredient')
ingredient_instance = Ingredient(
image=ingredient_data['image'],
name=ingredient_data['name'],
description=ingredient_data['description'],
price=ingredient_data['price'],
unit_price=ingredient_data['unit_price'],
unit_quantity=ingredient_data['unit_quantity'],
)
ingredient_instance.save()
recipes_ingredient_instance = RecipesIngredient(
recipe=recipe_instance,
ingredient=ingredient_instance,
quantity=recipe_ingredient['quantity']
)
recipes_ingredient_instance.save()
return recipe_instance
class Meta:
model = Recipe
fields = (
'url',
'image',
'name',
'description',
'votes',
'recipes_ingredients',
)
Also the JSON file looks a bit different now, but everything works just fine:
{
"image": "image-url",
"name": "recipes-name",
"description": "recipes-description",
"votes": 0,
"recipes_ingredients": [
{
"quantity": "ingredients-quantity",
"ingredient": {
"image": "image-url",
"name": ingredient-name",
"description": "ingredient-description",
"price": 3.6,
"unit_price": 0.36,
"unit_quantity": "100ML"
}
},
{
"quantity": "ingredients-quantity",
"ingredient": {
"image": "image-url",
"name": ingredient-name",
"description": "ingredient-description",
"price": 0.3,
"unit_price": 0.3,
"unit_quantity": "EACH"
}
},
{
"quantity": "ingredients-quantity",
"ingredient": {
"image": "image-url",
"name": ingredient-name",
"description": "ingredient-description",
"price": 2.0,
"unit_price": 0.8,
"unit_quantity": "KG"
}
}
]
}

django rest-framework serializer reverse relation

I am programming django based web site using django rest-framework.
I want to use rest-framework to get model's data.
this is my model.py
class TimeTable(models.Model):
subject_name = models.CharField(max_length=50)
subject_code = models.CharField(max_length=10, unique=True)
classification = models.CharField(max_length=50)
professor = models.CharField(max_length=50)
department = models.CharField(max_length=50)
credit = models.CharField(max_length=1)
year = models.CharField(max_length=4, default='2018')
semester = models.CharField(max_length=1, default='1')
def __str__(self):
return self.subject_code + '-' + self.subject_name
class Class(models.Model):
owner = models.ForeignKey(Profile, null=True)
timetable = models.ForeignKey(TimeTable, null=True)
grade = models.FloatField()
this is serializer.py
class TimeTableSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = TimeTable
fields = ('url', 'subject_name', 'subject_code', 'classification', 'professor', 'department', 'credit', 'year', 'semester')
class ClassSerializer(serializers.HyperlinkedModelSerializer):
timetables = TimeTableSerializer(read_only=True)
class Meta:
model = Class
fields = ('url','owner', 'timetable', 'grade', 'timetables')
I want to get JSON response Like this
(http://localhost:8000/api/classes/)
[
{
"url": "http://localhost:8000/api/classes/8/",
"owner": "http://localhost:8000/api/profiles/19/",
"timetable": "http://localhost:8000/api/timetables/3/",
"grade": 4.5
"timetables": {
"url": "http://localhost:8000/api/timetables/3/",
"subject_name": "Artificial Inteligence",
"subject_code": "3413513413",
"classification": "major",
"professor": "John Lee",
"department": "software",
"credit": "3",
"year": "2018",
"semester": "1"
}
}
]
but i got this
[
{
"url": "http://localhost:8000/api/classes/8/",
"owner": "http://localhost:8000/api/profiles/19/",
"timetable": "http://localhost:8000/api/timetables/3/",
"grade": 4.5
}
]
How Can I get TimeTable's JSON data in Class JSON??
class ClassSerializer(serializers.HyperlinkedModelSerializer):
timetable = TimeTableSerializer(read_only=True)
class Meta:
model = Class
fields = ('url','owner', 'timetable', 'grade')