I've just started with Django REST framework and I'm having trouble with saving foreign keys. I have a Merchant model and a Phone model. The Phone has a foreign key to Merchant. When making a POST request to Merchant, I want to create Phone objects for the numbers provided in the request. But when I supply the phone numbers, it gives me the following error
Object with phone=0123456789 does not exist.
I just want it to create the Phone object itself. Here are the models that I am using:
class Merchant(models.Model):
merchant_id = models.CharField(max_length=255)
name = models.CharField(max_length=255)
is_active = models.BooleanField(default=True)
class Meta:
managed = True
db_table = 'merchant'
# Managers
objects = models.Manager()
active = managers.ActiveManager()
class Phone(models.Model):
phone = models.CharField(max_length=255)
merchant = models.ForeignKey('merchant.Merchant',
related_name='phones',
blank=True,
null=True)
class Meta:
managed = True
db_table = 'phone'
And here is the view and serializer that I am using them with
class MerchantSerializer(serializers.ModelSerializer):
phones = serializers.SlugRelatedField(
many=True,
slug_field='phone',
queryset=primitives.Phone.objects.all())
class Meta:
model = Merchant
fields = (
'merchant_id',
'name',
'is_active',
'phones',
)
class MerchantViewSet(viewsets.ModelViewSet):
queryset = Merchant.active.all()
serializer_class = MerchantSerializer
Here's what my request body looks like:
{
"merchant_id": "emp011",
"name": "Abhinav",
"is_active": true,
"phones": [
"0123456789",
"9876543210"
]
}
Here's the response:
400 Bad Request
{"phones":["Object with phone=0123456789 does not exist."]}
The SlugRelatedField provided by Django REST framework, like many of the related fields, is designed to be used with objects that already exist. Since you are looking to reference objects which already exist, or object which need to be created, you aren't going to be able to use it as-is.
You will need a custom SlugRelatedField that creates the new object when one doesn't exist.
class CreatableSlugRelatedField(serializers.SlugRelatedField):
def to_internal_value(self, data):
try:
return self.get_queryset().get_or_create(**{self.slug_field: data})[0]
except ObjectDoesNotExist:
self.fail('does_not_exist', slug_name=self.slug_field, value=smart_text(data))
except (TypeError, ValueError):
self.fail('invalid')
class MerchantSerializer(serializers.ModelSerializer):
phones = CreatableSlugRelatedField(
many=True,
slug_field='phone',
queryset=primitives.Phone.objects.all()
)
class Meta:
model = Merchant
fields = (
'merchant_id',
'name',
'is_active',
'phones',
)
By switching to get_or_create, the phone number object will be created if one doesn't already exist. You may need to tweak this if there are additional fields that have to be created on the model.
Building on Kevin's answer, a little cleaner IMO due to not using get_or_create and [0]
class CreatableSlugRelatedField(serializers.SlugRelatedField):
def to_internal_value(self, data):
try:
return self.get_queryset().get(**{self.slug_field: data})
except ObjectDoesNotExist:
return self.get_queryset().create(**{self.slug_field: data}) # to create the object
except (TypeError, ValueError):
self.fail('invalid')
Note the only required field in the related object should be the slug field.
You have to specify a value for phone field of the object Phone. If you want create phone object without specifying value for the field phone then you have to enable null and blank fields.
phone = models.CharField(max_length=255,null=true,blank=true)
If you still experience problems, make sure the post data contains the required fields. You can use ipdb for this.
Related
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"
I've just started with Django REST framework and I'm having trouble with saving foreign keys. I have a Merchant model and a Phone model. The Phone has a foreign key to Merchant. When making a POST request to Merchant, I want to create Phone objects for the numbers provided in the request. But when I supply the phone numbers, it gives me the following error
Object with phone=0123456789 does not exist.
I just want it to create the Phone object itself. Here are the models that I am using:
class Merchant(models.Model):
merchant_id = models.CharField(max_length=255)
name = models.CharField(max_length=255)
is_active = models.BooleanField(default=True)
class Meta:
managed = True
db_table = 'merchant'
# Managers
objects = models.Manager()
active = managers.ActiveManager()
class Phone(models.Model):
phone = models.CharField(max_length=255)
merchant = models.ForeignKey('merchant.Merchant',
related_name='phones',
blank=True,
null=True)
class Meta:
managed = True
db_table = 'phone'
And here is the view and serializer that I am using them with
class MerchantSerializer(serializers.ModelSerializer):
phones = serializers.SlugRelatedField(
many=True,
slug_field='phone',
queryset=primitives.Phone.objects.all())
class Meta:
model = Merchant
fields = (
'merchant_id',
'name',
'is_active',
'phones',
)
class MerchantViewSet(viewsets.ModelViewSet):
queryset = Merchant.active.all()
serializer_class = MerchantSerializer
Here's what my request body looks like:
{
"merchant_id": "emp011",
"name": "Abhinav",
"is_active": true,
"phones": [
"0123456789",
"9876543210"
]
}
Here's the response:
400 Bad Request
{"phones":["Object with phone=0123456789 does not exist."]}
The SlugRelatedField provided by Django REST framework, like many of the related fields, is designed to be used with objects that already exist. Since you are looking to reference objects which already exist, or object which need to be created, you aren't going to be able to use it as-is.
You will need a custom SlugRelatedField that creates the new object when one doesn't exist.
class CreatableSlugRelatedField(serializers.SlugRelatedField):
def to_internal_value(self, data):
try:
return self.get_queryset().get_or_create(**{self.slug_field: data})[0]
except ObjectDoesNotExist:
self.fail('does_not_exist', slug_name=self.slug_field, value=smart_text(data))
except (TypeError, ValueError):
self.fail('invalid')
class MerchantSerializer(serializers.ModelSerializer):
phones = CreatableSlugRelatedField(
many=True,
slug_field='phone',
queryset=primitives.Phone.objects.all()
)
class Meta:
model = Merchant
fields = (
'merchant_id',
'name',
'is_active',
'phones',
)
By switching to get_or_create, the phone number object will be created if one doesn't already exist. You may need to tweak this if there are additional fields that have to be created on the model.
Building on Kevin's answer, a little cleaner IMO due to not using get_or_create and [0]
class CreatableSlugRelatedField(serializers.SlugRelatedField):
def to_internal_value(self, data):
try:
return self.get_queryset().get(**{self.slug_field: data})
except ObjectDoesNotExist:
return self.get_queryset().create(**{self.slug_field: data}) # to create the object
except (TypeError, ValueError):
self.fail('invalid')
Note the only required field in the related object should be the slug field.
You have to specify a value for phone field of the object Phone. If you want create phone object without specifying value for the field phone then you have to enable null and blank fields.
phone = models.CharField(max_length=255,null=true,blank=true)
If you still experience problems, make sure the post data contains the required fields. You can use ipdb for this.
I want to get id value passing in the serializer that is
id = serializers.IntegerField(label='ID') in the function to get the profile object
def profile_info(self, obj)
But it giving the error id is IntegerField please pass the int or
string
Can Anybody tell me how to get values passed in id field thanks. Down below is my serializer code
class UserSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(label='ID')
# print ('dadddaaa ',serializers.data)
profile = serializers.SerializerMethodField('profile_info')
username = serializers.CharField()
first_name = serializers.CharField()
last_name = serializers.CharField()
# Nazir = serializers.CharField()
# profile = UsersSerializer(Profile.objects.get(User.objects.get(pk=serializers.data['id'])))
def profile_info(self, obj):
# print ('selffff ', serializers)
prof_obj = Profile.objects.get(user=User.objects.get(pk=id))
return {'id':prof_obj.id}
I was searching for an answer but none of the worked for me but i got it from django it self
you can get it by using initial_data as inital_data returns the query dictionary
self.initial_data['id']
In Django Rest Framework every serializer extends from the upper class Field so to obtain a value, you most call to_internal_value or to_representation. This process runs automatically on your serializer over each field when you call .is_valid. In your case because you need the value of one Field the best option is skip this process and access using:
self.initial_data.get("your field name")
This return the original value passed to the serializer without validate. If you know that the function profile_info will be called after run a serializer.is_valid i recommend to access the value using:
self.validated_data.get("your field name")
Always use .get to get a value in a dictionary because by default returns None in the case that key does not exist. Using brackets will be raise an exception in the same escenario.
I would not recommend retrieving the related Profile model with a SerializerMethod, because this will fire a separate query for each User. I suggest to use a nested Serializer for the Profile model, so Django/DRF will build a JOIN query. Also, you don't have to specify each field, you can use the fields option of the Meta class.
First, make sure that you specify a related_name for the user-relation in your Profile model, so it will be accessible as a field in the ModelSerializer:
user = models.OneToOneField(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name='profile',
)
Then create a Serializer for each model:
class ProfileSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = ('id', 'ship', 'copilot')
class UserSerializer(serializers.ModelSerializer):
profile = ProfileSerializer(read_only=True)
class Meta:
model = User
fields = ('id', 'profile', 'username', 'first_name', 'last_name')
This will give you a JSON like this:
{
"id": 1,
"profile": {
"id": 1,
"ship": "Millennium Falcon",
"copilot": "Chewbacca"
},
"username": "hsolo",
"first_name": "Han",
"last_name": "Solo"
}
Note: By default nested relations are not writable, but it can be done by creating your own create() and update() functions
The main issue here is that you are passing an IntegerField object as an id in this query:
Profile.objects.get(user=User.objects.get(pk=id))
Try this instead:
Profile.objects.get(user=User.objects.get(pk=obj.id))
Full example:
class UserSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(label='ID')
# print ('dadddaaa ',serializers.data)
profile = serializers.SerializerMethodField('profile_info')
username = serializers.CharField()
first_name = serializers.CharField()
last_name = serializers.CharField()
# Nazir = serializers.CharField()
# profile = UsersSerializer(Profile.objects.get(User.objects.get(pk=serializers.data['id'])))
def profile_info(self, obj):
# print ('selffff ', serializers)
prof_obj = Profile.objects.get(user=User.objects.get(pk=obj.id))
return {'id':prof_obj.id}
I have a customer model in Bcustomer app that extends the django User model, So I will save the basic details such as name in User table and the remaining data (city, etc) in customer table.
When I call the below code through API, it shows the following error. But data is saving in the tables. I also want to implement the get and put calls for this api.
Got AttributeError when attempting to get a value for field `city` on serializer `CustomerSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `User` instance.
Original exception text was: 'User' object has no attribute 'city'.
my Bcustomer/models.py
class BCustomer(models.Model):
customer = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, primary_key=True, blank=True )
address = models.CharField(max_length=50)
city = models.CharField(max_length=256)
state = models.CharField(max_length=50)
user = models.ForeignKey(settings.AUTH_USER_MODEL, db_index=True, on_delete=models.CASCADE, related_name='customer_creator')
# more fields to go
def __str__(self):
# return str(self.name) (This should print first and last name in User model)
class Meta:
app_label = 'bcustomer'
my Bcustomer/serializers.py
from django.contrib.auth import get_user_model
from models import BCustomer
class CustomerSerializer(serializers.HyperlinkedModelSerializer):
city = serializers.CharField()
class Meta:
model = get_user_model()
fields = ('first_name', 'email','city')
def create(self, validated_data):
userModel = get_user_model()
email = validated_data.pop('email', None)
first_name = validated_data.pop('first_name', None)
city = validated_data.pop('city', None)
request = self.context.get('request')
creator = request.user
user = userModel.objects.create(
first_name=first_name,
email=email,
# etc ...
)
customer = BCustomer.objects.create(
customer=user,
city=city,
user=creator
# etc ...
)
return user
my Bcustomer/views.py
class CustomerViewSet(viewsets.ModelViewSet):
customer_photo_thumb = BCustomer.get_thumbnail_url
permission_classes = [permissions.IsAuthenticated, TokenHasReadWriteScope]
queryset = BCustomer.objects.all()
serializer_class = CustomerSerializer
my Bcustomer/urls.py
router.register(r'customer', views.CustomerViewSet, 'customers')
POST request format
{
"first_name":"Jsanefvf dss",
"city":"My City",
"email":"myemail#gmail.com",
#more fields
}
I also need to implement put and get for this api. Now data is saving in both tables but shows the error.
Sure it complains.
Validation goes well, so does the creation but once it's created, the view will deserialize the result and return it to the client.
This is the point where it goes south. Serializer is configured to think that city is a field of the default user while it actually is part of BCustomer. In order to work this around, you should set the source argument to the city field. You might need to update the serializer's create/update to reflect that change, not sure about that one.
suppose this model:
class Tweek(models.Model):
content = models.CharField(max_length=140)
date = models.DateTimeField(auto_now_add=True)
author = models.ForeignKey(User, related_name='author')
class Meta:
ordering = ['-date']
def __unicode__(self):
return self.content
Everthing works fine, now i try to bind a rest api uppon. I've installed the django rest framework and I can retrieve tweeks but I cannot create new ones.
I have this serializer for the Tweek model:
class TweekSerializer(serializers.ModelSerializer):
author = UserSerializer()
class Meta:
model = Tweek
fields = ('content', 'date', 'author')
def create(self, validated_data):
author_data = validated_data.pop('author')
author = User.objects.get(username=author_data)
return Tweek.objects.create(author=author, **validated_data)
and the user serializer somply looks like:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('username', 'first_name', 'last_name')
but the post returns
{
"author": {
"username": [
"This field must be unique."
]
}
}
I've followed the doc, but i'm lost with this case :(
Any idea ?
The issue here is that any field with unique=True set, like the username field on the Django User model, will automatically have the UniqueValidator added to the serializer field. This validator is what is triggering the message you are seeing, and you can remove the validation by setting validators to [] (an empty list) when initializing the field.
The other issue that you are going to run into is that you are trying to create an object with a foreign key, but you are returning the full serialized object in your response. This issue is easier fixed by using a second field for setting the id, that is write-only, and using the original serializer field for the nested representation and making that read-only.
You can find more information in the following Stack Overflow question: DRF: Simple foreign key assignment with nested serializers?