Unable to get data of Foreign key tables in same serializer - django

Order table
class Orders(models.Model):
restaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE, blank=True, null=True)
tableid=models.IntegerField()
orderid=models.IntegerField()
total_amount = models.DecimalField(max_digits=10, decimal_places=2)
Articles table to save articles like pizza
class OrderArticle(models.Model):
order = models.ForeignKey(Orders, on_delete=models.CASCADE)
article = models.ForeignKey(Articles, on_delete=models.CASCADE)
# article_options = models.ManyToManyField(ArticlesOptions)
Article options to save extra topping or any option available
class OrderArticleOptions(models.Model):
# order = models.ForeignKey(Orders, on_delete=models.CASCADE)
article_option = models.ForeignKey(ArticlesOptions, on_delete=models.CASCADE)
order_article = models.ForeignKey(OrderArticle, on_delete=models.CASCADE)
quantity = models.IntegerField(default=1)
price = models.DecimalField(max_digits=10, decimal_places=2)
EDIT
Article Option table
class ArticlesOptions(models.Model):
articleoptionrestaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE , blank=True, null=True)
optionname = models.ForeignKey(ArticlesOptionsName, on_delete=models.CASCADE, related_name="optionnames")
min = models.IntegerField()
max = models.IntegerField()
choice_price = models.DecimalField(max_digits=10, decimal_places=2)
choice = models.CharField(max_length=255)
def __str__(self):
return str(self.optionname)
So Now issue is When I try to get all data in one serialize I am not able to get . I am using this example to get
https://www.django-rest-framework.org/api-guide/relations/
EDIT
My serilizers are
class OrderSerializer(serializers.ModelSerializer):
restaurant=RestaurantSerializer(required=False)
class Meta:
model = Orders
fields = ['restaurant','tableid', 'orderid', 'total_amount']
class ArticlesSerializer(serializers.ModelSerializer):
order = OrderSerializer(read_only=True)
article=ListArticleSerializer(read_only=True)
class Meta:
model = OrderArticle
fields = ['order', 'article']
class ArticlesOptionSerializer(serializers.ModelSerializer):
article_option = ListCategoriesSerializer( read_only=True)
order_article=ArticlesSerializer(read_only=True)
class Meta:
model = OrderArticleOptions
fields = ['article_option','order_article','quantity','price']
depth=1
My view.py is
class OrderedArticles(APIView):
def get(self, request, restid):
Options=OrderArticleOptions.objects.filter(order_article=1)
orderserlizer=ArticlesOptionSerializer(Options , many=True)
return Response(success_response({'orders': orderserlizer.data},
"Restaurant with this all data."), status=status.HTTP_200_OK)
My JSON Response is
"article_option":{ },
"order_article":{
"order":{
"restaurant":{ },
"tableid":12,
"orderid":1,
"total_amount":"0.00"
},
"article":{
"id":1,
"category":{ },
"ingredient":[ ],
"articleoptionnames":{ },
"restaurant":{ },
"articlename":"Article1",
"price":"1.90",
"pickuptax":6,
"dineintax":21,
"description":"This is a tekst field with more information about the product",
"image":"/media/Article/c1.264f3b28_sxcPiqi.png"
}
},
While I want these "article_option" to be as child of article like Article {article_option1, article_option2} but its creating new objects with every new article option.

If I understand you correctly, you want to return a representation of OrderArticle which have ArticleOption objects as its children. Which means you should instantiate an ArticlesSerializer in your view, but also modify ArticlesSerializer so that it includes all related article_options as a list (using the source attribute). Something like the following:
class ArticlesOptionSerializer(serializers.ModelSerializer):
article_option = ListCategoriesSerializer(read_only=True)
class Meta:
model = OrderArticleOptions
fields = ['article_option', 'order_article', 'quantity', 'price']
class ArticlesSerializer(serializers.ModelSerializer):
order = OrderSerializer(read_only=True)
article = ListArticleSerializer(read_only=True)
article_options = ArticlesOptionSerializer(read_only=True, source='orderarticleoptions_set', many=True)
class Meta:
model = OrderArticle
fields = ['order', 'article', 'article_options']
Then in your view, you should instantiate your ArticlesSerializer with the appropriate OrderArticle object:
class OrderedArticles(APIView):
def get(self, request, restid):
order_article = OrderArticle.objects.get(pk=1) # get pk/id from request
serializer = ArticlesSerializer(order_article)
return Response(serializer.data, status=status.HTTP_200_OK)

