Issue using nested serializer with django-rest-framework - python

I'm trying to create a nested serializer, UserLoginSerializer , composed of a UserSerializer and a NotificationSerializer, but I'm getting this error when it tries to serialize:
AttributeError: Got AttributeError when attempting to get a value for
field email on serializer UserSerializer. The serializer field
might be named incorrectly and not match any attribute or key on the
UserSerializer instance. Original exception text was:
'UserSerializer' object has no attribute 'email'.
Here is my models.py:
class Notification(models.Model):
kind = models.IntegerField(default=0)
message = models.CharField(max_length=256)
class User(AbstractUser):
username = models.CharField(max_length=150, blank=True, null=True)
email = models.EmailField(unique=True)
customer_id = models.UUIDField(default=uuid.uuid4, editable=False)
And my serializers.py:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = [
"id",
"first_name",
"last_name",
"email",
"customer_id"
]
class NotificationSerializer(serializers.ModelSerializer):
class Meta:
model = Notification
fields = [
"id",
"kind",
"message",
]
class UserLoginSerializer(serializers.Serializer):
user_info = UserSerializer(read_only=True)
notifications = NotificationSerializer(many=True, read_only=True)
The error occurs at the last line in this endpoint:
def get_login_info(self, request):
notifications = Notification.objects.filter(recipient=request.user)
serializer = UserLoginSerializer(
{
"user_info": UserSerializer(request.user),
"notifications": NotificationSerializer(notifications, many=True),
}
)
return Response(serializer.data)
What am I doing wrong?

You can use .data attribute.
def get_login_info(self, request):
notifications = Notification.objects.filter(recipient=request.user)
serializer = UserLoginSerializer(
{
"user_info": UserSerializer(request.user).data,
"notifications": NotificationSerializer(notifications, many=True).data,
}
)
return Response(serializer.data)
You must pass the data, not the serializer objects themselves. The data argument allows you to pass in a dictionary of data that will be used by the inner serializers to create a serialized representation. In this case, you will pass in the serialized data from the UserSerializer and NotificationSerializer to the UserLoginSerializer, which then returns the final serialized representation of the data.
Or, you may pass user and notifications directly as such:
def get_login_info(self, request):
notifications = Notification.objects.filter(recipient=request.user)
serializer = UserLoginSerializer(
{
"user_info": request.user,
"notifications": notifications
}
)
return Response(serializer.data)
Django Rest Framework model serializers have a to_representation method which converts the model instances to their dictionary representation, so in this case, the UserLoginSerializer will automatically use the UserSerializer and NotificationSerializer to serialize the user and notifications data. You can modify/change this method's behaviour by overriding it.
I hope this helps.

Related

Django set auto-user from context, user field not be null, JWT

