usage of standard backward relation manager in serilizers - django

Question is about using standard Django backward related manager name in DRF.
I have following serializer
class ExtraUserSerializer(serializers.ModelSerializer):
boatmodel_set = serializers.PrimaryKeyRelatedField(many=True,
queryset=BoatModel.objects.all())
class Meta:
model = get_user_model()
fields = ("id", "username", 'boatmodel_set', )
This serializer represents primary model ExtraUser and boat_model set represents backward relationship to secondary model BoatModel. Related name “boatmodel_set” chosen simply because main core Django site uses this standard “secondarymodel_set” conventional backward related manager name so that in DRF part I had to use related_name = “ boatmodel_set” as well in order to not change code in the main part.
Question is - is it possible to keep related_name = “ boatmodel_set” but represent it in rendered json as , for example “boats”??
Thank you

Yes, you can just specify the source= parameter [drf-doc], and name the field differently, like:
class ExtraUserSerializer(serializers.ModelSerializer):
boats = serializers.PrimaryKeyRelatedField(
many=True,
queryset=BoatModel.objects.all(),
source='boatmodel_set'
)
class Meta:
model = get_user_model()
fields = ('id', 'username', 'boats')
Here the JSON-side will contain "boats": ..., whereas the model side will still use myuser.boatmodel_set.all().

Related

Django equivalent of ASP.NET Parameter Binding or Ruby on Rails Action Controller Parameters

I'm wondering what's mentioned in the title. This are links to the examples mentioned, regarding other techs:
ASP.NET Parameter Binding
Ruby on Rails Action Controller
Parameters
Currently I'm building an API using DRF and using custom code in views or serializers validate methods to validate parameters, like this:
class AnimalWriteSerializer(serializers.ModelSerializer):
class Meta:
model = Animal
fields = '__all__'
def validate_dicose_category(self, value):
raise serializers.ValidationError('Dicose Category cannot be set manually.')
Is there a better way?
Since in your example you are telling the serializer to support __all__ fields, then you need to disable updating that one manually.
You probably mean to use use exclude as in the example below, which will simply remove the field from "all". The primary difference between exclude and using read_only is that the output will include the dicose_category.
Use the exclude= to exclude this field. This is the opposite of fields=, and you can only use one at a time.
class AnimalWriteSerializer(serializers.ModelSerializer):
dicose_category = serializers.CharField(read_only=True)
class Meta:
model = Animal
exclude = ["dicose_category"]
You can declare the field as read only (directly or using extra kwarg). You can't write it but it will include be in the output. I'm not sure why you would want to do this, but it can be helpful if you are using the return data for something and need it there.
class AnimalWriteSerializer(serializers.ModelSerializer):
dicose_category = serializers.CharField(read_only=True)
class Meta:
model = Animal
fields = "__all__"
# or declare an extra_kwarg which does the same thing:
class AnimalWriteSerializer(serializers.ModelSerializer):
class Meta:
model = Animal
fields = "__all__"
extra_kwargs = {
"dicose_category": { "read_only": True }
}
And lastly, I strongly suggest listing all the fields you intended to be updated directly, rather than using __all__ or exclude=.
New fields added to the model are not automatically updateable
All updateable fields are explicitly and clearly listed
Unit tests can now be explicit, and the output format is consistent
class AnimalWriteSerializer(serializers.ModelSerializer):
class Meta:
model = Animal
fields = [
"name",
"mission",
"favorite_color",
]

How to dynamically specify fields for nested serialization