Related

Getting an Aggregate Value from a Many-to-Many Field inside a Serializer

We have an API endpoint that generates a response. However, we would like to aggregate some of the data and add an additional field to the serializer.
{
"id": 61,
"not_owned_value": # This is what we're trying to get (Aggregate best_buy_price from not_owned_inventory PlayerProfiles)
"not_owned_inventory": [ # PlayerProfile objects
"29666196ed6900f07fc4f4af4738bffe",
"0ff73ca20cd787c5b817aff62e7890da",
"99d4eaef9991695d7ad94b83ad5c5223",
"6fcabe9f9c8a95980923530e7d7353a7",
"80b34c84a6e5ed25df112c11de676adc",
"0a4c5b96474f0584519d1abc4364d5a2",
"9ed1f55ac4f3b402b1d08b26870c34a6",
]
}
Here is the models.py
class PlayerProfile(models.Model):
card_id = models.CharField(max_length=120, unique=True, primary_key=True)
name = models.CharField(max_length=120, null=True)
class PlayerListing(models.Model):
player_profile = models.OneToOneField(
PlayerProfile,
on_delete=models.CASCADE,
null=True)
best_buy_price = models.IntegerField(null=True)
class InventoryLiveCollection(models.Model):
not_owned_inventory = models.ManyToManyField(PlayerProfile, related_name="not_owned_inventory")
Here is the serializer.py
class InventoryLiveCollectionSerializer(serializers.ModelSerializer):
not_owned_inventory = PlayerProfileAndListingForNesting(read_only=True, many=True)
class Meta:
model = InventoryLiveCollection
fields = (
'id',
'date',
'not_owned_value', # Trying to get this
'not_owned_inventory',
)
How can I aggregate the best_buy_price of the player_profile objects that belong to a specific InventoryLiveCollection__not_owned_inventory?
You can try this.
from django.db.models import Sum
not_owned_value = serializers.SerializerMethodField()
def get_not_owned_value(self, obj):
value = obj.not_owned_inventory.all().aggregate(total=Sum('playerlisting__best_buy_price'))
return value['total']

I want to post a list of JSON values to a model's field in Django

