i have a somewhat tangled problem, I have the following models
class ModelA(models.Model):
name = models.CharField(max_length=64)
class ModelB(models.Model):
model_a = models.ForeignKey(ModelA, on_delete=models.CASCADE)
and the serializers
class ModelASerializer(serializers.ModelSerializer):
class Meta:
model = ModelA
fields = '__all__'
class ModelBSerializer(serializers.ModelSerializer):
terminal = ModelASerializer(read_only=True)
class Meta:
model = ModelB
fields = '__all__'
when I want to create a ModelB object including the model_a_id it has 2 responses depending on whether I put the read_only=True or False
I would like that when I try to create via POST in an object of ModelB I only have to send the mode_a_id, but at the same time when I do a GET it returns the serialized data of ModelASerializer in the model_a field.
If I put the read_only = True it only works to receive the serialized data in GET, but via POST I cannot assign an id to model_a
in case of read_only=False, I can't assign the id to that object either since it asks me for a dictionary, I imagine to create a new ModelA Object, but I only want to assign one that already exists.
A solution may be to place the serializer with another variable name, but I would really like it to be the same for both POST and GET.
EDIT #1: examples of the json expected
make a GET for a simple view of ModelB object
{
"model_a" : {
"id": 1,
"name" : "foo"
}
}
and a POST like:
{
"model_a" : 1
}
that retrive of POST the same json as a GET:
{
"model_a" : {
"id": 1,
"name" : "foo"
}
}
Just override to_representation() method of your ModelBSerializer like the following.
class ModelASerializer(serializers.ModelSerializer):
class Meta:
model = ModelA
fields = '__all__'
class ModelBSerializer(serializers.ModelSerializer):
terminal = ModelASerializer(read_only=True)
class Meta:
model = ModelB
fields = '__all__'
def to_representation(self, instance):
pre_represented_data = super().to_representation(instance)
pre_represented_data['model_a'] = ModelASerializer(instance.model_a).data
return pre_represented_data
And if you seriously don't want the id of ModelB in the GET call then just delete the key from pre_represented_data like following.
class ModelASerializer(serializers.ModelSerializer):
class Meta:
model = ModelA
fields = '__all__'
class ModelBSerializer(serializers.ModelSerializer):
terminal = ModelASerializer(read_only=True)
class Meta:
model = ModelB
fields = '__all__'
def to_representation(self, instance):
pre_represented_data = super().to_representation(instance)
pre_represented_data['model_a'] = ModelASerializer(instance.model_a).data
del pre_represented_data['id'] # deleting id of ModelB
return pre_represented_data
Related
I have two serializers, one for Country and one for my model Foo, I want to save the object by using the foreign key for this model but it errors out whenever I try to validate.
I have this
class Actor(TLPWrappedModel, CommentableModel):
label = models.CharField(max_length=56, unique=True)
country_of_origin = models.ForeignKey(Country, on_delete=models.CASCADE)
class FooSerializer(serializers.ModelSerializer):
country_of_origin = CountrySerializer()
class Meta:
model = Actor
fields = [
'id',
'country_of_origin',
'label',
]
class Country(models.Model):
label = models.CharField(max_length=56, unique=True)
iso_code = models.CharField(max_length=3, unique=True)
class CountrySerializer(serializers.ModelSerializer):
class Meta:
model = Country
fields = [
'iso_code',
'label',
]
And this is what I'm trying to do
serializers = FooSerializer(data={'label': 'Foobar',
'country_of_origin': self.country.id})
serializers.is_valid()
print(serializers.errors)
print(serializers.validated_data)
serializers.save()
But I get this error {'country_of_origin': {'non_field_errors': [ErrorDetail(string='Invalid data. Expected a dictionary, but got int.', code='invalid')]}}
is it possible to use the ID of a foreign key to validate and create the object using the serializer?
We can update the to_represent of the FooSerializer to get the desired output
Try
class FooSerializer(serializers.ModelSerializer):
class Meta:
model = Actor
fields = [
'id',
'country_of_origin',
'label',
]
def to_representation(self, instance):
data = super().to_representation(instance)
data['country_of_origin'] = CountrySerializer(instance.country_of_origin)
return data
serializers = FooSerializer(data={'label': 'Foobar', 'country_of_origin': self.country})
serializers.is_valid(raise_expection=True)
serializers.save()
In this I have updated the code to assign the self.country as country_of_origin. Also, I am using the raise_expection in the is_valid method. This method will return the errors as 400 response.
Try
class FooSerializer(serializers.ModelSerializer):
class Meta:
model = Actor
fields = [
'id',
'country_of_origin',
'label',
]
You can safely drop defining the 'country of origin` in the FooSerializer
contry_of_origin would be an object, and you are passing an id for it.
Do you need a nested serializer? : country_of_origin = CountrySerializer()
For the example that you have given, I would suggest you to change it to PrimaryKeyRelatedField()
Your serializer would look like:
class FooSerializer(serializers.ModelSerializer):
country_of_origin = serializers.PrimaryKeyRelatedField()
class Meta:
model = Actor
fields = [
'id',
'country_of_origin',
'label',
]
Trying out serialising parent and child model.Here are my models:
class HealthQuotation(models.Model):
quotation_no = models.CharField(max_length=50)
insuredpersons = models.IntegerField()
mobile_no = models.CharField(max_length=10)
def __str__(self):
return self.quotation_no
class HealthQuotationMember(models.Model):
premium = models.FloatField(null=True)
suminsured = models.FloatField()
quotation = models.ForeignKey(HealthQuotation,on_delete=models.CASCADE)
def __str__(self):
return str(self.quotation)
Here are my serializers:
class HealthQuotationMemberSerializer(serializers.ModelSerializer):
class Meta:
model = HealthQuotationMember
fields= "__all__"
class HealthQuotationSerializer(serializers.ModelSerializer):
members = HealthQuotationMemberSerializer(many=True)
class Meta:
model = HealthQuotation
fields = ['id','members']
On Serialising parent model with parent serializer, Django throws error "Got AttributeError when attempting to get a value for field members on serializer HealthQuotationSerializer. The serializer field might be named incorrectly and not match any attribute or key on the HealthQuotation instance. Original exception text was: 'HealthQuotation' object has no attribute".
Because you don't have members field in your model... Try to change your serializer as following and see if it works:
class HealthQuotationSerializer(serializers.ModelSerializer):
quotation = HealthQuotationMemberSerializer()
class Meta:
model = HealthQuotation
fields = ['id','quotation']
Note that I've removed many=True because there will be always one object per this data (ForeignKey). when you have more than one object such as Many2Many you should use many=True.
You have "HealthQuotation" as a parent and "HealthQuotationMember" as a child.
Now, you have decided to retrieve data from parent "HealthQuotation"
and its associated children which will come from "HealthQuotationMember", right?
To achieve that you can use Django SerializerMethodField():
Your serializers.py should look like:
class HealthQuotationMemberSerializer(serializers.ModelSerializer):
class Meta:
model = HealthQuotationMember
fields= '__all__'
class HealthQuotationSerializer(serializers.ModelSerializer):
members = serializers.SerializerMethodField() # I am using SerializerMethodField()
class Meta:
model = HealthQuotation
fields = '__all__'
def get_members(self, quotation):
q = HealthQuotationMember.objects.filter(quotation = quotation)
serializer = HealthQuotationMemberSerializer(q, many=True)
return serializer.data
Your views.py
class GetHealthQuotationList(ListAPIView):
serializer_class = HealthQuotationSerializer
queryset = HealthQuotation.objects.all()
Your url.py should be:
path('get-health-quotation-list', GetHealthQuotationList.as_view()),
NOTE: In case you plan to retrieve data from child table and find its associated parent, then your serializer should be good to go without many=True argument.
How do you format data returned from the Generics.ListAPIView class before sending it to the front end? I want to add metadata to the return value, but I can't add any metadata that isn't already part of a model. For instance:
class someList(generics.ListAPIView):
serializer_class = someSerializer
def get_queryset(self):
return someQueryset()
class someListSerializer(SurveySerializer):
class Meta:
model = someListModel
fields = ['modelField']
class someListModel(Base):
modelField=models.TextField(default="", blank=True)
This would yield
[{modelField:information},{modelField:information},{modelField:information}]
What if I want
[
{modelField:information, informationCalculatedFromQueryset:butNotPartOfModel}, {modelField:information, informationCalculatedFromQueryset:butNotPartOfModel},{modelField:information, informationCalculatedFromQueryset:butNotPartOfModel}
]
informationCalculatedFromQueryset is not part of someListModel, so I can't just add it into fields. How do I manually append this information to the front end? Is this possible?
For that you can use SerializerMethodField in this you can do like this:
class SurveySerializer(serializers.Serializer):
otherField = serializers.SerailzerMethodField()
get_otherField(self, obj):
return "I am other field"
class Meta:
model = Survey
fields = "__all__"
Using Django 2.1/Django Rest Framework.
I am receiving the Model object output from DRF instead of the actual fields.
I would like to recieve all the items for both the audio_links and release_artists tables.
Here's what I have.
Output
{
"title": "Attack The Dancefloor Volume Two",
"audiolinks": [
"AudioLinks object (55708)",
"AudioLinks object (55709)",
"AudioLinks object (55710)",
"AudioLinks object (55711)"
],
"releaseartists": [
"ReleaseArtists object (140)",
"ReleaseArtists object (141)"
]
}
models.py
class AudioLinks(models.Model):
release = models.ForeignKey('ReleasesAll', models.DO_NOTHING, db_column='release_id', related_name='audiolinks')
track_number = models.IntegerField()
class Meta:
managed = False
db_table = 'audio_links'
class ReleaseArtists(models.Model):
release = models.ForeignKey('ReleasesAll', models.DO_NOTHING, db_column='release_id', related_name='releaseartists')
artists = models.CharField(max_length=100)
class Meta:
managed = False
db_table = 'release_artists'
views.py
class ListReleaseDetailView(generics.RetrieveUpdateDestroyAPIView):
queryset = ReleasesAll.objects.all()
serializer_class = ReleasesSerializer
def get(self, request, *args, **kwargs):
try:
a_release = self.queryset.prefetch_related('releaseartists','audiolinks').get(pk=kwargs['release_id'])
return Response(ReleasesSerializer(a_release).data)
except ReleasesAll.DoesNotExist:
return Response(
data = {
"message": "{} does not exist".format(kwargs["release_id"])
},
status=status.HTTP_404_NOT_FOUND
)
serializers.py
class ReleasesSerializer(serializers.ModelSerializer):
audiolinks = serializers.StringRelatedField(many=True)
releaseartists = serializers.StringRelatedField(many=True)
class Meta:
model = ReleasesAll
fields = ('title','audiolinks','releaseartists')
serializers.stringRelatedField gives the output of the __str__ method defined on the Model.
Hence you are getting this "AudioLinks object (55708)" and ReleaseArtists object (141), which is the default __str__ representation of any model.
In order to get all fields, you must define serializers for every other model that is related to the required fields as such:
class AudioLinksSerializer(serializers.ModelSerializer):
class Meta:
model = AudioLinks
fields = ('__all__')
class ReleaseArtistsSerializer(serializers.ModelSerializer):
audiolinks = serializers.StringRelatedField(many=True)
releaseartists = serializers.StringRelatedField(many=True)
class Meta:
model = ReleaseArtists
fields = ('__all__')
and then:
class ReleasesSerializer(serializers.ModelSerializer):
audiolinks = AudioLinksSerializer(many=True)
releaseartists = ReleaseArtistsSerializer(many=True)
class Meta:
model = ReleasesAll
fields = ('title','audiolinks','releaseartists')
That would do enough for you to get all fields.
***And also as of Django convention, you need to name the models as in singular form i.e, AudioLink, ReleaseArtist. This way you see the model as a single object. And it will be easier when you try to get audiolinks as you will be thinking like "there are many AudioLink objects and I am getting few of them"
How would I do this to show the ForeignKey protected_area's name field?:
class NotificationReceiverSerializer(serializers.ModelSerializer):
class Meta:
model = NotificationReceiver
fields = ('pk','cellphone', 'protected_area__name')
So now its just showing as PK, as expected:
protected_area":1
Try something like this.
class NotificationReceiverSerializer(serializers.ModelSerializer):
proteced_area = serializers.ReadOnlyField(source="protected_area.name")
class Meta:
model = NotificationReceiver
fields = ('pk','cellphone', 'protected_area')
This will show the protected_area names as a read only field. Alternatively,
class NotificationReceiverSerializer(serializers.ModelSerializer):
proteced_area = ProtectedAreaSerializer(read_only=True, many=True)
class Meta:
model = NotificationReceiver
fields = ('pk','cellphone', 'protected_area')
to show all the fields in the related model