When working with Django rest-framework, and object relationships, I often encounter situations in which I want to return different fields from a serializer depending on the context in which I'm calling that serializier. To provide an example, lets say I have some models representing artiste and album
class Artiste(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
name = models.CharField(max_length=30)
songs = models.ManyToManyField(Song, related_name='artiste')
albums = models.ManyToManyField(Album, related_name='artiste')
class Album(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
title = models.CharField(max_length=30)
description = models.TextField(blank=True, null=True)
These models being serialized by these serializers, which include field from the many to many relationship
class AlbumSerializer(serializers.ModelSerializer):
artiste = ArtisteSerializer(many=True)
class Meta:
model = Album
fields = ('id','title','description','artiste')
class ArtisteSerializer(serializers.ModelSerializer):
class Meta:
model = Artiste
fields = ('id', 'name', 'albums')
A request to my album endpoint might produce an object like this
"id": "5807bd12-254f-4508-b76f-02dc0740e809",
"title": "Test",
"description": "",
"artiste": [
{
"id": "31f09ef0-50ce-48b1-96a6-a6c234930ce5",
"name": "Alec Benjamin",
"albums": [
"5807bd12-254f-4508-b76f-02dc0740e809"
]
}
]
As you can see, using nested serialization ends up giving me more information that I actually want in this context. Given that I had more data for example, when looking at the information for an artiste, I'd love to have the information regarding all the albums that the artiste has made, but when looking at my album endpoint, I'd rather not have this information be included, especially here where the only album the artiste has made is the one I'm currently looking at anyways. As the system gets larger, this problem gets worse as i include songs, playlists etc
What I want to do, is to be able to specify what serialzier fields I want depending on the context in which I'm calling that serializer, as opposed to defining my serializer once and then always getting the same information even when its not needed.
my current approach to this is to use a SerializerMethodField and adding this to my album serializer
artiste = serializers.SerializerMethodField('get_artiste')
def get_artiste(self, obj):
return [(i.id ,i.name)for i in obj.artiste.all()]
Doing this allows me to explicitly state what I want to return in the context of my album serializer but this makes my artiste field read only, meaning that when trying to create an album, I cannot specify what artiste it belongs to. This issue also seems to be the case with StringRelatedField etc.
What is your approach to doing nested serialization? Is there any way to specify what fields should be returned from serialization in a specific situation without rewriting the serializer or making the field read only?
Sorry for the lengthy, probably stupid question and thanks for your help.
according to documentation serializers classes is very like regular django form classes so you only need to specify which fields you want that serializer to show and it will be done in your example it will be :
class ArtisteSerializer(serializers.ModelSerializer):
class Meta:
model = Artiste
fields = ('id', 'name')
and now this serializer will only show the related 'id' and 'name' of the given artiste model.
and there is much more that you can do here like special fields = '__all__'
which will show all fields of a given model or instead of using fields you can use exclude = ('album',) that just removes these fields from serializer .
also you can use django's models relationship here as well.
update :
to be able to use these serializers in different endpoints it is better to use python class inheritance create a base serializer and use different attributes on childs
class BaseArtisteSerializer(serializers.ModelSerializer):
class Meta:
model = Artiste
fields = ('id', 'name', 'artist')
class ArtisteSerializer(BaseArtisteSerializer):
class Meta(BaseArtisteSerializer.Meta):
fields = ('id', 'name')

Django REST ModelSerializer --- General Question

I am working through a tutorial that includes the building of an articles app. I have an Article model that I am serializing and I am curious about why I need to explicitly set certain fields when using a ModelSerializer.
Here is my model:
from django.db import models
from core.models import TimestampedModel
class Article(TimestampedModel):
slug = models.SlugField(db_index=True, max_length=255, unique=True)
title = models.CharField(db_index=True, max_length=255)
description = models.TextField()
body = models.TextField()
author = models.ForeignKey('profiles.Profile', on_delete=models.CASCADE, related_name='articles')
def __str__(self):
return self.title
Pretty standard stuff. Next step is to serialize the model data in my serializers.py file:
class ArticleSerializer(serializers.ModelSerializer):
author = ProfileSerializer(read_only=True) # Three fields from the Profile app
description = serializers.CharField(required=False)
slug = serializers.SlugField(required=False)
class Meta:
model = Article
fields = (
'author',
'body',
'createdAt',
'description',
'slug',
'title',
'updatedAt',
)
Specifically, why do I need to explicitly state the author, description, and slug fields if I am using serializers.ModelSerializer and pulling those fields in from my model in my class Meta: below?
Thanks!
In the Django-Rest-Framework documentation, drf-docs/model_serializer/specifying-which-fields-to-include it says:
If you only want a subset of the default fields to be used in a model serializer, you can do so using fields or exclude options, just as you would with a ModelForm. It is strongly recommended that you explicitly set all fields that should be serialized using the fields attribute. This will make it less likely to result in unintentionally exposing data when your models change.
Therefore by using fields = in the Serializer META, you can specify just the needed fields, and not returning vital fields like id, or exessive information like updated and created timestamps.
You can also instead of using fields, use exclude, which again takes in a tuple, but just excludes the fields you don't want.
These are especially useful when your database table contains a lot of information, returning all this information, especially if it is listed, can result in large return JSON's, where the frontend may only use a small percentage of the sent data.
DRF has designed their framework like this to specifically combat these problems.
In my opinion, we should define field in serializer for:
Your api use serializer don't need all data of your models. Then you can limit field can get by serializer. It faster if you have so much data.
You dont want public all field of your model. Example like id
Custom field in serializer like serializers.SerializerMethodField() must define in fields for work
Finally, iF you dont want, you can define serializer without define fields. Its will work normally

Write only, read only fields in django rest framework

I have models like this:
class ModelA(models.Model):
name = models.CharField()
class ModelB(models.Model):
f1 = models.CharField()
model_a = models.ForeignKey(ModelA)
Serializers:
class ASerializer(serializers.ModelSerializer):
model_b_ids = serializers.CharField()
class Meta:
model = ModelA
write_only_fields = ('model_b_ids',)
views:
class AView(CreateModelMixin, GenericViewSet):
def perform_create(self, serializer):
model_b_ids = parse_somehow(serializer.validated_data["model_b_ids"])
#do something...
The problem I am getting is the with the "model_b_ids"
The user should submit it while sending post data.
I use it in perform_create to link to related models.
But thats not "real column" in ModelA so when I try to save it is raising exception.
I tried to it pop from validated_data but then again getting error somewhere that cannot read model_b_ids from model. Any idea on using this kind of field correctly ?
The Django Rest Framework does not have Meta attribute write_only_fields anymore
According to their docs you set write-only fields in extra_kwargs
e.g
class UserSerializer(ModelSerializer):
"""
``Serializer`` for ``User`` ..
"""
class Meta:
model = User
fields = ('id', 'email', 'first_name', 'last_name' ,'security_question', 'security_question_answer', 'password', 'is_active', 'is_staff')
read_only_fields = ('is_active', 'is_staff')
extra_kwargs = {
'security_question': {'write_only': True},
'security_question_answer': {'write_only': True},
'password': {'write_only': True}
}
Update
As #AKHIL MATHEW highlighted in his answer below
From DRF v3 onwards, setting a field as read-only or write-only can use serializer field core arguments mentioned as follows.
write_only
Set this to True to ensure that the field may be used when updating or creating an instance, but is not included when serializing the representation.
Defaults to False
Eg:
company = serializers.PrimaryKeyRelatedField(write_only=True)
In accordance with the Django REST Framework documentation:
The write_only_fields option on ModelSerializer has been moved to PendingDeprecation and replaced with a more generic extra_kwargs
that's why it's recommended to do like this: you should use extra_kwargs:
extra_kwargs = {
'model_b_ids': {'write_only': True},
'another_field': {'read_only': True}
}
or:
model_b_ids = serializers.IntegerField(write_only=True)
Probably you're overseeing that your ModelA has the property modelb_set. In Django you describe the relationship in one model class. Django offers a backward relationship by lower-casing the target model and suffixing it with _set. So you could do:
a = ModelA.objects.get(pk=1)
a.modelb_set.all()
This would get the element with ID (or primary key) 1 from ModelA and retrieve all related ModelB elements.
You can set a value for related_name to overwrite the default value:
class ModelB(models.Model):
f1 = models.CharField()
model_a = models.ForeignKey(ModelA, related_name='model_b')
In DRF you can slightly adapt your serializer:
class ASerializer(serializers.ModelSerializer):
model_b = serializers.PrimaryKeyRelatedField(many=True, read_only=False)
class Meta:
model = ModelA
write_only_fields = ('model_b',)
With serializers.CharField() you can't post values and write them to the model, because it isn't a model field.
Give this example a try. Tinker and experiment. It should bring you closer to the solution.
EDIT:
I'm not really sure how Django creates the name for backward relationship for PascalCase class names. Is it model_b_set for ModelB? Or is it modelb_set? You can try and find it out.
From docs you can use read_only
Read-only fields are included in the API output, but should not be included in the input during create or update operations. Any 'read_only' fields that are incorrectly included in the serializer input will be ignored.
Set this to True to ensure that the field is used when serializing a representation, but is not used when creating or updating an instance during deserialization.
Defaults to False
As example:
We can use it on Serializer fields:
model_b_ids = serializers.IntegerField(read_only=True)
or we can use it in extra_kwargs:
extra_kwargs = {
'model_b_ids': {'read_only': True}
}
From DRF v3 onwards, setting a field as read-only or write-only can use serializer field core arguments mentioned as follows.
Core arguments
Each serializer field class constructor takes at least
these arguments. Some Field classes take additional, field-specific
arguments, but the following should always be accepted:
read_only
Read-only fields are included in the API output but should
not be included in the input during create or update operations. Any
'read_only' fields that are incorrectly included in the serializer
input will be ignored.
Set this to True to ensure that the field is used when serializing a
representation, but is not used when creating or updating an instance
during deserialization.
Defaults to False
Eg:
price = serializers.IntegerField(read_only=True)
write_only
Set this to True to ensure that the field may be used when
updating or creating an instance, but is not included when serializing
the representation.
Defaults to False
Eg:
company = serializers.PrimaryKeyRelatedField(write_only=True)
Well you could override the serializer.save() method on ASerializer to instantiate modelA object, set its attributes, save it, then set relations on existing modelB objects, save them as well and drink to success.
But I think maybe setting that related_name and RelatedField on serializer as was suggested would do exactly the same thing.... with less typing.. and overall better:)

How to choose specific columns when using depth in serializer

I have a model which consists of two ForeignKeys. I am only interested in parsing the content of the ForeignKeys, so i'm using the depth variable, which basically gives me all columns of the tables referenced with the FK. Is there a way to select which columns there should be included?
class SomeSerializer(serializers.ModelSerializer):
class Meta:
model = MyAwesomeModel
fields = ('id', 'fk_one','fk_two')
depth = 1
Try using nested serializers. Documentation here.
Example:
class FKOneSerializer(serializers.ModelSerializer):
class Meta:
model = FKOne
fields = ('name', 'id')
class SomeSerializer(serializers.ModelSerializer):
fk_one = FKOneSerializer()
class Meta:
model = MyAwesomeModel
fields = ('id', 'fk_one','fk_two')
EDIT:
Similar answer here by the creator of the Django Rest Framework. Also includes some related notes, including that nested serializers are read-only and that you may need to include a source argument on the serializer field.