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}
Related
DRF docs says that "By default nested serializers are read-only. If you want to support write-operations to a nested serializer field you'll need to create create() and/or update() methods in order to explicitly specify how the child relationships should be save."<<
In general this is true except for one case. When User class is a nested object in serializer.
Use case is to add existing user to newly created organization.
Consider my simple example where I noticed that issue:
views.py
class UserOrganizationViewSet(viewsets.ModelViewSet):
# authentication_classes = (JwtAuthentication, SessionAuthentication)
# permission_classes = (permissions.JWT_RESTRICTED_APPLICATION_OR_USER_ACCESS,)
serializer_class = UserOrganizationSerializer
queryset = UserOrganization.objects.all()
models.py:
class UserOrganization(models.Model):
name = models.CharField(max_length=256)
users = models.ManyToManyField(User, blank=True, related_name="user_organizations", through='OrganizationMember')
def __str__(self):
return self.name
class OrganizationMember(models.Model):
organization = models.ForeignKey(UserOrganization, on_delete=CASCADE)
user = models.ForeignKey(User, on_delete=CASCADE)
joined = AutoCreatedField(_('joined'))
serializers.py:
First version - User class has its own simple serializer and it is used as nested serializer:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = ['username']
class UserOrganizationSerializer(serializers.ModelSerializer):
users = UserSerializer(many=True, required=False)
class Meta:
model = UserOrganization
fields = ['id', 'name', 'users']
read_only_fields = ['id']
Is this case I am able to fetch data via GET method (Organization created through admin panel):
But when I am trying to create organization I am getting error which says that user already exist:
So, I checked what will happen when I try to paste not existed username in JSON data send. According to expectation DRF says that I need to override create() method:
{
"name": "Test2",
"users": [
{
"username": "admins"
}
]
}
AssertionError: The `.create()` method does not support writable nested fields by default.
Write an explicit `.create()` method for serializer `organization.serializers.UserOrganizationSerializer`, or set `read_only=True` on nested serializer fields.
And there is a first weird behavior. DRF still tries to created User object:
My create method looks like this:
def create(self, validated_data):
print("validated_data", validated_data)
users = validated_data.pop('users', [])
user_organization = UserOrganization.objects.create(**validated_data)
print("users", users)
for user in users:
userSerializer = UserSerializer(user)
print("userSerializer.data", userSerializer.data)
username = userSerializer.data['username']
print("username", username)
user_organization.users.add(get_user_model().objects.get(username=username))
return user_organization
In this case my method isn't called at all.
In case when I passed user which does not exist, my create() method is called and exeption is thrown as it should be. (I know I am not handling errors, this is just sandbox):
{
"name": "Test2",
"users": [
{
"username": "admin1"
}
]
}
django.contrib.auth.models.User.DoesNotExist: User matching query does not exist.
New model class UserProfile and serializer for this class used as nested:
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model = UserProfile
fields = ['name']
class UserOrganizationSerializer(serializers.ModelSerializer):
profiles = UserProfileSerializer(many=True)
class Meta:
model = UserOrganization
fields = ['id', 'name', 'profiles']
read_only_fields = ['id']
def create(self, validated_data):
print("validated_data ", validated_data)
users = validated_data.pop('profiles', [])
user_organization = UserOrganization.objects.create(**validated_data)
print("create", users)
for user in users:
print("username = ", user['name'])
user_organization.profiles.add(UserProfile.objects.get(name=user['name']))
user_organization.save()
return user_organization
Small change was needed to change field users to profiles inside UserOrganization.
Creating organization with existed user profile (created by admin) via POST:
In this scenario there is no such problem as mentioned in first case.
Final solution using SlugReleatedField works as expected. No serializer used for User.
class UserOrganizationSerializer(serializers.ModelSerializer):
users = serializers.SlugRelatedField(many=True, queryset=get_user_model().objects.all(), slug_field='username')
class Meta:
model = UserOrganization
fields = ['id', 'name', 'users']
read_only_fields = ['id']
Above code does what I exactly need and is very simple. Here is small concern regarding performance when fetched all users from DB in this line:
queryset=get_user_model().objects.all()
Can someone explain why this happens what we could observe in scenario 1?
Sorry for this post being so long. I tried to shorten it as much I as can. I can put whole project in github if it will be needed for readability.
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'm doing a HTTP PUT call to update the data of an object with a nested relationship, and I'm met by the following error:
HTTP 400 Bad Request
"AttributeChoice with this slug already exists."
The reason why this is confusing is because I'm doing a HTTP PUT call and I expect it to treat it as an UPDATE and not a CREATE.
My Models look like this:
class Attribute(models.Model):
name = models.CharField(max_length=100)
text_input = models.BooleanField(default=False)
slug = models.SlugField(unique=True)
class AttributeChoice(models.Model):
attribute = models.ForeignKey(Attribute)
value = models.CharField(max_length=100)
slug = models.SlugField(unique=True)
My Serializers look like this:
class AttributeChoiceSerializer(serializers.ModelSerializer):
class Meta:
model = AttributeChoice
fields = '__all__'
extra_kwargs = {'id': {'read_only': False}}
class AttributeSerializer(serializers.ModelSerializer):
attributechoice_set = AttributeChoiceSerializer(many=True)
class Meta:
model = Attribute
fields = ('id', 'name', 'text_input', 'slug', 'attributechoice_set')
def update(self, instance, validated_data):
choice_data = validated_data.pop('attributechoice_set')
for choice in choice_data:
# If id is within the call, then update the object with matching id
if 'id' in choice:
try:
choice_obj = AttributeChoice.objects.get(pk=choice['id'])
choice_obj.value = choice['value']
choice_obj.slug = choice['slug']
choice_obj.attribute = instance
# If ID is not found, then create a new object
except AttributeChoice.DoesNotExist:
choice_obj = AttributeChoice(**choice)
# If no ID within the call, create a new object.
else:
choice_obj = AttributeChoice(**choice)
choice_obj.save()
return instance
Debug:
Even if I remove the update() function, I still get the same error. I believe the error is reported from when .is_valid() is called in the ViewSet. So it's not the update() that causes it.
Also, if I remove attributechoice_set = AttributeChoiceSerializer(many=True) and just include the attributechoice_set in the fields = (), the error disappears, but I need that line for the rest of the code to work.
Even through you're doing an update, it doesn't mean the nested data will just be updated.
You're simply saying that you want to update the top most object.
In some cases, you'll be removing or creating new nested objects while updating the top most one.
Therefore DRF considers by default that nested objects are for creation. You can work around this by explicitly removing the unique constraint on the nested serializer:
class AttributeChoiceSerializer(serializers.ModelSerializer):
class Meta:
model = AttributeChoice
fields = '__all__'
extra_kwargs = {
'id': {'read_only': False},
'slug': {'validators': []},
}
Someone has already developed a handy UniqueFieldsMixin for solving the problem:
pip install drf-writable-nested
now:
from drf_writable_nested import UniqueFieldsMixin
class User(models.Model):
name = models.CharField(max_length=200, unique=True)
class UserSerializer(UniqueFieldsMixin, serializers.ModelSerializer):
class Meta:
model = User
fields = '__all__'
I think it is because of the validators.
Like:
Django rest serializer Breaks when data exists
As my solution, I mark this nested field to read_only=True,
And do my own update, create function to access self.initial_data to handle myself.
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?
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.