I want to post a movie into the collection's movie field( list of movies).
I define the model as
class Movie(models.Model):
# collection = models.ForeignKey(Collection, on_delete = models.CASCADE) #, related_name='reviews'
title = models.CharField(max_length=200)
description = models.CharField(max_length=200)
genres = models.CharField(max_length=200)
uuid = models.CharField(max_length=200)
def __str__(self):
return self.title
class Collection(models.Model):
title = models.CharField(max_length=200)
uuid = models.CharField(max_length=200, primary_key = True)
description = models.CharField(max_length=200)
movie = models.ForeignKey(Movie, on_delete = models.CASCADE)
def __str__(self):
return self.title
this is how i am using the viewset
class CollectionViewSet(viewsets.ModelViewSet):
queryset = models.Collection.objects.all()
serializer_class = serializers.CollectionSerializer
but i am not able to enter values for the movie field
enter image description here
also my serializer
class CollectionSerializer(serializers.ModelSerializer):
class Meta:
model = Collection
fields = '__all__'
By default, DRF will represent the relationship with a PrimaryKeyRelatedField, thus expecting a movie ID.
To achieve what you want (create an instance of movie with a collection), you need to overwrite the foreign key field in your serializer with your own Movie serializer.
class MovieSerializer(serializers.ModelSerializer):
class Meta:
model = Movie
fields = '__all__'
class CollectionSerializer(serializers.ModelSerializer):
movie = MovieSerializer()
class Meta:
model = Collection
fields = '__all__'
def create(self, validated_data):
movie = validated_data.pop('movie')
movie = Movie .objects.create(**movie )
collection = Collection.objects.create(movie=movie, **validated_data)
return collection
You need to overwrite the create method so when creating a Collection, you also create a movie.
However, I am not sure the foreign key is set in the right model in your model. (a movie belongs to many collection but not the other way around?) If that's not what you want, just reverse the logic for the serializer.
Edit:
Sending the following should work fine:
{ "uuid": "1001",
"title": "Action",
"description": "Action Movies",
"movie": { "title": "The Burkittsville 7",
"description": "The story of Rustin Parr.",
"genres": "Horror",
"uuid": "5e904"
}
}
The only problem as I mentionned earlier is in your model you defined the foreign key field in collection. So it expects one single movie instance and not a list, thus I took off the brackets you put around movie. Maybe you should consider setting the foreign key in the Movie model, or use a Many to many relationship.
#models.py
class Movie(models.Model):
# collection = models.ForeignKey(Collection, on_delete = models.CASCADE) #, related_name='reviews'
title = models.CharField(max_length=200)
description = models.CharField(max_length=200)
genres = models.CharField(max_length=200)
uuid = models.CharField(max_length=200)
def __str__(self):
return self.title
class Collection(models.Model):
title = models.CharField(max_length=200)
uuid = models.CharField(max_length=200, primary_key = True)
description = models.CharField(max_length=200)
movie = models.ManyToManyField(Movie, on_delete = models.CASCADE)
def __str__(self):
return self.title
serializers.py:
class MovieSerializer(serializers.ModelSerializer):
class Meta:
model = Movie
fields = '__all__'
class CollectionSerializer(serializers.ModelSerializer):
movie = MovieSerializer(read_only=True, many=True)
class Meta:
model = Collection
fields = '__all__'
hope this will give you better unserstand this will work for you

Validation fail - field is required. ManyToMany serialization Django

I have form data that I want to serialize to create two objects, Account and AccountClub. AccountClub is the in between table between Account and Club with additional fields, rakeback and chip_value.
I can serialize the formdata but when i call the is.valid() function before saving, I get returned an error with the manytomany fields empty
Here is my models:
class Account(models.Model):
nickname = models.CharField(max_length=64)
club_account_id = models.IntegerField()
agent_players = models.ManyToManyField(
AgentPlayer, related_name="accounts")
clubs = models.ManyToManyField(
Club, through='AccountClub', related_name='accounts')
def __str__(self):
return f"{self.nickname} ({self.club_account_id})"
class AccountClub(models.Model):
account = models.ForeignKey(
Account, on_delete=models.CASCADE, related_name='club_deal')
club = models.ForeignKey(
Club, on_delete=models.CASCADE, related_name='account_deal')
rakeback_percentage = models.DecimalField(
max_digits=3, decimal_places=3, validators=[MinValueValidator(Decimal('0.01'))])
chip_value = models.DecimalField(max_digits=3, decimal_places=2, validators=[
MinValueValidator(Decimal('0.01'))])
def __str__(self):
return f"{self.account.nickname} belongs to {self.club.name} with rakeback of {self.rakeback_percentage} and chip value of {self.chip_value}"
Serializers:
class AgentPlayerSerializer(serializers.ModelSerializer):
class Meta:
model = AgentPlayer
fields = "__all__"
class ClubSerializer(serializers.ModelSerializer):
agent_players = AgentPlayerSerializer(many=True)
class Meta:
model = Club
fields = '__all__'
class AccountSerializer(serializers.ModelSerializer):
agent_players = AgentPlayerSerializer(many=True)
clubs = ClubSerializer(many=True)
class Meta:
model = Account
fields = [
'nickname',
'club_account_id',
'agent_players',
'clubs',
]
def create(self, validated_data):
rakeback_percentage = validated_data.pop('rakeback_percentage')
chip_value = validated_data.pop('chip_value')
club = validated_data.club
account = Account.objects.create(**validated_data)
account.account_club.rakeback_percentage = rakeback_percentage
account.account_club.chip_value = chip_value
AccountClub.create(account=account, club=club,
rakeback_percentage=rakeback_percentage, chip_value=chip_value)
return account
views.py:
def create_account(request):
data = FormParser().parse(request)
serializer = AccountSerializer(data=data)
if serializer.is_valid():
serializer.save()
next = request.POST.get('next', '/')
return HttpResponseRedirect(next, status=201)
return JsonResponse(serializer.errors, status=400)
your clubs field on the Account model is not blank=True so you can not create an account without at least a club. so you can not do
account = Account.objects.create(**validated_data)
and then do
AccountClub.create(account=account, club=club, rakeback_percentage=rakeback_percentage, chip_value=chip_value)
you may change your Account model code to:
clubs = models.ManyToManyField(Club, through='AccountClub', blank=True, related_name='accounts')
also checkout these links:
https://stackoverflow.com/a/6996358/6484831
https://stackoverflow.com/a/10116452/6484831

