I use Django REST and I would know if it is possible to customise the display of attributes in the json response.
Exemple :
class MyModel(models.Model):
name = models.CharField(max_length=300)
and my serializer :
class MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = ['name']
But instead to see {'name' : 'its value'}, I would see {'My customed model name' : 'its value'}.
Do you think that it's possible?
Thank you very much.
You can override the to_representation method of the serializer to change the name of the field:
class MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = ['name']
def to_representation(self, instance):
ret = super().to_representation(instance)
# ret is an OrderedDict, so this will change the order of the result.
ret['custom_name'] = ret.pop('name')
return ret
def to_internal_value(self, data):
# if you want to write to the serializer using your custom name.
data['name'] = data.pop('custom_name')
return super().to_internal_value(data)
One way you could do it is using a SerializerMethodField (https://www.django-rest-framework.org/api-guide/fields/#serializermethodfield)
class MyModelSerializer(serializers.ModelSerializer):
my_customed_model_name = serializers.SerializerMethodField()
def get_my_customed_model_name(self, obj):
return obj.name
class Meta:
model = MyModel
Although if you want the field name to have spaces in it, this solution won't work for you.
You can do this in this way
class MyModelSerializer(serializers.ModelSerializer):
other_name = serializers.CharField(source='name')
class Meta:
model = MyModel
fields = ['other_name']
Related
DRF v3.12.4
class AModel(models.Model):
field_0 = models.CharField()
field_1 = models.FloatField()
field_2 = models.FloatField()
Have such a model, and I need to serialize it (read-only) in such way:
{ "field_0": "FIELD0", "arr": [ 1.0, 2.0 ]}
For now, I make the next solution:
class BSerializer(serializer.Serializer):
def to_representation(self, data):
return [data.field_1, data.field_2]
class ASerilizer(serializers.Serializer):
arr = BSerializer(source='*')
class Meta:
model = AModel
fields = ['field_0', 'arr']
It's work, but I also use drf-spectacular and of course, it can't analyze the overriding method. I'm trying #extend_schema_serializer on BSerilizer, but it does not work.
Is it possible without overriding the to_representation method?
So, the question is it possible for such a case to use a serializer without overriding the to_representation method, ListSerializer for example? Or maybe someone knows why #extend_schema_serializer does not work?
Thx
If you want just use arr field as a read_only field, you can use serializer's SerializerMethodField:-
from rest_framework import serializers
class ASerilizer(serializers.Serializer):
arr = serializers.SerializerMethodField()
class Meta:
model = AModel
fields = ['field_0', 'arr']
def get_arr(self, instance):
return [instance.field_1, instance.field_2]
For more information you can refer from here https://www.django-rest-framework.org/api-guide/fields/#serializermethodfield
Let's say I have the following:
class EntityManager(Manager):
def create(label, entity_type, **kwargs):
... do stuff with label and entity type
obj = super().create(**cleanedupkwargs)
obj.addstuffwithlabel(label)
return obj
class Entity(Model):
somefields...
objects = EntityManager()
There's no problem with this and I can call Entity.objects.create(label='foo', entity_type=my_entity_type, other_params=foo)
the issue is I'm now using a serializer and I tried this
class EntityBareboneSerializer(serializers.ModelSerializer):
label = serializers.SerializerMethodField()
entity_type = serializers.SerializerMethodField()
class Meta:
model = Entity
fields = [
'id',
'label',
'entity_type',
]
def validate_label(self, label):
return label
def validate_entity_type(self, entity_type):
return entity_type
def create(self, validated_data):
# do stuff with label and entity type
return Entity.objects.create(**validated_data)
The issue is when is_valid is called the validated_data param comes back empty.
Any idea if it's possible to effectively use my custom create method in the serializer?
You can pre-process the validated data, before creating an instance
def create(self, validated_data):
label = validated_data.pop("label", "some_default_value")
entity_type = validated_data.pop("entity_type", "some_default_value")
obj = Entity.objects.create(**validated_data)
obj.addstuffwithlabel(label)
return obj
I have two models, a parent and it's items
class Parent(models.Model):
#fields
class ParentItems(models.Model):
parentId = #parentId
#fields
and want to create method POST parent/ with ParentItems included, like
{
"parentField": "value",
"parentItems": [
{
"parentItemsField": "value"
}
]
}
So I create Parent view using ModelViewSet and then the serializer:
class ParentItemSerializer(serializers.ModelSerializer):
#serializer
class ParentSerializer(serializers.ModelSerializer):
parentItems = ParentItemSerializer(many=True, required=True, write_only=True)
def _createParentItems(self, parentItem, parent):
for item in parentItem:
item['parent'] = parent.id
parentItemSerializer = ParentItemSerializer(data=item)
if parentItemSerializer.is_valid(raise_exception=True):
itemSerializer.save()
def create(self, validated_data):
with transaction.atomic():
parentItems = validated_data.pop('parentItems')
createdData = super(ParentSerializer, self).create(validated_data)
# create the parent item
self._createParentItems(parentItems, createdData)
return createdData
I add parentItems field in ParentSerializer, inside the create method I pop the parentItems and pass it into _createParentItems to create the items.
but this doesn't work, the parentItems is already an object. when I call parentItemSerializer.is_valid(raise_exception=True) it errors.
How do I save an object from another serializer in django rest?
should I use another serializer field?
Tried using OrderedDict, it doesn't work, it didn't pass the items to validated_data
First change your model like the following code. Then you can use serializer as I wrote for you.
class Parent(models.Model):
#fields
class ParentItem(models.Model):
parent = models.ForeignKey(Parent, on_delete=models.CASCADE)
# other fields
class ParentItemSerializer(ModelSerializer):
class Meta:
model = ParentItem
fields = ('field1', ...)
def create(self, validated_data):
ParentItem.objects.create(field1=validated_data['field2'],...)
return validated_data
class Parent(ModelSerializer):
parentItems = ParentIremSerializer(many=True)
class Meta:
model=Parent
fields = ('parentItems', 'other fields',...)
def create(self, validated_data):
parent = Parent('field1'=validated_data['field1'])
parent.save()
parentItemsList = validate_data.pop('parentItems')
parentItemSer = ParentItemSerialzer(data=parentItemsList,many=True)
if parentItemSer.is_valid():
parentItemSer.save()
return validated_data
How can I pass an argument to a serializers.RelatedField class from views.py. I need to pass language_id to query Language.objects model within that RelatedField.
I am not sure if I took a right approach to this issue. What I want to achieve is to present information about genres associated to a movie from database model about depending on the language. The MovieGenre model has genre ID field which I want to replace with actual Genre name.
My serialiser.py
class GenreField(serializers.RelatedField):
def to_representation(self, value, language_id=1):
genre_name = GenresVideo.objects.get(genre_id=value, language_id=language_id)
return genre_name.name
class MovieGenresSerializer(serializers.ModelSerializer):
genre_id = GenreField(read_only=True)
class Meta:
model = MoviesGenres
As you see, here I query Language.objects with default value but I would like to pass it from views (language_id).
My views.py:
class MovieGenresTestViewSet(viewsets.ModelViewSet):
lookup_field = 'movie'
queryset = MoviesGenres.objects.all()
serializer_class = MovieGenresSerializer
def list(self, request, language_pk):
queryset = MoviesGenres.objects.all()
serializer = MovieGenresSerializer(queryset, many=True)
return Response(serializer.data)
def retrieve(self, request, movie, language_pk):
queryset = MoviesGenres.objects.filter(movie=movie)
serializer = MovieGenresSerializer(queryset, many=True)
return Response(serializer.data)
And my urls.py:
router.register(r'lang', LanguagesViewSet, base_name='lang')
mov_gen = routers.NestedSimpleRouter(router, r'lang', lookup='language')
mov_gen.register(r'mg', MovieGenresTestViewSet, base_name='mg')
url(r'^api/', include(genre_spec.urls))
My models.py
class Languages(models.Model):
name = models.CharField(unique=True, max_length=255)
short_name = models.CharField(unique=True, max_length=4, blank=True, null=True)
active = models.BooleanField(default="")
class Meta:
managed = False
db_table = 'languages'
ordering = ('id',)
class GenresVideo(models.Model):
genre_id = models.IntegerField()
language = models.ForeignKey('Languages')
name = models.CharField(max_length=255, blank=True, null=True)
class Meta:
managed = False
db_table = 'genres_video'
unique_together = (('genre_id', 'language'),)
ordering = ('genre_id',)
class MoviesGenres(models.Model):
movie = models.ForeignKey(Movies)
genre_id = models.IntegerField()
class Meta:
managed = False
db_table = 'movies_genres'
unique_together = (('movie', 'genre_id'),)
Through the urls routes, I can get a correct response from API including the language_id. I just need to pass it to the view somehow.
Thanks a lot for help!
I'll try to answer to your first question, with the easiest implementation possible: SerializerMethodField. Because we will get the language id via the context passed to the serializer, we should either generate the context for the serializer, or let the framework do that for us.
Now to the problem at hand: you aren't filtering the queryset (MoviesGenres) by language per se. Thus, we can avoid overwriting the list and retrieve methods. Nevertheless, the router mechanism will inject in kwargs for the view method the language_pk parameter - that's the parameter that we will retrieve from within the serializer context:
class MovieGenresSerializer(serializers.ModelSerializer):
genre = searializers.SerializerMethodField()
class Meta:
model = MoviesGenres
def get_genre(self, instance):
# get the language id from the view kwargs
language_id = self.context['view'].kwargs['language_pk']
# get the genre
try:
genre_name = GenresVideo.objects.get(genre_id=instance.genre_id, language_id=language_id).name
except GenresVideo.DoesNotExist:
genre_name = None
# return the formatted output
return genre_name
class MovieGenresTestViewSet(viewsets.ModelViewSet):
lookup_field = 'movie'
queryset = MoviesGenres.objects.all()
serializer_class = MovieGenresSerializer
I'm trying to serialize a model containing a property field that I also want to serialize.
models.py:
class MyModel(models.Model):
name = models.CharField(max_length=100)
slug = models.AutoSlugField(populate_from='name')
#property
def ext_link(self):
return "/".join([settings.EXT_BASE_URL, self.slug])
serializers.py:
class MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = ('name', 'ext_link')
When trying to get to the related URL, I'm getting a serializer exception (KeyError) on the ext_link property.
How can I serialize the ext_link property?
Because it's not a model field, it needs to be added explicitly to the serializer class
class MyModelSerializer(serializers.ModelSerializer):
ext_link = serializers.Field()
class Meta:
model = MyModel
fields = ('name', 'ext_link')
as #Robert Townley's comment, this work with version 3.8.2:
class MyModelSerializer(serializers.ModelSerializer):
ext_link = serializers.ReadOnlyField()
class Meta:
model = MyModel
fields = "__all__"
The accepted answer doesn't seem to work for me, nor does the ReadOnlyField.
However, I have had success when I use a field that corresponds to the return type of my property function.
So for the example, I would do this:
class MyModelSerializer(serializers.ModelSerializer):
ext_link = serializers.CharField()
class Meta:
model = MyModel
fields = ('name', 'ext_link')
I've been able to do this with ListField, DictField, and IntegerField as well.
Another thing you might want to do is add a property that its contents are not a string. Let's say you have a model called Person and another one called Food that look like this (we assume that each food is the favorite of only one person, making it a OneToMany connection):
class Person(models.Model):
name = models.CharField(max_length=255)
#property
def favorite_foods(self):
return Food.objects.filter(person=self.pk)
class Food(models.Model):
name = models.CharField(max_length=255)
persons_favorite = models.ForeignKey(Person, on_delete=models.CASCADE)
If you want to add favorite_foods in Person's serializer all you have to do is:
class PersonSerializer(serializers.ModelSerializer):
favorite_foods = FoodSerializer(read_only=True, many=True)
class Meta:
model = Person
fields = ('name', 'favorite_foods')