In my case,I use JWT authentication, and when I create new "Post"(my model), I want automatically set author to user that request it.But when I do it, I got an error
{
"author": [
"This field is required."
]
}
I know,I'm not passing user, but I want to set it automatically, and I dont know how.
I just want to know how to avoid error, because when I pass value, that allows me to go ahead, the user is set automatically from context.
Serializer
class PostSerializer(FlexFieldsModelSerializer):
class Meta:
model = Post
fields = ('title','content',author','category','likedBy')
expandable_fields = {
'category': ('blogApi.CategorySerializer', {'many': True}),
'comments': ('blogApi.CommentSerializer', {'many': True}),
'likedBy': ('blogApi.LikedBySerializer', {'many': True}),
}
def create(self, validated_data):
user = self.context['request'].user
post = Post.objects.create(
author=user, title=validated_data['title'], content=validated_data['content'])
post.category.set(validated_data['category'])
return post
My create view
class PostCreate(generics.CreateAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer
permission_classes = [IsAuthenticated]
Model
class Post(models.Model):
title = models.CharField(max_length=255)
content = models.TextField()
category = models.ManyToManyField(Category, related_name='posts')
author = models.ForeignKey(User, on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
likedBy = models.ManyToManyField(User, related_name='posts', blank=True)
class Meta:
ordering = ['-created']
def __str__(self):
return self.title
and when I create
You can make author a read-only field, or if you're just using this serializer to create users and not retreive them. You can just remove 'author' from fields in the serializer meta.
Read-only field
from rest_framework import serializers
class PostSerializer(FlexFieldsModelSerializer):
author = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = Post
fields = ('title','content','author','category','likedBy')

Python Django Rest Framework: The `.create()` method does not support writable nested fields by default

I am using django rest framework to create an api endpoint. I am using the default user model django offers. I need to create a post which uses the user as a foreign key. A user called "author" in the post can have multiple posts.
This is an example of a post json.
[
{
"author": {
"id": 1,
"username": "sorin"
},
"title": "First Post",
"description": "Hello World!",
"created_at": "2020-08-05T14:20:51.981163Z",
"updated_at": "2020-08-05T14:20:51.981163Z"
}
]
This is the model.
class Post(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=255)
description = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
This is the serializer.
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username')
class PostSerializer(serializers.HyperlinkedModelSerializer):
author = UserSerializer()
class Meta:
model = Post
fields = ('author', 'title', 'description', 'created_at', 'updated_at')
I am getting the error "The .create() method does not support writable nested fields by default." when trying to make a post request using a "username", "title" and "description".
Any help to how to solve this?
I like hooking in the create function of the serializer for these kind of use cases.
Make sure your UserSerializer is set to read_only=True.
class PostSerializer(serializers.HyperlinkedModelSerializer):
author = UserSerializer(read_only=True)
class Meta:
model = Post
fields = ('author', 'title', 'description', 'created_at', 'updated_at')
def create(self, validated_data):
request = self.context['request']
author_data = request.data.get('author')
if author is None or not isinstance(author.get('id'), int):
raise ValidationError({'author': ['This field is invalid.']})
author_instance = get_object_or_404(User, id=author.get('id'))
return Post.objects.create(author=author_instance, **validated_data)

AttributeError - When using Django NestedFields serializers

I've 2 models:-
class Users(models.Model):
first_name = models.CharField(max_length=255)
middle_name = models.CharField(max_length=255)
class UserAddress(models.Model):
line1 = models.CharField(max_length=255)
country = models.CharField(max_length=255)
user = models.ForeignKey(Users)
The user model & user address model. Following are the 2 serializers.
class UserAddressSerializer(ModelSerializer):
class Meta:
model = UserAddress
exclude = ('id', 'user')
class UserSerializer(ModelSerializer):
address = UserAddressSerializer(many=True)
class Meta:
model = Users
fields = '__all__'
def create(self, validated_data):
address = validated_data.pop('address', [])
user = Users.objects.create(**validated_data)
for ad in address:
UserAddress.objects.create(user=user, **ad)
return user
The data I receive from the client is
{
"first_name": "string",
"last_name": "string",
"address": [{
"line1": "asd",
"country": "asd",
}],
}
This is how I create a new user and its corresponding address.
class UserCreate(GenericAPIView):
serializer_class = UserSerializer
def post(self, request, *args, **kwargs):
data = request.data
serializer = UserSerializer(data=data)
if not serializer.is_valid():
return
user = serializer.save()
response = {
'user_id': user.uuid
}
return
Now, upon getting the user details back, I receive an error saying
AttributeError: Got AttributeError when attempting to get a value for field `address` on serializer `UserSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `Users` instance.
Original exception text was: 'Users' object has no attribute 'address'.
This is how I get the details of the user, including the address.
class UserDetails(GenericAPIView):
queryset = Users.objects.all()
serializer_class = UserSerializer
lookup_field = 'uuid'
def get(self, request, uuid, *args, **kwargs):
user = Users.get_user(uuid)
if not user:
return
serializer = UserSerializer(instance=user)
return
I'd read this example of nested relationship, and am doing exactly the same way. why is the error coming up?
Also, can this code be shorten up more (in a nicer clean way) using some DRF mixins? If yes, then how?
I think the most simple solution for your case is: in model UserAddress add related_name='address'
class UserAddress(models.Model):
line1 = models.CharField(max_length=255)
country = models.CharField(max_length=255)
user = models.ForeignKey(Users, related_name='address')
# ^^^^^^^^^^^^^^^
or you can add sourse property in serializer:
class UserSerializer(ModelSerializer):
address = UserAddressSerializer(source='useraddress_set', many=True)
Serializer try to find attribute 'address' in the model User, but by default it is modelname underscore set (useraddress_set in your case), and you try other name, so you can set in the model or specify by source.
in the example you can look on models and find the related_name='tracks'

DRF: TypeError: <Model: Model object> is not JSON serializable

I have problem getting an object data from the request using django-rest-framework ModelViewset create(self, request) method.
Here is my model.py
class Heat(models.Model):
# Relationship Fields
animal = models.ForeignKey(
Animal, related_name='heats',
on_delete=models.CASCADE
)
# Fields
performer = models.CharField(max_length=25)
is_bred = models.BooleanField(default=False)
note = models.TextField(max_length=250, blank=True, null=True)
And this is my serializers.py
class HeatSerializer(serializers.ModelSerializer):
class Meta:
model = Heat
fields = [
'id',
'animal',
'performer',
'is_bred',
'note',
]
And here is where we can find the error in my ModelViewset:
class HeatViewSet(viewsets.ModelViewSet):
queryset = Heat.objects.all()
serializer_class = HeatSerializer
#transaction.atomic
def create(self, request):
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
print serializer.validated_data['animal']
If you look at this line of code:
// this purpose is to print the actual value of an animal object
// but it gave me the TypeError: <Animal: Animal object> is not JSON serializable.
print serializer.validated_data['animal']
If we include this one it works fine because this it just a normal field the same true to the other field except the animal which is a relational field:
print serializer.validated_data['performer']
Now when i do this the object was saved directly to the database.
serializer.save()
But django would prompt me the SAME TYPE ERROR!
When removing the entire create(self, request) the method flow works well without an error but i would like to perform many queries inside the create(self, request)!. This won't do right either please help!
When you serialize on Queryset the JSON result will be into a .data object
animal = AnimalSerializer(your_model.animal_set(), many=True, read_only=True)
return Response(animal.data)

django-rest-framework PUT manytomany through model

I have a Django Model w/ a m2m relationship that uses a through model:
models.py
class ModelA(models.Model):
name = models.CharField(max_length=64)
class ModelB(models.Model):
name = models.CharField(max_length=64)
other_models = models.ManyToManyField("ModelA", through="ModelC")
class ModelC(models.Model):
model_a = models.ForeignKey("ModelA", related_name="link_to_model_a")
model_b = models.ForeignKey("ModelB", related_name="link_to_model_b")
some_other_info = models.TextField()
class Meta:
unique_together = ("model_a", "model_b", )
I want to serialize this using django-rest-framework:
serializers.py
class ModelCSerializer(ModelSerializer):
class Meta:
model = ModelC
fields = ('id', 'model_a', 'model_b', 'some_other_info', )
class QModelBSerializer(ModelSerializer):
class Meta:
model = ModelB
fields = ('id', 'other_models', )
other_models = ModelCSerializer(many=True, required=False, source="link_to_model_b")
Now, for existing models the GET displays properly:
{
"id": 2,
"name": "i am an instance of model_b",
"other_models": [
{"id": 1, "model_a": 1,"model_b": 2, "some_other_info":"here is some other info"}
],
}
But, if I try to PUT some data it fails w/ a unique_together error. I thought that sending this as a PUT would cause an update (which shouldn't raise a unique_together error) not a create? Here is the code for PUT:
views.py
class ModelBDetail(APIView):
def put(self, request, pk, format=None):
model = ModelB.objects.get(id=pk)
serializer = ModelBSerializer(model, data=request.data, context={"request": request})
if serializer.is_valid(): # THIS IS RETURNING FALSE
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Any thoughts?
Django rest framework documentation states that it is up to developer to implement creates and updates for nested representations.
Although #Ivan was correct about writing my own create & update fn, the specific issue I was seeing was that the nested serialization did not have an instance attribute associated with it.
The new code looks like this:
serializers.py
class ModelBSerializer(ModelSerializer):
....
def update(self, model_instance, validated_data):
model_c_serializer = self.fields["other_models"]
model_c_data = validated_data.pop(model_c_serializer.source, [])
for key, value in validated_data.iteritems():
setattr(model_instance, key, value)
model_instance.save()
model_c_serializer.update(model_instance.link_to_model_b.all(),
model_c_data)
return model_instance
class ModelCSerializer(ModelSerializer):
...
def to_internal_value(self, data):
# this is as good a place as any to set the instance
try:
model_class = self.Meta.model
self.instance = model_class.objects.get(pk=data.get("id"))
except ObjectDoesNotExist:
pass
return super(ModelCSerializer, self).to_internal_value(data)
Basically, I call update for the nested serializers explicitly and I also force each nested serializer to check the data that is passed to them for an instance.

Categories

Resources