Write only, read only fields in django rest framework - django

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:)

Related

Exclude history fields from django simples history on to_representation method in django rest framework

I'm using Django Rest Framework with django-simple-history and currently I would like to return the history modifications in my Board rest API, currently it is doing well but I would like to hide some fields. This is the currently output:
But, I don't need id, history_id, etc.
my implementation is the same of alexander answer in this post.
this is my currently serializers, where I put history on my Board model
class HistoricalRecordField(serializers.ListField):
child = serializers.DictField()
def to_representation(self, data):
representation = super().to_representation(data.values())
# i've tried to do it by deleting, but does't work well.
del representation[0]['history_id']
return representation
class BoardSerializer(serializers.ModelSerializer):
history = HistoricalRecordField(read_only=True)
class Meta:
model = Board
fields = '__all__'
But it does not seem the best way to do it.
If you have some hint about how to do it the correct way I would like to know.
Thanks in advance!
You can try this for history_id at least:
def to_representation(self, data):
representation = super().to_representation(data.values())
for hist in representation['history']:
hist.pop('history_id')
return representation
I don't know django-simple-history, so they may be better solutions than mine. However, you can do this with a more DRF-friendly approach by simply using a ModelSerializer instead of a ListSerializer:
class HistoricalRecordSerializer(serializers.ModelSerializer):
class Meta:
model = HistoricalRecords
fields = ('name', 'description', 'share_with_company', [...]) # Only keep the fields you want to display here
class BoardSerializer(serializers.ModelSerializer):
history = HistoricalRecordSerializer(read_only=True, many=True)
class Meta:
model = Board
fields = ('name', 'description', 'history', [...]) # Only keep the fields you want to display here
If you want to only retrieve the latest update, you could use a SerializerMethodField (documentation here). Remember to declare it in Meta.fields instead of 'history' (or rename your SerializerMethodField "history" if you want to keep this name):
class HistoricalRecordSerializer(serializers.ModelSerializer):
class Meta:
model = HistoricalRecords
fields = ('name', 'description', 'share_with_company', [...]) # Only keep the fields you want to display here
class BoardSerializer(serializers.ModelSerializer):
latest_history = serializers.SerializerMethodField()
def get_latest_history(self, instance):
latest_history = instance.history.most_recent() # Retrieve the record you want here
return HistoricalRecordSerializer(latest_history).data
class Meta:
model = Board
fields = ('name', 'description', 'latest_history', [...]) # Only keep the fields you want to display here
Keep in mind that I don't know much about this lib, so this should work but I cannot guarantee it's the best way to to it.

usage of standard backward relation manager in serilizers

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().

ModelSerializer required field data from instance

I have following serializer:
class TrackGroupSerializer(serializers.ModelSerializer):
class Meta:
model = TrackGroup
fields = ('id', 'name', 'report', 'tracks') # `report` is FK
I am taking report id from url, so I thougth that this will work:
...
track_group = TrackGroup(report=report)
serializer = TrackGroupSerializer(
instance=track_group,
context=dict(request=request),
data=request.data
)
if serializer.is_valid():
...
This doesn't work because serializer has error for field report as the field is missing and is required. What is a correct way to provide data like report and have it still listed in TrackGroupSerializer fields as this serializer is used to return all data in response.
Thanks
Set the required flag to False in model serializer
class TrackGroupSerializer(serializers.ModelSerializer):
report = serializers.CharField(required=False)
class Meta:
model = TrackGroup
fields = ('report', ...)
In case you want to create a serializer and save a model instance without providing value to a variable, you can always set a default value to it in the model.
In models.py
class TrackGroup(models.Model):
report = models.CharField(default = '-')
You can set data as a dict with all keys not as request.data like so
data = {'report': report.id, **request.data}
serializer = TrackGroupSerializer(
instance=track_group,
context=dict(request=request),
data=data
)
The correct solution seems to be partial=True:
serializer = TrackGroupSerializer(
instance=track_group,
context=dict(request=request),
data=request.data,
partial=True
)
It seems to be cleaner than modifying request data.

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

Django REST Framework: nested relationship, how to submit json?

I'm using Django 2.1, DRF 3.7.7.
I've some models and their relative (model) serializers: these models are nested, and so are the serializers.
Let me give an example:
# models.py
class City(models.Model):
name = models.CharField(max_length=50)
class Person(models.Model):
surname = models.CharField(max_length=30)
birth_place = models.ForeignKey(City)
# serializers.py
class CitySerializer(serializers.ModelSerializer):
class Meta:
model = models.CitySerializer
fields = "__all__"
class PersonSerializer(serializers.ModelSerializer):
birth_place = CitySerializer()
class Meta:
model = models.Person
fields = "__all__"
If I submit an AJAX request with a json like:
{'surname': 'smith', 'birth_place': 42}
I get back a Bad Request response, containing: Invalid data. Expected a dictionary, but got int.
If I submit a nested json like:
{'surname': 'smith', 'birth_place': {'id': 42, 'name': 'Toronto'}}
the relation is not converted, the id field is ignored and the rest is parsed to:
OrderedDict([('birth_place', OrderedDict([('name', 'Toronto')]))])
The following is the post method I'm using on a class-based view:
def post(self, request):
print("Original data:", request.data)
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
self.data = serializer.validated_data
print("Parsed data:", self.data)
...
I only need to get data from the endpoints connected to the serializers, I don't need to write/save anything through the REST interface, since the POST processing of the form is done by Django.
TL;DR: How should I correctly submit a JSON request to a nested serializer, without having to write handmade conversions? Did I commit errors in setting up the serializers?
Edit: I've discovered that by adding id = serializers.IntegerField() to the serializer parent class (e.g. City), the serializer parser now processes the id. At least now I'm able to perform actions in the backend with django.
Generic writing for nested serializers is not available by default. And there is a reason for that:
Consider, you are creating a person with a birthplace, using a POST request. It is not clear if the submitted city is a new one or an existing one. Should it return an error if there isn't such a city? Or should it be created?
This is why, if you want to handle this kind of relationship in your serializer, you need to write your own create() and update() methods of your serializer.
Here is the relevant part of the DRF docs: http://www.django-rest-framework.org/api-guide/relations/#writable-nested-serializers
It's definitely not clearly put into the docs of django-rest. If you follow the process of serializers processing the data for creation then it becomes clear that django manages m2m by saving the parent instance first and then adding the m2m values, but somehow the m2m fields don't go through the validation if you mark them as read_only.
The solution to this is to overr run_validation method of the serializer. The serializer should look like this:
class ExampleSerializer(serializers.ModelSerializer):
queryset = SomeModel.objects.all()
tags = TagSerializer(many=True, read_only=True)
class Meta:
model = SomeModel
fields = ['pk', 'name', 'tags']
def run_validation(self, data):
validated_data = super(StudyResourceSerializer, self).run_validation(data)
validated_data['tags'] = data['tags']
return validated_data
The request body should look like this:
{
"tags": [51, 54],
"name": "inheritance is a mess"
}