I'm using Django REST Framework and I'm fairly newbie to this thing.
I want to have string representation for the manytomanyfield and foreignkey fields in my JSON output rather than the value.
models.py
class Movie(models.Model):
"""Movie objects"""
name = models.CharField(max_length=128)
directorName = models.ForeignKey(Director)
genre = models.ManyToManyField(Genre)
serializers.py
class MovieSerializer(serializers.ModelSerializer):
"""
Serialiazing all the Movies.
"""
genre = serializers.PrimaryKeyRelatedField(many=True, queryset=Genre.objects.all())
directorName = serializers.PrimaryKeyRelatedField(queryset=Director.objects.all())
owner = serializers.ReadOnlyField(source='owner.username')
class Meta:
model = Movie
fields = ('popularity',"directorName",'genre','imdbScore','name','owner')
JSON output
{"popularity":"90.0","directorName":1,"genre":[1,2,3],"imdbScore":"8.9","name":"Titanic"}
instead of directorName's and genre's display_name, I'm getting only the values.
Please suggest me how to correct this.
EDIT
[SOLVED]
You need to override the to_representation() method of PrimaryKeyRelatedField as it returns the pk.
To do that, you need to override the to_representation() method of PrimaryKeyRelatedField as it returns the pk.
You can create a MyPrimaryKeyRelatedField which inherits from PrimaryKeyRelatedField and then override its to_representation() method.
Instead of value.pk which PrimaryKeyRelatedField returned, return the string representation now. I have used six.text_type() instead of str() to handle both the Python 2(unicode) and Python 3(str) versions.
from django.utils import six
from rest_framework import serializers
class MyPrimaryKeyRelatedField(serializers.PrimaryKeyRelatedField):
def to_representation(self, value):
return six.text_type(value) # returns the string(Python3)/ unicode(Python2) representation now instead of pk
Your serializers.py would then look like:
class MovieSerializer(serializers.ModelSerializer):
"""
Serialiazing all the Movies.
"""
genre = MyPrimaryKeyRelatedField(many=True, queryset=Genre.objects.all())
directorName = MyPrimaryKeyRelatedField(queryset=Director.objects.all())
owner = serializers.ReadOnlyField(source='owner.username')
class Meta:
model = Movie
fields = ('popularity',"directorName",'genre','imdbScore','name','owner')
The simplest is probably to use StringRelatedField
class MovieSerializer(serializers.ModelSerializer):
directorName = serializers.StringRelatedField(many=True)
class Director(Model):
# [...]
def __unicode__(self):
return self.directorName
However, that does not work when you need different representations of the Director model. In that case you need to go with a custom serializer (see answer from Rahul Gupta).
Related
I need to have extra fields in response if they are available, but not all objects of that class have this property. So for example we have
class Car(models.Model):
brand = model.CharField()
wheelcount = model.IntField()
class Truck(Car):
max_load = model.IntField()
class Bus(Car):
max_people = model.IntField()
and a view
class CarView(ReadOnlyModelViewSet):
serializer_class = CarSerializer
queryset = Car.objects.all()
And I want to have max_load and max_people when i check all available cars.
Is there a way to either write CarSerializer to somehow serialize child objects differently, or a way to make view class choose a serializer based on class or additional field(like having an enum CarType)?
You can specify which fields you'd like to serialize normally in the meta class.
ex:
class CarSerializer(serializers.ModelSerializer):
class Meta:
model = Car
fields = [<whatever specific fields you want to serialize>]
if you need to serialize objects based on certain conditions you can use the SerializerMethodField. https://www.django-rest-framework.org/api-guide/fields/#serializermethodfield
From django rest documentation:
This is a read-only field. It gets its value by calling a method on
the serializer class it is attached to. It can be used to add any sort
of data to the serialized representation of your object.
Signature: SerializerMethodField(method_name=None)
method_name - The name of the method on the serializer to be called.
If not included this defaults to get_<field_name>. The serializer
method referred to by the method_name argument should accept a single
argument (in addition to self), which is the object being serialized.
It should return whatever you want to be included in the serialized
representation of the object. For example:
from django.contrib.auth.models import User
from django.utils.timezone import now
from rest_framework import serializers
class UserSerializer(serializers.ModelSerializer):
days_since_joined = serializers.SerializerMethodField()
class Meta:
model = User
fields = '__all__'
def get_days_since_joined(self, obj):
return (now() - obj.date_joined).days
in your case:
class CarSerializer(serializers.ModelSerializer):
object = serializers.SerializerMethodField()
def get_object(self, instance):
if instance.CarType:
return <your desired object>
I have 2 models that look like this:
models.py
class Client(models.Model):
deal = models.ManyToManyField('Deal', related_name="clients")
class Deal(models.Model):
client = models.ManyToManyField(Client, related_name="deals")
Then in the admin, I have inlined the related models to make it easy to make changes regardless of the object type you have open.
admin.py
class ClientInline(admin.TabularInline):
model = Deal.client.through
class DealAdmin(admin.ModelAdmin):
inlines = [ClientInline]
class DealInline(admin.TabularInline):
model = Client.deal.through
class ClientAdmin(admin.ModelAdmin):
inlines = [DealInline]
However, if you add a Client to a Deal and then open the Client detail page, the corresponding deal does not appear. Is there something I'm not connecting?
It is enough to have relation define only in one model. Otherwise you'll have 2 separate tables for separate ManyToMany relation: ClientDeal and DealClient.
What you need to do is to choose which one you need to leave. And probably update Admin inlines according to Django Admin documentation
class Client(models.Model):
deals = models.ManyToManyField('Deal', related_name="clients")
class Deal(models.Model):
pass
Yes, If you're using models.manytoMany() , you have to put it only in one model. no the two
But there's a very good attribute you should use: through
with through attribute you can create a intermediate model. here there's an example:
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=200)
groups = models.ManyToManyField('Group', through='GroupMember', related_name='people')
class Meta:
ordering = ['name']
def __unicode__(self):
return self.name
class Group(models.Model):
name = models.CharField(max_length=200)
class Meta:
ordering = ['name']
def __unicode__(self):
return self.name
class GroupMember(models.Model):
person = models.ForeignKey(Person, related_name='membership')
group = models.ForeignKey(Group, related_name='membership')
type = models.CharField(max_length=100)
def __unicode__(self):
return "%s is in group %s (as %s)" % (self.person, self.group, self.type))
later, you can use your inline admin class!
I just tested this an you were actually really close.
First, #wowkin2 said, you don't want to define a ManyToManyField in both models so I would probably just define it in your Deal model.
Second, replace this:
class DealInline(admin.TabularInline):
model = Client.deal.through
with this:
class DealInline(admin.TabularInline):
model = Deal.client.through
And everything should work.
So, this is what your files should now look like:
models.py
class Deal(models.Model):
client = models.ManyToManyField(Client, related_name="deals")
admin.py
class ClientInline(admin.TabularInline):
model = Deal.client.through
class DealAdmin(admin.ModelAdmin):
inlines = [ClientInline]
class DealInline(admin.TabularInline):
model = Deal.client.through
class ClientAdmin(admin.ModelAdmin):
inlines = [DealInline]
I am trying to post to my API with foreign key relationships. It's throwing me back an error saying it's expecting a dictionary as opposed to int for character, character_opponent and stage. This is because the way my models are set up. They have foreign key relationships. The model in question looks like this:
import uuid
from django.db import models
from django.utils import timezone
from analysis.models import Analysis
from characters.models import Character
from stages.models import Stage
class Match(models.Model):
analysis = models.ForeignKey(Analysis, on_delete=models.CASCADE)
character = models.ForeignKey(Character, on_delete=models.CASCADE, related_name='character')
character_won = models.BooleanField()
character_opponent = models.ForeignKey(Character, on_delete=models.CASCADE, related_name='character_opponent')
character_opponent_won = models.BooleanField()
created_at = models.DateTimeField(editable=False)
id = models.UUIDField(primary_key=True, default=uuid.uuid4)
updated_at = models.DateTimeField(editable=False)
stage = models.ForeignKey(Stage, on_delete=models.CASCADE)
def __str__(self):
return '%s vs. %s on %s' % (self.character, self.character_opponent, self.stage)
def save(self, *args, **kwargs):
''' On save, update timestamps '''
if not self.created_at:
self.created_at = timezone.now()
self.updated_at = timezone.now()
return super(Match, self).save(*args, **kwargs)
class Meta:
db_table = "matches"
And here is my serializer:
from rest_framework import serializers
from matches.models import Match
from characters.serializers import CharacterSerializer
from stages.serializers import StageSerializer
class MatchSerializer(serializers.ModelSerializer):
character = CharacterSerializer()
character_opponent = CharacterSerializer()
stage = StageSerializer()
class Meta:
model = Match
fields = ('id', 'analysis', 'character', 'character_won', 'character_opponent', 'character_opponent_won', 'stage')
Is there some option I am missing here to be able to post properly? Clearly I shouldn't have to pass the entire character object each time I want to post something, right? I should just be able to pass the primary key.
From your few comments I understood that you need nested serializer in GET method. What I suggest is, use two[or more] serializers for your API class.
Assuming you are using ModelViewSet API class is using,then you could override get_serializer_class() method as below,
from rest_framework.viewsets import ModelViewSet
class MatchAPI(ModelViewSet):
queryset = Match.objects.all()
def get_serializer_class(self):
if self.action == 'create':
return MatchCreateSerializer
return MatchSerializer
And your MatchCreateSerializer will be like this,
class MatchCreateSerializer(serializers.ModelSerializer):
class Meta:
fields = '__all__'
model = Match
Thus, you only need to provide the PKs of analysis,character etc while creation of Match instance
It will come down to your CharacterSerializer and StageSerializer. If you want to input 1 format (using serialisers.PrimaryKeyRelatedField()), but output another (CharacterSerializer, StageSerializer), you might be best served using 2 serialisers and switching in your view.
In your view you can override get_serializer_class and check your request method, or in the case of a viewset you can check the method being invoked.
When you declare a serializer related field using another serializer, like this
character = CharacterSerializer()
you are telling django-rest-framework that you want a nested serializer. What you want is something like this instead
character = serializers.PrimaryKeyRelatedField()
or you can actually just leave the explicit field declaration out of the serializer (since this is the default), see the doc on serializer relations.
In short, I want to have a global default serializer per model. My use case here is to create dynamic serializer- i.e creating ModelSerializer classes on the fly.
class Customer(models.Model):
name = models.CharField(max_length=200)
code = models.CharField(max_length=200)
# many more fields..
class CustomerTicket(models.Model):
customer = models.ForeignKey(Customer)
date = models.DateTimeField(auto_now_add=True)
# more fields..
Customer will be referenced by many other models, and hence it will be serialized as a nested object. I don't want the 'code' field to appear in the output - no matter what it should always be excluded.
Now I'd like to create a function:
def serialize_default(model, fields, queryset):
class S(serializers.ModelSerializer):
class Meta:
model = model
fields = fields
depth = 1
return S(queryset, many=True)
if I serialize CustomerTicket queryset using this function, I will get all the customer fields as a nested object. I know I can override it locally, but I want to define a CustomerSerializer that will be used by default (for the nested Customer here) unless other serializer is specified as a field. How to achieve this?
Would something like that work for you?
class DefaultCustomerSerializer(serializers.ModelSerializer):
# whatever fields you want
class DefaultCustomerSerializerModel(serializers.ModelSerializer):
customer = DefaultCustomerSerializer()
# You can inherit from this to have default customer serializer
# on serializers you want.
class CustomerTicketSerializer(DefaultCustomerSerializerModel):
# Other fields
I'm trying to use Django Rest to return a json representation of a model based on a ordering from a custom field that is not attached to the model, but is attached to the serializer. I know how to do this with model specific fields, but how do you use django rest to return an ordering when the field is only within the serializer class? I want to return a list of Pics ordered by 'score'. Thanks!
------Views.py
class PicList(generics.ListAPIView):
queryset = Pic.objects.all()
serializer_class = PicSerializerBasic
filter_backends = (filters.OrderingFilter,)
ordering = ('score')
------Serializer.py
class PicSerializer(serializers.ModelSerializer):
userprofile = serializers.StringRelatedField()
score = serializers.SerializerMethodField()
class Meta:
model = Pic
fields = ('title', 'description', 'image', 'userprofile', 'score')
order_by = (('title',))
def get_score(self, obj):
return Rating.objects.filter(picc=obj).aggregate(Avg('score'))['score__avg']
You could move the logic of the method get_score to the manager of the class Pic. In this answer there is an example of how to do it.
Once you have it in the manager, the score field would become "magically" available for every object of the class Pic everywhere (serializer, views...) and you'll be able to use it for ordering.