Serialize Many to Many Relationship with Extra fields

Please I'm stuck trying to get around this issue. Guess there is something I'm not getting after looking at other similar questions.
I have these models:
class Dish(BaseModel):
class Meta:
verbose_name_plural = 'dishes'
name = models.CharField(_('dish'), max_length=100)
dish_type = models.CharField(_("dish type"), max_length=100)
price = models.PositiveIntegerField(_("price"))
def __str__(self):
return f"{self.name} costs {self.price}"
class Order(BaseModel):
dishes = models.ManyToManyField(Dish, through='DishOrder')
customer = models.ForeignKey(Customer, on_delete=models.CASCADE)
discount = models.PositiveIntegerField(_("total discount"), blank=True)
total = models.PositiveIntegerField(_("total"), blank=True)
shipping = models.PositiveIntegerField(_("shipping cost"), blank=True)
grand_total = models.PositiveIntegerField(_("grand total"), blank=True)
country = models.CharField(_('country code'), max_length=2)
def __str__(self):
return f"order from {self.customer} at {self.total}"
def get_absolute_url(self):
return reverse('order-details', kwargs={'pk': self.pk})
class DishOrder(models.Model):
dish = models.ForeignKey(Dish, on_delete=models.CASCADE, related_name='dishes')
order = models.ForeignKey(Order, on_delete=models.CASCADE, related_name='dishes')
quantity = models.PositiveIntegerField(_("quantity"))
discount = models.PositiveIntegerField(_("discount"))
price = models.PositiveIntegerField(_('price'))
And the corresponding serializers like so:
class DishOrderSerializer(serializers.ModelSerializer):
class Meta:
model = DishOrder
fields = (
"quantity",
"discount",
"price"
)
class OrderSerializer(serializers.ModelSerializer):
dishes = DishOrderSerializer(source='dish', many=True)
class Meta:
model = Order
fields = (
"id",
"country",
"customer",
"dishes",
"total",
"discount",
"grand_total",
"voucher"
)
So as can be seen, I have a m2m relationship via a through table. However I can't get the serializer to work. This is the error I keep getting:
Got AttributeError when attempting to get a value for field dishes
on serializer OrderSerializer. The serializer field might be named
incorrectly and not match any attribute or key on the Order
instance. Original exception text was: 'Order' object has no attribute
'dish'.
I have been looking through this for some time trying to figure out what the error is. I will appreciate any help
Since you are using related_name='dishes' in model you should use dishes as source to the manytomany objects:
class OrderSerializer(serializers.ModelSerializer):
dishes = DishOrderSerializer(source='dishes', many=True)
or simple:
class OrderSerializer(serializers.ModelSerializer):
dishes = DishOrderSerializer(many=True)
Since source='dishes' redundant in case you named serializer's field dishes also.

