Accepting a different field in POST in Django REST Framework - django

If I have the following Dango model and Django REST serializer:
# model
class Attribute(models.Model):
name = models.CharField(max_length=50)
code = models.CharField(max_length=50)
value = models.IntegerField(default=0)
# serializer
class AttributeSerializer(serializers.ModelSerializer):
name = serializers.CharField()
code = serializers.CharField()
value = serializers.IntegerField()
class Meta:
model = Attribute
fields = ('name', 'code', 'value', 'group')
Is it possible to accept a different field during the PUT or POST to update the model? for example, could it accept attribute_value and use that to update the value field?

There is to_internal_value function read more on Docs:
Override this to support deserialization, for write operations.
You can override it like this:
def to_internal_value(self, data):
if data.get('attribute_value'):
data['value'] = data.pop('attribute_value')
data = super().to_internal_value(data)
return data

Related

Can't render nested relationship in Django Rest Framework

The problem is I have a 'details' field which should render into a nested relationship with it's parent serializer. I have tried a bunch of stuff and nothing seems to be working.
Here's my models:
class BusinessOrderModel(OrderToModel):
reference = models.IntegerField()
business_num = models.ForeignKey('BusinessModel', on_delete=models.CASCADE)
def __str__(self):
return str(self.reference)
class BusinessModel(models.Model):
Business_num = models.IntegerField(primary_key=True)
def __str__(self):
return str(self.Business_num)
class DetailModel(models.Model):
id = models.AutoField(primary_key=True)
detail = models.TextField()
order = models.ForeignKey('BusinessOrderModel', on_delete=models.CASCADE)
and here's my serializers which aren't working:
class DetailSerializer(serializers.ModelSerializer):
class Meta:
model = DetailModel
fields = ('id', 'detail')
class BusinessOrderSerializer(serializers.ModelSerializer):
details = DetailSerializer(many=True)
class Meta:
model = BusinessOrderModel
fields = ('reference', 'business_num', 'details')
I've tried many different things but I get this error:
Got AttributeError when attempting to get a value for field details
on serializer BusinessOrderSerializer. The serializer field might be
named incorrectly and not match any attribute or key on the
BusinessOrderModel instance. Original exception text was:
'BusinessOrderModel' object has no attribute 'details'.
Any help is much appreciated.
Thank you very much.
Using details to lookup reverse relationships only works if you set it as the related_name. The default for BusinessOrderModel to DetailModel will be detailmodel_set.
To make it accessible by calling details you should make this change:
class DetailModel(models.Model):
id = models.AutoField(primary_key=True)
detail = models.TextField()
order = models.ForeignKey('BusinessOrderModel', related_name="details", on_delete=models.CASCADE)
Now you can use DetailModel.objects.get(id=1).details.all()
You can also customize the query in your serializer:
class BusinessOrderSerializer(serializers.ModelSerializer):
details = SerializerMethodField()
class Meta:
model = BusinessOrderModel
fields = ('reference', 'business_num', 'details')
def get_details(self, obj):
return DetailSerializer(obj.details.filter(), many=True).data

How to save a model in Django Rest Framework having one to one relationship

