I have a field what displays an average score and depends on another model's field. I use SerializerMethodField in order to get needed value. It looks like this:
class TitleSerializer(serializers.ModelSerializer):
rating = serializers.SerializerMethodField()
class Meta:
fields = '__all__'
model = Titles
def get_rating(self, obj):
rating = obj.reviews.all().aggregate(Avg('score'))
return rating
It works but doesn't return it in a way I need. Now I get data what looks like:
"rating" : {
"score__avg" : some_value
}
How can I change it to:
"rating" : some_value
Thanks in advance.
You give the aggregate expression a name, so with a named parameter:
def get_rating(self, obj):
# returns {'rating': … }
return obj.reviews.aggregate(rating=Avg('score'))
or you can unwrap it out of the dictionary:
def get_rating(self, obj):
# returns … (value)
return obj.reviews.aggregate(Avg('score'))['score__avg']
Related
I'm wanting to know how you would pass nested data to a ModelSerializer if the child of the nested data is not a model on its own.
The data that I'm working with looks like this:
{
'leadId': 12345,
'updateTime': 1651250096821,
'changeInfo': {
'oldstage': 'New Leads',
'newstage': 'Attempting Contact'
}
}
From previous experience, I know that if I was only working with the leadId and the updateTime, my serializer would look like this:
class LogSerializer(serializers.ModelSerializer):
leadId = serializers.IntegerField(source="lead_id")
updateTime = serializers.IntegerField(source="update_time")
class Meta:
model = Log
fields = ["leadId", "updateTime"]
Which would then make it possible to do this:
data = {
'leadId': 12345,
'updateTime': 1651250096821
}
serializer = LogSerializer(data=data)
serializer.is_valid()
serializer.save()
If I'm not wanting to turn changeInfo into its own model, is it possible to map the fields to the nested data? Something that might look like this (but this obviously doesn't work):
class LogSerializer(serializers.ModelSerializer):
leadId = serializers.IntegerField(source="lead_id")
updateTime = serializers.IntegerField(source="update_time")
oldstage = serializers.IntegerField(source="oldstage")
newstage = serializers.IntegerField(source="newstage")
class Meta:
model = Log
fields = ["leadId", "updateTime", "oldstage", "newstage]
You can use a custom serializer for your changeInfo field (you don't need to create a model for that):
class ChangeInfoSerializer(serializers.Serializer):
oldstage = serializers.CharField(max_length=100, source="old_stage") # Set max_length to a value that suits your needs
newstage = serializers.CharField(max_length=100, source="new_stage")
def create(self, validated_data):
pass
def update(self, instance, validated_data):
pass
class LogSerializer(serializers.ModelSerializer):
leadId = serializers.IntegerField(source="lead_id")
updateTime = serializers.IntegerField(source="update_time")
changeInfo = ChangeInfoSerializer(required=False) # Change to required=True if you want this field to be mandatory
class Meta:
model = Log
fields = ["leadId", "updateTime", "changeInfo"]
def create(self, validated_data):
change_info = validated_data.pop('changeInfo')
for key, value in change_info.items():
if key == "old_stage":
validated_data['old_stage'] = value
elif key == "new_stage":
validated_data['new_stage'] = value
log = Log.objects.create(**validated_data)
return log
def update(self, instance, validated_data):
change_info = validated_data.pop('changeInfo')
instance.lead_id = validated_data.get('leadId', instance.lead_id)
instance.update_time = validated_data.get('updateTime', instance.update_time)
# Here you can use change_info['oldstage'] and change_info['newstage'] if 'changeInfo' is sent (otherwise you'll get a KeyError)
instance.save()
return instance
As mentioned in the comments, a SerializerMethodfield is a good way to go:
serializers.py
class LogSerializer(...):
...
changeInfo = serializers.SerializerMethodField()
def get_changeInfo(self, obj): return {
"leadId" : obj.lead_id,
"updateTime": obj.update_time
}
class Meta:
fields = ["changeInfo", ...]
...
I have the following serializer :
class SalesProjectListSerializer(serializers.ModelSerializer):
permissions = serializers.SerializerMethodField('get_custompermissions',read_only=True)
class Meta:
model = SalesProject
fields = ['sales_project_id', 'sales_project_name',
'sales_project_est_rev', 'project_status','permissions']
depth = 2
def get_custompermissions(self, obj):
permission_list = ['add_salesproject']
user_perms = User.get_user_permissions(self.context['request'].user)
return { permission: True if permission in user_perms else False for permission in permission_list }
This serializer is used to serialize the data thats used to render the project listing page.
The serialized data would give me something like :
projects = [{sales_project_id : 1 , sales_project_name = 'test , ... ,permissions: [...]}]
However instead what i wish to return is somthing like this :
projects = {projects:[{sales_project_id : 1 , sales_project_name = 'test , ... }]
,permissions: [...]}
You cand override the method responsible for the response depending on your View type.
I assume you are using a ListAPIView so this is how you would do it:
class YourView(ListAPIView):
model = SalesProject
serializer_class = SalesProjectListSerializer
def list(self, request, *args, **kwargs):
serializer = self.get_serializer(self.get_queryset(), many=True)
# change the data
# serializer.data is the response that your serializer generates
res = {"projects": serializer.data}
return Response(res)
It's the same for other views such as RetrieveAPIView but you should override the retrieve method instead.
I wanna do something like below, but the code as it is inefficient,
How can I return paginated response of related object?
class Bar(models.Model):
pass
class Foo(models.Model):
bar = models.ForeignKey('bar')
foo_id = request.data.get('foo_id')
foos = Foo.objects.get(id=foo_id)
bars = [
foo.bar
in
foo
for
foos
]
page = self.paginate_queryset(bars)
serializer = BarSerializer(page, many=True)
return self.get_paginated_response(serializer.data)
Actually I found an answer here https://stackoverflow.com/a/37657376/433570
Idea is you flatten the nested object
class FooSerializer():
bar = BarSerializer()
def to_representation(self, obj):
"""Move fields from profile to user representation."""
representation = super().to_representation(obj)
profile_representation = representation.pop('bar')
for key in profile_representation:
representation[key] = profile_representation[key]
return representation
I want change a object.key to a another relevant value, like this:
class SomeViewSet(ModelViewSet):
queryset = Some.objects.all()
serialize_class = SomeSerializer
....
def get_queryset(self):
some_param = self.request.query_params.get("some_param", None)
if some_param:
for some in queryset:
some.fk_another_key = SomeAnother.objects.get(pk=some.fk_another_key.id)
return queryset
Whats I want is change the fk__pk to "fk object". I use rest-framework, and to some cases, not could declares a "depth" or "set object" in Serializer.
Currently my result json is this
{
...
keyX: "",
keyY: "",
some_another: 1
...
}
And I needs some like this
{
...
keyX: "",
keyY: "",
some_another: {
...
valueX: "",
valueY: ""
...
}
}
Thanks.
The proper way to do that is to modify your serializer so that the some_another foreign key is serialized using the corresponding "SomeAnother" serializer
class SomeAnotherSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = SomeAnother
fields = ('pk', 'valueX', 'valueY')
class SomeSerializer(serializers.HyperlinkedModelSerializer):
some_another = SomeAnotherSerializer()
class Meta:
model = Some
fields = ('pk', 'keyX', 'keyY', 'some_another ')
I'd like to filter my results to only show the first "Variation" class pointing to "Product" class.
I almost want it to work like this:
'product__variation__image__image'[0],
OR
'product_set__variation__image__image'[0],
I tried using:
.distinct('product_pk')
But this would not work if I was using .order_by()
The below code works, but each item is repeated several times as it has variations relating to it.
Here's a snippet of my Django rest views:
wardrobematch = {
'user': lambda x: ('user__pk', x)
}
class WardrobeListView(APIView):
renderer_classes = (JSONRenderer, )
def get(self, request, *args, **kwargs):
filters = {}
for key, value in request.GET.items():
key = key.lower()
if key in wardrobematch:
lookup, val = wardrobematch[key](value.lower())
filters[lookup] = val
qset = (
Analytic.objects
.filter(like=True,**filters)
# .distinct('product_id',)
.values('product_id', 'product__name', 'product__brand', 'product__store__store_name', 'product__variation__image__image', 'product__variation__price__price', 'updated',)
)
result = sorted(qset, key=lambda obj: obj.updated)
return Response(serializers.serialize("json", result))
When you specify field names, you must provide an order_by() in the QuerySet, and the fields in order_by() must start with the fields in distinct(), in the same order.
Taken from: Django's documentation.
You could sort it afterwards anyway.
qs = Analytic.objects.filter(like=True,**filters)
.distinct('product_pk',)
.values('product_pk', 'product__name', 'product__brand', 'product__store__store_name', 'product__variation__image__image', 'product__variation__price__price', 'updated')
result = sorted(qs, key=lambda obj: obj['updated'])
return Response(json.dumps({'result': result}))