DRF - serialize different models into one

I have several models that have a user as a foreign key. For example, I have model Profile, model profilePic and model userQuestions - in all that models user is foreign key.
Is the there a way a can get a profile, a profilePic and userQuestions that corresponds to given user in one single json response ?
my models.py is following
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
gender = models.CharField(max_length=2)
name = models.CharField(max_length=200)
birthday = models.DateField(auto_now=False, auto_now_add=False)
weight = models.IntegerField(default=0)
heigth = models.IntegerField(default=0)
sign = models.CharField(max_length=200, choices=SIGNS_CHOICES, default='E')
orientation = models.CharField(max_length=200, choices=ORIENTATION_CHOICES, default='E')
bodytype = models.CharField(max_length=200, choices=BODYTYPE_CHOICES, default='E')
education = models.CharField(max_length=200, choices=EDUCATION_CHOICES, default='E')
religion = models.CharField(max_length=200, choices=RELIGION_CHOICES, default='E')
smoking = models.CharField(max_length=200, choices=SMOKING_CHOICES, default='E')
alcohol = models.CharField(max_length=200, choices=ALCOHOL_CHOICES, default='E')
kids = models.CharField(max_length=200, choices=KIDS_CHOICES, default='E')
pets = models.CharField(max_length=200, choices=KIDS_CHOICES, default='E')
location = models.CharField(max_length=100)
latitude = models.FloatField()
longtitude = models.FloatField()
class ProfileFields(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=200)
text = models.TextField()
order = models.IntegerField(default=0)
class ProfilePic(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
profilePic = models.ImageField(upload_to='Images/', default='Images/None/No-img.jpg')
class Pics(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
pic = models.ImageField(upload_to='Images/', default='Images/None/No-img.jpg')
UPD
tried to add
def to_representation(self, data):
profile_info = {kv: data[kv] for kv in data if kv in list(ProfileSerializer.Meta.fields)}
profile_pic_info = {kv: data[kv] for kv in data if kv in list(ProfilePicSerializer.Meta.fields)}
return super(DeviceInfoSerializer, self).to_representation({
'profile': profile_info,
'profile_pic': profile_pic_info,
})
but now it says object 'User' is not iteratable
You can add them as nested serializers to your UserSerializer. Something like this.
class ProfileSerializer(serializers.ModelSerializer)
class Meta:
model = Profile
fields = '__all__'
class ProfilePicSerializer(serializers.ModelSerializer)
class Meta:
model = ProfilePic
fields = '__all__'
class UserQuestionsSerializer(serializers.ModelSerializer)
class Meta:
model = UserQuestions
fields = '__all__'
class UserSerializer(serializers.ModelSerializer)
profile = ProfileSerializer(many=False)
profilepic = ProfilePicSerializer(many=False)
user_questions = UserQuestionsSerializer(mane=True)
class Meta:
model = User
fields = '__all__'
I struggled with the same issue about a month ago and found no good answer on SO.
Sardorbek's answer is totally correct in case you dont care if json response is flat or not. JSON created by that code is something like:
{
"profile": {"name": ..., "gender": ..., "birthday": ...,},
"profile_pic": {"profilePic": ...., },
"user_questions": [{...}, {...}, ...]
User model attributes go here...
}
but if you want it to be flat, like:
{
"name": ...,
"gender": ...,
"birthday": ...,
....
"profilePic": ...,
"user_questions": [{...}, {...}, ...],
User model attributes go here...
}
You need a little hack here. Override to_internal_value in UserSerializer class in this way:
def to_internal_value(self, data):
profile_info = {kv: data[kv] for kv in data if kv in list(ProfileSerializer.Meta.fields)}
profile_pic_info = {kv: data[kv] for kv in data if kv in list(ProfilePicSerializer.Meta.fields)}
return super(DeviceInfoSerializer, self).to_internal_value({
'profile': profile_info,
'profile_pic': profile_pic_info,
})
You may need to override to_representation too, with similar idea.