I have a Django model named BankDetail that has a one to one relationship with User.
#BankeDetails
class BankDetail(models.Model):
account_holder_name = models.CharField(max_length=100)
account_number = models.CharField(max_length=50, blank=True, null=True)
iban = models.CharField("IBAN", max_length=34, blank=True, null=True)
bank_name = models.CharField(max_length=100)
bank_address = models.CharField(max_length=500)
swift_bic_code = models.CharField(max_length=11)
user = models.OneToOneField(MyUser,on_delete=models.CASCADE)
accepting_fiat_currency = models.OneToOneField(AcceptedFiatCurrency)
created_at = models.DateTimeField(auto_now_add=True,null=True)
updated_at = models.DateTimeField(auto_now=True)
The serializer is as listed below :
class BankDetailSerializer(serializers.ModelSerializer):
"""Serializer for BankDetails"""
class Meta:
model = BankDetail
fields = "__all__"
def validate(self, data):
if data['account_number'] or data['iban']:
raise serializers.ValidationError("Please fill Account Number or IBAN")
return data
Request Payload :
{
"account_holder_name":"Aladin",
"account_number":"1239893",
"bank_name":"Aladin Bank",
"bank_address":"Republic of Wadia",
"swift_bic_code":"1",
"user_id":"1",
"accepting_fiat_currency_id":"1"
}
Now when I'm trying to save the model from my view, I get the following error :
{
"user": [
"This field is required."
],
"accepting_fiat_currency": [
"This field is required."
]
}
How can I pass I refrence ob these objects, do I need to manually retrieve them from the db using the id's?
AFAIR you should define a related model field in the serializer. And don't use __all__ instead of explicit write all fields. It's my recommendation :)
You should find answer in following questions:
Post from StackOverflow
That helped me last week
DRF documentation about Serializer relations
One solution is to use different serializer for creating/retrieving data in/from database.
Your serializer for creating should be something like -
class BankDetailSerializer(serializers.ModelSerializer):
"""Serializer for BankDetails"""
class Meta:
model = BankDetail
exclude = ('user', 'accepting_fiat_currency', ) # Note we have excluded the related fields
def validate(self, data):
if data['account_number'] or data['iban']:
raise serializers.ValidationError("Please fill Account Number or IBAN")
return data
Then while saving the serializer pass the above two excluded fields. Like -
serializer = BankDetailSerializer(data=request.data)
if serializer.is_valid():
serializer.save(user=request.user, accepting_fiat_currency=<AcceptedFiatCurrency-object>)
If you are using Generic Views in DRF then I would suggest you to look
into perform_create method.
You need to send "user":"1". instead of "user_id":"1". The same with other "accepting_fiat_currency"

How to serialize a API response (in JSON) and modify and add fields to it in Django Rest Framework?

