I have created 3 groups 1. Staff, 2. Admin, 3. Operational Manager and assigned permission. Now whenever I wanted to add new user I wanted to show the choice field of the group and able to select the group.
As per the framework the User and Groups have many-to-many relations and I am trying to implement nested serializer. And as per documentation to add nested data I need to implement create or update method in the serializer but here I am not getting the choice field in group and the data is null.
GroupSerializer:
class GroupSerializer(serializers.ModelSerializer):
class Meta:
model = Group
fields = ('name',)
UserSerializers:
class UserSerializers(serializers.ModelSerializer):
groups = GroupSerializer(many=True)
class Meta:
model = User
fields = ('first_name', 'last_name','address','contact', 'email', 'date_of_birth', 'branch', 'groups')
def create(self, validated_data):
groups_data = validated_data.pop('groups')
user = User.objects.create(**validated_data)
for group_data in groups_data:
Group.objects.create(user=user, **group_data)
return user
when I wanted to make a post request I am getting :
{
"first_name": "",
"last_name": "",
"address": "",
"contact": null,
"email": "",
"date_of_birth": null,
"branch": null,
"groups": []
}
here groups fields are empty.
If I try to make a post request it says
{
"groups": [
"This field is required."
]
}
Edit : Solved
I removed GroupSerializer because User model has many-to-many relation with Group so we don't need to explicitly specify Group object
# class GroupSerializer(serializers.ModelSerializer):
# class Meta:
# model = Group
# fields = ('name',)
class UserSerializers(serializers.ModelSerializer):
# groups = GroupSerializer(many=True)
class Meta:
model = User
fields = ('username','first_name', 'last_name','address','contact', 'email', 'date_of_birth', 'branch', 'groups')
def create(self, validated_data):
groups_data = validated_data.pop('groups')
user = User.objects.create(**validated_data)
for group_data in groups_data:
# Group.objects.create(user=user, **group_data)
user.groups.add(group_data)
return user
You can use PrimarykeyRelatedField and provide id of groups that you want to add in a list. like below:
class UserSerializers(serializers.ModelSerializer):
groups = serializers.PrimaryKeyRelatedField(queryset=Group.objects.all())
class Meta:
model = User
fields = ('first_name', 'last_name', 'address', 'contact', 'email', 'date_of_birth', 'branch', 'groups')
def create(self, validated_data):
groups_data = validated_data.pop('groups')
user = User.objects.create(**validated_data)
for group_data in groups_data:
Group.objects.create(user=user, **group_data)
return user
def to_representation(self, instance):
r = super(UserSerializers, self).to_representation(instance)
r.update({
'groups': GroupSerializer(many=True).to_representation(instance.groups)
})
return r
So just provide list of groups like this: "groups": [1,2,3]
and you should also override to_representation method if you want to have serialized json result of each group in response.
Related
I've looked extensively on here and probably exhausted all the answers and still haven't found a solution to my particular problem, which is to make an API that update/delete from both models, and I am getting the following error:
The .update()method does not support writable nested fields by default. Write an explicit.update()method for serializeruser_profile.serializers.UserSerializer, or set read_only=True on nested serializer fields.
In this particular instance this happens when I try to update a field from the user_profile model
I have separated my Django project into several apps/folders with each model being in its own folder.
I have a user app and a user_profile app each with their own models.
the user model is basically an AbstractUser sitting in its own app
the user_profile model is as follows:
class UserProfile(models.Model):
user = models.OneToOneField(to=User, on_delete=models.CASCADE, related_name='userprofile')
location = models.CharField(blank=True, max_length=30)
created_time = models.DateTimeField(auto_now_add=True)
updated_time = models.DateTimeField(auto_now=True)
The serializers are as follows:
class UserProfileCrudSerializer(serializers.ModelSerializer):
class Meta:
model = UserProfile
fields = ('location', 'created_time', 'updated_time')
class UserSerializer(serializers.ModelSerializer):
profile = UserProfileCrudSerializer(source='userprofile', many=False)
class Meta:
model = User
fields = ('username', 'email', 'first_name', 'last_name', 'profile')
def update(self, instance, validated_data):
userprofile_serializer = self.fields['profile']
userprofile_instance = instance.userprofile
userprofile_data = validated_data.pop('userprofile', {})
userprofile_serializer.update(userprofile_instance, userprofile_data)
instance = super().update(instance, validated_data)
return instance
and my view is:
class RetrieveUpdateView(RetrieveUpdateAPIView):
serializer_class = UserSerializer
queryset = User.objects.all()
def get_object(self):
return self.request.user
when I do a GET I am getting the following response without any problems:
{
"username": "blue",
"email": "bluebear#bluebear.com",
"first_name": "Blue",
"last_name": "Bear",
"profile": {
"location": "London",
"created_time": "2023-02-03T00:39:15.149924Z",
"updated_time": "2023-02-03T00:39:15.149924Z"
}
}
and I do a patch request like this:
{
"profile": {
"location": "Paris"
}
}
The way the code is now I have no issue updating username, email, first_name, and last_name which come from the AbstractUser but I am getting the above error when I try to patch the location which is in the UserProfile model.
I've looked at many similar solutions online, but none that pertain to my particular situation.
The .update() method does not support writable nested fields by default. Write an explicit .update() method for serializeruser_profile.serializers.UserSerializer, or set read_only=True on nested serializer fields.
It already shows in the message, you need to explicitly write the update method for the writable nested serializer which is documented here https://www.django-rest-framework.org/topics/writable-nested-serializers/ or you can use another module that is also referred to in the docs https://github.com/beda-software/drf-writable-nested.
Your approach is correct already but there is some typo and wrong indentation in your code:
class UserSerializer(serializers.ModelSerializer):
profile = UserProfileCrudSerializer(source='userprofile', many=False)
class Meta:
model = User
fields = ('username', 'email', 'first_name', 'last_name', 'profile')
def update(self, instance, validated_data):
# update is a method of serializer not serializer.Meta
userprofile_serializer = self.fields['profile']
userprofile_instance = instance.userprofile
# should be 'profile' here instead of 'userprofile' as you defined in serializer
userprofile_data = validated_data.pop('profile', {})
userprofile_serializer.update(userprofile_instance, userprofile_data)
instance = super().update(instance, validated_data)
return instance
I have the following to create a user and add a Token to it:
User = get_user_model()
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username', 'password', 'first_name', 'last_name', 'email')
extra_kwargs = {'passwords': {'write_only': True, 'required': True}}
def create(self, validated_data):
user = User.objects.create_user(**validated_data)
token = Token.objects.create(user=user)
return user
I would like to be able to do the same thing for a Player:
class PlayerSerializer(serializers.ModelSerializer):
class Meta:
model = Player
fields = '__all__'
def create(self, validated_data):
# what to do here? How to create a new token for 'Player'?
I've tried this:
def create(self, validated_data):
player = Player.objects.create_user(**validated_data)
token = Token.objects.create(user=player)
return player
But got this error: TypeError: Direct assignment to the forward side of a many-to-many set is prohibited. Use groups.set() instead.
The Player model extends the 'MyUser' model:
class MyUser(AbstractUser):
pass
class Player(MyUser):
national_id = models.CharField(max_length=10)
def save(self, *args, **kwargs):
self.username = self.email
return super().save(*args, **kwargs)
I assume that you are using a payload similar to this:
{
"username": "root",
"password": "test",
"first_name": "a",
"last_name": "b",
"email": "a#b.com",
"national_id": "123",
}
Django's AbstractUser comes with several fields not included in this payload (eg. is_staff, is_superuser), most of which come with either default arguments, or are allowed to be null.
Because you specified fields = '__all__' in your PlayerSerializer, your endpoint is expecting every field to be present in the payload, hence your error.
If you were using a traditional model setup (by that I mean no inheritance), you would have received a helpful error message telling you exactly what's missing. I suspect the model inheritance aspect part of your code broke the serializer early, resulting in an unhelpful message.
Anyways, if my assumption is correct, you can fix this in 2 ways:
include all of AbstractUser's fields in your payload; see the source code for a full list
modify your serializer's meta to specify the fields you want.
eg:
class PlayerSerializer(serializers.ModelSerializer):
class Meta:
model = Player
fields = (
'username',
'password',
'first_name',
'last_name',
'email',
'national_id',
)
def create(self, validated_data):
player = Player.objects.create_user(**validated_data)
token = Token.objects.create(user=player)
return player
I have following serializers:
class UserSerializer(ModelSerializer):
class Meta:
model = User
fields = ('id', 'username', 'password')
class ProfileSerializer(ModelSerializer):
user = UserSerializer()
class Meta:
model = Profile
fields = ('id', 'user', 'name', 'address')
when I want to create a profile I should send following data:
{
"user":{
"username": "test_username",
"password": "123456789"
},
"name": "David",
"address": "Baker St"
}
my question is, is it possible to just send "user": 5 instead of sending dictionary in case of POST request?
You have two options, either you can use two different serializers for retrieve and create like this
class ProfileCreateSerializer(ModelSerializer):
class Meta:
model = Profile
fields = ('id', 'user', 'name', 'address')
and
class ProfileRetrieveSerializer(ProfileCreateSerializer):
user = UserSerializer()
and decide which serializer to use in view (hint: override get_serializer_class method)
OR
Use one serialzer and decide field type according to action type:
class ProfileCreateSerializer(ModelSerializer):
class Meta:
model = Profile
fields = ('id', 'user', 'name', 'address')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.context['request'].method == 'GET': # or whatever condition you want to use
self.fields['user'] = UserSerializer()
you can do, that you can create your custom serializer fields
class CustomForeignKeyField(serializers.PrimaryKeyRelatedField):
def get_queryset(self):
return self.queryset
def to_representation(self, value):
value = super().to_representation(value)
user = User.objects.get(pk=value)
return UserSerializer(user).data
in serializer you can use this fiedls
class ProfileSerializer(ModelSerializer):
user = CustomForeignKeyField(queryset=User.objects.all())
class Meta:
model = Profile
fields = ('id', 'user', 'name', 'address')
it will accept value as int and return the response in json
I am working on a Django project with the django_rest_framework. I am trying to create a serializer that returns a user with all the groups that he belongs to as a simple array.
For example:
{
"username": "John Doe",
"groups": "[Group1, Group2]"
}
My current configuration however returns the groups as objects and adds attribute names, so my previous example unfortunately returns as follows:
{
"username": "John Doe",
"groups": "[{"name":"Group1"},{"name":"Group2"}]"
}
Are you able to get the achieve the result that I want with the django_rest_framework? Here are my serializers:
serializers.py
from django.contrib.auth.models import User, Group
from rest_framework import serializers
class GroupSerializer(serializers.ModelSerializer):
class Meta:
model = Group
fields = ('name',)
class UserSerializer(serializers.ModelSerializer):
groups = GroupSerializer(many=True)
class Meta:
model = User
fields = ('username', 'groups')
You can use SerializerMethodField.
class UserSerializer(serializers.ModelSerializer):
groups = SerializerMethodField()
class Meta:
model = User
fields = ('username', 'groups')
def get_groups(self, obj):
return [group.name for group in obj.groups]
Try to override GroupSerializer's to_representation method:
class GroupSerializer(serializers.ModelSerializer):
class Meta:
model = Group
fields = ('name',)
def to_representation(self, obj):
return obj.name
Didn't test it, please check if this work.
I have my User saved in two different models, UserProfile and User. Now from API perspective, nobody really cares that these two are different.
So here I have:
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('url', 'username', 'first_name', 'last_name', 'email')
and
class UserPSerializer(serializers.HyperlinkedModelSerializer):
full_name = Field(source='full_name')
class Meta:
model = UserProfile
fields = ('url', 'mobile', 'user','favourite_locations')
So in UserPSerializer the field user is just a link to that resource. But form a User perspective there is really no reason for him to know about User at all.
Is there some tricks with which I can just mash them together and present them to the user as one model or do I have to do this manually somehow.
You can POST and PUT with #kahlo's approach if you also override the create and update methods on your serializer.
Given a profile model like this:
class Profile(models.Model):
user = models.OneToOneField(User)
avatar_url = models.URLField(default='', blank=True) # e.g.
Here's a user serializer that both reads and writes the additional profile field(s):
class UserSerializer(serializers.HyperlinkedModelSerializer):
# A field from the user's profile:
avatar_url = serializers.URLField(source='profile.avatar_url', allow_blank=True)
class Meta:
model = User
fields = ('url', 'username', 'avatar_url')
def create(self, validated_data):
profile_data = validated_data.pop('profile', None)
user = super(UserSerializer, self).create(validated_data)
self.update_or_create_profile(user, profile_data)
return user
def update(self, instance, validated_data):
profile_data = validated_data.pop('profile', None)
self.update_or_create_profile(instance, profile_data)
return super(UserSerializer, self).update(instance, validated_data)
def update_or_create_profile(self, user, profile_data):
# This always creates a Profile if the User is missing one;
# change the logic here if that's not right for your app
Profile.objects.update_or_create(user=user, defaults=profile_data)
The resulting API presents a flat user resource, as desired:
GET /users/5/
{
"url": "http://localhost:9090/users/5/",
"username": "test",
"avatar_url": "http://example.com/avatar.jpg"
}
and you can include the profile's avatar_url field in both POST and PUT requests. (And DELETE on the user resource will also delete its Profile model, though that's just Django's normal delete cascade.)
The logic here will always create a Profile model for the User if it's missing (on any update). With users and profiles, that's probably what you want. For other relationships it may not be, and you'll need to change the update-or-create logic. (Which is why DRF doesn't automatically write through a nested relationship for you.)
I just came across this; I have yet to find a good solution particularly for writing back to my User and UserProfile models. I am currently flattening my serializers manually using the SerializerMethodField, which is hugely irritating, but it works:
class UserSerializer(serializers.HyperlinkedModelSerializer):
mobile = serializers.SerializerMethodField('get_mobile')
favourite_locations = serializers.SerializerMethodField('get_favourite_locations')
class Meta:
model = User
fields = ('url', 'username', 'first_name', 'last_name', 'email', 'mobile', 'favourite_locations')
def get_mobile(self, obj):
return obj.get_profile().mobile
def get_favourite_locations(self, obj):
return obj.get_profile().favourite_locations
This is horribly manual, but you do end up with:
{
"url": "http://example.com/api/users/1",
"username": "jondoe",
"first_name": "Jon",
"last_name": "Doe",
"email": "jdoe#example.com",
"mobile": "701-680-3095",
"favourite_locations": [
"Paris",
"London",
"Tokyo"
]
}
Which, I guess is what you're looking for.
I would implement the modifications on the UserPSerializer as the fields are not going to grow:
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('url', 'username', 'first_name', 'last_name', 'email')
class UserPSerializer(serializers.HyperlinkedModelSerializer):
url = serializers.CharField(source='user.url')
username = serializers.CharField(source='user.username')
first_name = serializers.CharField(source='user.first_name')
last_name = serializers.CharField(source='user.last_name')
email = serializers.CharField(source='user.email')
class Meta:
model = UserProfile
fields = ('mobile', 'favourite_locations',
'url', 'username', 'first_name', 'last_name', 'email')