I'm building an API using Django-restframework.
models.py
class Researches(models.Model):
research_id = models.BigAutoField(primary_key=True)
I would like to use the same value on two differents fileds, like:
[
{
"research_id": 1,
"id": 1
},
]
Is it possible?
You need to do something like this:
class ResearchesSerializer(
serializers.ModelSerializer
):
id = serializers.IntegerField(
source="research_id", read_only=True
)
class Meta:
model = Researches
fields = (
"research_id",
"id",
)
for ref: https://www.django-rest-framework.org/api-guide/serializers/#specifying-fields-explicitly
Related
in my post requests to OrderProduct model, i want to only have to pass order.id and product.id and it works... untill i add a serializer to retrieve product.name. It might be because i didnt understand documentation about nested requests, but im unable to advance further into my project :(
[
{
"id": 2,
"order": 1,
"product": 1,
}
]
^ here's how it looks without nested serializer, and thats the data that i wanna have to input
[
{
"id": 2,
"order": 1,
"product": {
"id": 1,
"name": "gloomhaven",
},
},
^ here's how it looks after i add an additional serializer. I pretty much want these nested fields to be read only, with me still being able to send simple post requests
here are my serializers
class OrderProductSerializer(serializers.ModelSerializer):
product = Product()
class Meta:
model = OrderProduct
fields = [
"id",
"order",
"product"]
class Product(serializers.ModelSerializer):
class Meta:
model = Product
fields = (
"id",
"name")
Is there any way for me to accomplish this? Thank you for trying to help!
Just overwrite to_representation method of the serializer
def to_representation(self, instance):
response = super().to_representation(instance)
response['other_field'] = instance.id# also response['other_field'] = otherSerializer(instance.model)
return response
This can solve your problem
I think you are missing many=True
class OrderProductSerializer(serializers.ModelSerializer):
product = Product(many=True)
class Meta:
model = OrderProduct
fields = [
"id",
"order",
"product"]
I have a model where users can upvote other users for specific topics. Something like:
#models.py
Class Topic(models.Model):
name = models.StringField()
def __str__(self):
return str(self.name)
Class UserUpvotes(models.Model):
"""Holds total upvotes by user and topic"""
user = models.ForeignKey(User)
topic= models.ForeignKey(Topic)
upvotes = models.PositiveIntegerField(default=0)
Using DRF, I have an API that returns the following: topic_id, topic_name, and upvotes, which is the total upvotes for a given topic.
One of the project requirements is for the API to use these field names specifically: topic_id, topic_name, and upvotes
#serializers.py
class TopicUpvotesSerializer(serializers.ModelSerializer):
topic_name = serializers.StringRelatedField(source="topic")
class Meta:
model = UserUpvotes
fields = ["topic_id", "topic_name", "upvotes"]
My trouble is aggregating these fields. I'm filtering the UserUpvotes by user or team and then aggregating by topic.
Desired output
This is the result I want to get. When I don't perform any aggregations (and there are views where this will be the case), it works.
[
{
"topic_id": 3,
"topic_name": "Korean Studies",
"upvotes": 14
},
{
"topic_id": 12,
"topic_name": "Inflation",
"upvotes": 3
},
]
At first, I tried creating a TopicSerializer, and then assigning it to the topic field in TopicUpvotesSerializer. But then, the resulting json would have a nested "topic" field and the aggragation would fail.
Attempt 1
#views.py
def get_queryset(self):
return (
UserUpvotes.objects.filter(user__team=team)
.values("topic")
.annotate(upvotes=models.Sum("upvotes"))
.order_by("-upvotes")
)
My problem is that the topic_id and topic_name fields are not showing. I get something like:
[
{
"topic_name": "3",
"upvotes": 14
},
{
"topic_name": "12",
"upvotes": 3
},
]
Attempt 2
Another queryset attempt:
# views.py
def get_queryset(self):
return (
UserUpvotes.objects.filter(user__team=team)
.values("topic__id", "topic__name")
.annotate(upvotes=models.Sum("upvotes"))
.order_by("-upvotes")
)
Which yields:
[
{
"upvotes": 14
},
{
"upvotes": 3
},
]
The aggregation worked on the queryset level, but the serializer failed to find the correct fields.
Attempt 3
This was the closest I got:
# views.py
def get_queryset(self):
return (
UserUpvotes.objects.filter(user__team=team)
.values("topic__id", "topic__name")
.annotate(upvotes=models.Sum("upvotes"))
.values("topic_id", "topic", "upvotes")
.order_by("-upvotes")[:n]
)
[
{
"topic_name": 3,
"topic_name": "3",
"upvotes": 14
},
{
"topic_name": 12,
"topic_name": "12",
"upvotes": 3
},
]
I have no idea why "topic_name" is simply transforming the "topic_id" into a string, instead of calling the string method.
Work with a serializer for the topic:
class TopicSerializer(serializers.ModelSerializer):
upvotes = serializers.IntegerField(read_only=True)
class Meta:
model = Topic
fields = ['id', 'name', 'upvotes']
then in the ModelViewSet, you annotate:
from django.db.models import Sum
from rest_framework.viewsets import ModelViewSet
class TopicViewSet(ModelViewSet):
serializer_class = TopicSerializer
queryset = Topic.objects.annotate(upvotes=Sum('userupvotes__upvotes'))
Desired output
This is the result I want to get. When I don't perform any aggregations (and there are views where this will be the case), it works.
[
{
"topic_name": 3,
"topic_name": "Korean Studies",
"upvotes": 14
},
{
"topic_name": 12,
"topic_name": "Inflation",
"upvotes": 3
},
]
The serialized FK will always give you the ID of the related model. I am not sure why you name it topic_name if that is equal to an ID. Now, if you really want to get the name field of the Topic model
in the topic_name = serializers.StringRelatedField(source="topic") you should give it a source="topic.name"
However, if you trying to get the ID of the relation you can still use ModelSerializer :
class TopicUpvotesSerializer(serializers.ModelSerializer):
class Meta:
model = UserUpvotes
fields = "__all__"
#willem-van-onsem's answer is the correct one for the problem as I had put it.
But... I had another use case (sorry! ◑﹏◐), for when the Users API used UserUpvotes serializer as a nested field. So I had to find another solution. This is was I eventually ended up with. I'm posting in case it helps anyone.
class UserUpvotesSerializer(serializers.ModelSerializer):
topic_name = serializers.SerializerMethodField()
def get_topic_name (self, obj):
try:
_topic_name = obj.topic.name
except TypeError:
_topic_name = obj.get("skill__name", None)
return _topic_name
class Meta:
model = UserUpvotes
fields = ["topic_id", "topic_name", "upvotes"]
I still have no idea why the SerializerMethodField works and the StringRelatedField field doesn't. It feels like a bug?
Anyways, the rub here is that, after the values().annotate() aggregation, obj is no longer a QuerySet, but a dict. So accessing namedirectly will give you a 'UserUpvotes' object is not subscriptable error.
I don’t know if there are any other edge cases I should be aware of (this is when I REALLY miss type hints in Django), but it works so far
How can i merge equal fields and append different values to the fields in the returned response into one, independent of the amount of objects? When accessing the enpoint, i currently get the following response:
[
{
"colors": [
"Red",
"Orange",
],
"styles": [
"Rock"
],
"application": [
"Wall"
],
"material": [
"Mosaic"
]
},
{
"colors": [
"Yellow",
],
"styles": [
"Mosaic"
],
"application": [
"Wall"
],
"material": [
"Ceramic"
]
}
]
While want to achieve something like the snippet bellow, where unique values are appended and equal fields are merged:
[
{
"colors": [
"Red",
"Orange",
"Yellow"
],
"styles": [
"Rock"
"Mosaic"
],
"application": [
"Wall"
],
"material": [
"Mosaic"
"Ceramic"
]
},
]
My serializer is structured like this:
class ProductFiltersByCategorySerializer(serializers.ModelSerializer):
"""
A serializer to display available filters for a product lust
"""
colors = serializers.StringRelatedField(read_only=True, many=True)
styles = serializers.StringRelatedField(read_only=True, many=True)
application = serializers.StringRelatedField(read_only=True, many=True)
material = serializers.StringRelatedField(read_only=True, many=True)
class Meta:
model = Product
fields = (
'colors',
'styles',
'application',
'material'
)
My viewset is structured like this:
class ProductFiltersByCategory(generics.ListAPIView):
"""
This viewset takes the category parameter from the url and returns related product filters
"""
serializer_class = ProductFiltersByCategorySerializer
def get_queryset(self):
category = self.kwargs['category']
return Product.objects.filter(category__parent__name__iexact=category).distinct()
The fields colors, styles, application and material are ManytoMany relations to their own models from the Product model.5
Update 1: (Models)
class ProductSize(models.Model):
...
class ProductColor(models.Model):
...
class ProductStyle(models.Model):
...
class ProductApplication(models.Model):
...
class ProductMaterial(models.Model):
...
class Product(models.Model):
...
colors = models.ManyToManyField(
ProductColor,
related_name='product_color'
)
styles = models.ManyToManyField(
ProductStyle,
related_name='product_style'
)
application = models.ManyToManyField(
ProductApplication,
related_name='product_application'
)
material = models.ManyToManyField(
ProductMaterial,
related_name='product_material'
)
absorption = models.FloatField(
null=True,
blank=True
)
...
This is because your query returns two objects and each object has some related field. Serializers serialize each object separately so you should merge fields in your view that is responsible for the logic of the application, for this if your database is postgres you can use some code like this:
from django.contrib.postgres.aggregates import ArrayAgg
Product.objects.filter(category__parent__name__iexact=category).distinct().aggregate(colors_field=ArrayAgg('colors__name'))
or you can write separate query for each field.
I already have a general idea of how it should be done. The only issue that I face now is how to actually send the data. I don't want to create new Projects I just want to add them to the notifications. How do I pass the data, the actual JSON?
class NotificationsScheduleSerializer(ModelSerializer):
projects = ProjectSerializer(many=True) # Thats the Many2Many Field
user = HiddenField(default=CurrentUserDefault())
class Meta:
model = NotificationsSchedule
fields = [
"pk",
"projects",
"period",
"week_day",
"created_at",
"time",
"report_type",
"user",
]
def create(self, validated_data):
breakpoint() # I don't ever get "projects" in validated_data just Empty OrderedDict
projects_data = validated_data.pop("projects", [])
notification = NotificationsSchedule.objects.create(**validated_data)
return notification
class ProjectSerializer(ModelSerializer):
class Meta:
model = Project
fields = ["pk", "name"]
I want to be able to pass something like this.
{
"projects": [290, 289],
"period": "daily",
"week_day": 2,
"time": "16:02:00",
"report_type": "word_report"
}
But it expects dict instead.
"non_field_errors": [
"Invalid data. Expected a dictionary, but got int."
]
You have to set read_only,
projects = ProjectSerializer(many=True, read_only=True)
And when creating Notifications ,
notification = NotificationsSchedule.objects.create(**validated_data)
notification.projects.add(*self.initial_data.get("projects"))
notification.save()
I have an api in django-rest framework that now returns this json data:
[
{
"id": 1,
"foreignobject": {
"id": 3
},
"otherfields": "somevalue"
}
]
But I want it to return something like this (flatten the foreigneky to its ID only):
[
{
"id": 1,
"foreignobject_id":3,
"otherfields": "somevalue"
}
]
Doing this in the model Resource, now I Have (simplified):
class ForeignKeyInDataResource(ModelResource):
model = TheOtherModel
fields = ('id',)
class SomeModelResource(ModelResource):
model = SomeModel
fields = ( 'id',('foreignobject','ForeignKeyInDataResource'),'otherfields',)
I tried already something like:
class SomeModelResource(ModelResource):
model = SomeModel
fields = ( 'id','foreignobject__id','otherfields',)
but that did not work
for the complete story, this how the view returns the data, list is a result of a query over the SomeModel:
data = Serializer(depth=2 ).serialize(list)
return Response(status.HTTP_200_OK, data)
I'm not really in a position to support REST framework 0.x anymore, but if you decide to upgrade to 2.0 this is trivial - simply declare the field on the serializer like so: foreignobject = PrimaryKeyRelatedField()
I found another option: (by reading the ModelResource documentation...)
In the Modelresource you can define a function(self,instance), which can return the id.
in the fields you can add this function!
so, this works:
class SomeModelResource(ModelResource):
model = SomeModel
fields = ( 'id','foreignobject_id','otherfields',)
def foreignobject_id(self, instance):
return instance['foreignobject']['id']