I am getting data from different APIs in DRF. However, to enforce modularity I need to serialize the JSON response and make a 'fake' model for each API endpoint I am calling.
I have already created a model and a serializer for an endpoint, but I need to make another API call while serializing previous response I need to modify some of the fields.
from rest_framework import serializers
from django.db import models
from ..nlp_utils.google_nlp import GoogleNLP
class Search(models.Model):
title = models.CharField(blank=True, default='')
link = models.CharField(blank=True, default='')
snippet = models.CharField(blank=True, default='')
description = models.CharField(blank=True, default='')
sentiment_score = models.FloatField(blank=True, default=0.0)
sentiment_magnitude = models.FloatField(blank=True, default=0.0)
class SearchResultSerializer(serializers.ModelSerializer):
class Meta:
model = Search
fields = ('title', 'link', 'snippet', 'description','sentiment_score', 'sentiment_magnitude')`
here I need to call some more endpoints and populate sentiment_score and sentiment_magnitude
You have two options:
Option 1
You can override to_representation method of serializer. Each serializer has a method called to_representation that will create json response that will be passed to users.
for example:
class SearchResultSerializer(serializers.ModelSerializer):
def to_representation(self, instance):
r = super(TodoSerializer, self).to_representation(instance)
r.update({
'sentiment_score': 'anything you want here'
})
return r
Option 2
Use django rest MethodSerializer fields in your serializer.
class SearchResultSerializer(serializers.ModelSerializer):
sentiment_magnitude = serializers.SerializerMethodField()
class Meta:
model = Search
fields = '__all__'
def get_sentiment_magnitude(self, obj):
sentiment_magnitude = "anything you want here"
return sentiment_magnitude
Rather than defining it in the model, you can directly attach these fields in the serializer like this(using SerializerMethodField):
class SearchResultSerializer(serializers.ModelSerializer):
sentiment_score = serializers.SerializerMethodField()
sentiment_magnitude = serializers.SerializerMethodField()
class Meta:
model = Search
fields = ('title', 'link', 'snippet', 'description','sentiment_score', 'sentiment_magnitude')
def get_sentiment_magnitude(self, obj):
# call external api with search obj which has been stored in your previous call
return data
def get_sentiment_score(self, obj):
# call external api with search obj which has been stored in your previous call
return data
Update
You can use context from any Generic Views or Viewset to pre-populate data. You can try like this:
class YourViewSet(ViewSet):
...
def get_serializer_context(self):
context = super(YourViewSet, self).get_serializer_context()
data = get_it_from_api()
context['sentiment_score'] = data.get('sentiment_score')
context['sentiment_magnitude'] = data.get('sentiment_magnitude')
return context
And use it in serializer like this:
class SearchResultSerializer(serializers.ModelSerializer):
sentiment_score = serializers.SerializerMethodField()
sentiment_magnitude = serializers.SerializerMethodField()
class Meta:
model = Search
fields = ('title', 'link', 'snippet', 'description','sentiment_score', 'sentiment_magnitude')
def get_sentiment_magnitude(self, obj):
return self.context.get('sentiment_magnitude')
def get_sentiment_score(self, obj):
return self.context.get('sentiment_score')
Also, even without using the generic views/viewset, you can still pass extra context like this SearchResultSerializer(instance, context={'sentiment_magnitude': sentiment_magnitude, "sentiment_score": sentiment_score}). Please see the documentation.

Validations of Model Serializers

I have to modify the feature of ModelSerializer, my expectations are as,
I have two fields in my Model. Both are charFields.
class MyModel(models.Model):
name = models.CharField(blank=False,
null=False,
max_length=20)
value = models.CharField(blank=True,
null=True,
max_length=20)
My serializer is as,
class MyModelSerializer(ModelSerializer):
class Meta:
model = MyModel
fields = '__all__'
I am have to create the object of MyModel using this serializer.
Now issue is that if I am passing the 'bool' values in my fields, its showing error message that 'Not a valid string.' (As expected by Modelserializer)
{ "name":True, "value":False }
My requirements are to handle the 'bool' value and converted that 'bool' into 'str'. what should be the trick to resolve this.
use the to_internal_value function, for example this works:
class MyModelSerializer(ModelSerializer):
class Meta:
model = MyModel
fields = '__all__'
def to_internal_value(self, data):
for field in ('name', 'value'):
if field in data and isinstance(data[field], bool):
data[field] = str(data[field])
return super().to_internal_value(data)

Validate field not in model by Django rest

I have a model:
class EventTracker(models.Model):
"""
Track events of user's behaviors
"""
class Meta:
verbose_name = "EventTracker"
verbose_name_plural = "EventTrackers"
unique_together = ("application", "label")
application = models.ForeignKey(Application, related_name='events')
label = models.CharField(max_length=50)
count = models.PositiveIntegerField(default=0)
value = models.IntegerField(null=True)
def __str__(self):
return "[{}] {}".format(self.application, self.label)
This is my serializer for this model:
class EventTrackerSerializer(serializers.ModelSerializer):
subscriber_id = serializers.IntegerField(min_value=1)
class Meta:
model = EventTracker
fields = ('id', 'application', 'label', 'count', 'value', 'subscriber_id')
write_only_fields = ('subscriber_id', )
read_only_fields = ('count',)
subscriber_id is a field that doesn't belong to this model. But request data must have subscriber_id to do a thing. So I want to validate it in serializer. I don't know how to validate it. I tried like above, it threw error:
This may be because you have a writable field on the serializer class that is not a valid argument to.....
So what can I do ?
First, you should probably be more explicit about what you want to do. We don't know what that field is for nor if it's readable nether what/how you want to validate it, so I'd do some guesswork.
Assuming it's write only:
subscriber_id = serializers.IntegerField(min_value=1, write_only=True)
Note that the write_only_fields has been removed for some time.
Next, you'll have to write manually the serializer's create/update. Example for the create:
class EventTrackerSerializer(serializers.ModelSerializer):
...
def create(self, validated_data):
subscriber_id = validated_data.pop('subscriber_id')
instance = EventTracker.objects.create(**validated_data)
return instance