django rest framework override save() method - python

When I override the save() method, the create() method is called inside, but when I add a read-only field uuid, the field is not serialized.
serializer.py
class AwsUploadFileSerializer(serializers.ModelSerializer):
extract_file_name = serializers.CharField(source='extract_file.name', read_only=True)
class Meta:
model = ExtractAWSFile
fields = [
'uuid',
'extract_file',
'extract_file_name'
]
extra_kwargs = {
'extract_file': {'write_only': True}
}
def save(self, **kwargs):
instance: ExtractAWSFile = super().create(self.validated_data)
res = upload_file_to_aws(instance.extract_file.path)
if not res:
instance.extract_file.delete()
instance.delete()
return instance
response
{
"extract_file_name": "tets3.jpg"
}
So I'm trying to call the save() method of the parent class so that the uuid field can be serialized, but there is something wrong with the file name field I wrote earlier and it will take the path with it instead of just displaying the name.
serializer.py
class AwsUploadFileSerializer(serializers.ModelSerializer):
extract_file_name = serializers.CharField(source='extract_file.name', read_only=True)
class Meta:
model = ExtractAWSFile
fields = [
'uuid',
'extract_file',
'extract_file_name'
]
extra_kwargs = {
'extract_file': {'write_only': True}
}
def save(self, **kwargs):
instance = super().save(**kwargs)
res = upload_file_to_aws(instance.extract_file.path)
if not res:
instance.extract_file.delete()
instance.delete()
return instance
response
{
"uuid": "c4aea74e-3748-4c05-8d6c-2413b1eebcc6",
"extract_file_name": "extractAWS/2022/10/08/tets3.jpg"
}
Why is that? I'm wondering what I should do after save() to properly display the uuid field

def save(self, **kwargs):
res = upload_file_to_aws(instance.extract_file.path)
if not res:
instance.extract_file.delete()
instance.delete()
instance = super().save(**kwargs) #call the save method end of your code

Related

Django Nested Serializer - Return Null Field if condition is fullfiled

I have a nested serializes, which content I need to return as Null in case of the parent-serializer field "is_profile_private" (a boolean) is True.
I tried using get_queryset in order to filter the User Profile but no progress was made.
Tried using SerializerMethordField() and get_profile() but Django complained about UserProfileSerializer type of object not being allowed to be serialized.
serializers.py
class UserProfileSerializer(UserSerializer):
height = serializers.SerializerMethodField()
class Meta:
model = UserProfile
fields = (
"bio",
"gender",
"custom_gender",
"non_binary_list",
"birthdate",
"avatar",
"height",
"hometown",
"zodiac_sign",
"language",
)
#staticmethod
def get_height(obj):
return {"value": obj.height, "unit": obj.height_unit}
class SimpleUserSerializer(UserSerializer):
profile = UserProfileSerializer(source="user", required=False)
class Meta:
model = User
fields = (
"id",
"name",
"username",
"is_profile_private",
"date_joined",
"profile",
)
views.py
class UserProfileAPIView(RetrieveModelMixin, UpdateModelMixin, GenericViewSet):
lookup_field = "id"
queryset = User.objects.all()
serializer_class = SimpleUserSerializer
http_method_names = ["get"]
#staticmethod
def get(request, *args, **kwargs):
return User.objects.get(id=str(request.data))
You can use SerializerMethodField:
class SimpleUserSerializer(UserSerializer):
profile = serializers.SerializerMethodField()
class Meta:
model = User
fields = (
"id",
"name",
"username",
"is_profile_private",
"date_joined",
"profile",
)
def get_profile(self, obj):
if obj.is_profile_private:
return None
return UserProfileSerializer(obj.user).data
please note that you should return serializer's data, not serializere itself.

Extended User Model not updating

Hii I'am new to Django rest frame work and was preparing API's So this is my
models.py
class User(auth.models.User, auth.models.PermissionsMixin):
def __str__(self):
return "#{}".format(self.username)
class User_log(models.Model):
user = models.OneToOneField(auth.models.User,on_delete=models.CASCADE,related_name='user_logs')
fullname=models.CharField(max_length=255)
fb_login=models.BooleanField(default=False)
def __str__(self):
return self.fullname
serializers.py
class userSerializers(serializers.ModelSerializer):
fullname = serializers.StringRelatedField(source='user_logs.fullname',read_only=False)
fb=serializers.BooleanField(source='user_logs.fb_login')
class Meta:
model = User
fields=('id','username','email','fullname','fb')
related_fields = ['user_logs']
def update(self, instance, validated_data):
# Handle related objects
for related_obj_name in self.Meta.related_fields:
print('exe')
print(instance,validated_data)
# Validated data will show the nested structure
data = validated_data.pop(related_obj_name)
related_instance = getattr(instance, related_obj_name)
# Same as default update implementation
for attr_name, value in data.items():
setattr(related_instance, attr_name, value)
related_instance.save()
return super(userSerializers,self).update(instance, validated_data)
viewset.py
class Userview(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class=userSerializers
Now the problem: whenever i try to update fullname column through json request
{ "id": 2,"username": "karam","email": "karam#83ideas.com","fullname": "karm","fb": true } i am getting this error "POST /accounts/accounts/2/ HTTP/1.1" 405 41
and as in serializers.py update method i have printed the validated data instead of this
karam {'username': 'karam', 'email': 'karam#83ideas.com', 'user_logs': {'fullname':'karam','fb_login': True}}
i am getting this
karam {'username': 'karam', 'email': 'karam#83ideas.com', 'user_logs': {'fb_login': True}}
SO any idea how to resolve it?
Instead of using StringRelatedField in serializers.py use CharField.
class userSerializers(serializers.ModelSerializer):
fullname=serializers.CharField(source='user_logs.fullname')
fb=serializers.BooleanField(source='user_logs.fb_login')
class Meta:
model = User
fields=('id','username','email','fullname','fb')
related_fields = ['user_logs']

How to save a ModelSerializer with a foreign Key in Django Rest Framework

I have these serializers
class OwnerSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = [
'id',
'name'
]
class PetSerializer(serializers.ModelSerializer):
owner = OwnerSerializer(many=False, read_only=False)
class Meta:
model = Pet
fields = [
'id',
'name',
'owner'
]
def create(self, validated_data):
"""
Create and return a new `Pet` instance, given the validated data.
"""
owner_data = validated_data.pop('owner', None)
if owner_data:
owner = User.objects.get(**owner_data)
validated_data['owner'] = owner
return Pet.objects.create(**validated_data)
This is my ViewSet:
class PetViewSet(viewsets.ModelViewSet):
"""
ModelViewSet for model pets
"""
queryset = Pet.objects.all()
serializer_class = PetSerializer
def create(self, request, *args, **kwargs):
request.data['owner'] = {'id': request.user.id, 'name': request.user.name}
pet_serializer = self.get_serializer(data=request.data)
pet_serializer.is_valid(raise_exception=True)
self.perform_create(pet_serializer)
headers = self.get_success_headers(pet_serializer.data)
return Response(pet_serializer.data, status=status.HTTP_201_CREATED, headers=headers)
But when storing it, all the data is saved, but in the created function the owner object does not arrive, it is shown as 'owner': OrderedDict ()}
So when I return the saved object, I get it with the owner with the default id, although the user I get in the view is 'owner': {'id': 10, 'name': 'My name'}} ):
My Json Response:
{
"id": 26,
"name": "My pet",
"owner": {
"id": 1
}
}
What am I doing wrong? Thank you
It looks to me like it's returning normalized data of the owner object, rather than the relation.
Instead of owner = OwnerSerializer() try something like:
owner = PrimaryKeyRelatedField(queryset=Owner.objects.all())
I made the next changes in my Serializers,the main changes was in the OwnerSerializer, 'cause the id was read only for default.
class OwnerSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = [
'id',
'name'
]
extra_kwargs = {
'id': {
'read_only': False,
'required': True
}
}
class PetSerializer(serializers.ModelSerializer):
owner = OwnerSerializer(many=False, read_only=False)
class Meta:
model = Pet
fields = [
'id',
'name',
'owner'
]
def create(self, validated_data):
"""
Create and return a new `Pet` instance, given the validated data.
"""
owner_data = validated_data.pop('owner', None)
if owner_data:
owner = User.objects.get(**owner_data)
validated_data['owner'] = owner
return Pet.objects.create(**validated_data)
.get returns one item and if it matched more, It raises an expception.
owner = User.objects.get(**owner_data), remove the trailing [0]
EDIT: Since this was not your problem, I'll tell you the problem
You're overriding create which have the validated_data, You assumed that the owner is validated but it may wrong.
Do the same logic in a method named validate_owner(self, value) in which value is the value you passed for owner in the POST before it gets validated.

Rest framework: different serializer for input and output data on post/put operations

Lets say I have these models:
class Download(MPTTTimeStampedModel):
endpoint = models.ForeignKey(EndPoint, related_name="downloads",)
class EndPoint(TimeStampedModel):
name = models.CharField(max_length=100, verbose_name=_(u"Nombre"))
url = models.CharField(max_length=2000, verbose_name=_(u"Url"))
These serializers:
class DownloadSerializer(serializers.ModelSerializer):
class Meta:
model = Download
fields = ('id', 'endpoint')
def create(self, validated_data):
...
def update(self, validated_data):
...
class EndPointSerializer(serializers.ModelSerializer):
class Meta:
model = EndPoint
fields = ('id', 'name', 'url')
def create(self, validated_data):
...
def update(self, validated_data):
...
And this generic api view:
class DownloadList(generics.ListCreateAPIView):
queryset = Download.objects.all()
serializer_class = DownloadSerializer
This will allow me to create a download by sending a json representation looking like this:
{
'id': null,
'endpoint': 19
}
And upon creation, the web service will send me back the data with the id from the database. Now, I actually want the web service to send me back not just the endpoint id but a complete representation of the object, Something like this:
{
'id': null,
'endpoint': {
'id': 19,
'name': 'my endpoint',
'url': 'http://www.my-endpoint.com/'
}
}
I would manage this with this serializer:
class DownloadDetailedSerializer(DownloadSerializer):
endpoint = EndPointSerializer(many = False, read_only=False)
And now the actual question: how do i tell my generic view to use this last serializer for the returned data while keeping the original DownloadSerializer for the input?
EDIT: as #neverwalkeralone suggested the way to go is creating a custom field and overriding to_representation method. But that lead me to an exception in the line serializer = EndPointSerializer(value), and after some testing I found out that it would be better to inherit my custom field from RelatedField. That implies overriding to_internal_value too. So here is what finally got the job done:
class EndPointField(serializers.RelatedField):
def to_representation(self, value):
serializer = EndPointSerializer(value)
return serializer.data
def to_internal_value(self, instance):
endpoint = EndPoint.objects.get(pk=instance)
return endpoint
def get_queryset(self):
return EndPoint.objects.all()
You can define custom field, use to_representation method to customize output format:
class EndPointField(serializers.PrimaryKeyRelatedField):
def to_representation(self, value):
serializer = EndPointSerializer(value)
return serializer.data
def get_queryset(self):
return models.EndPoint.objects.all()
And use it in DownloadSerializer for endpoint field:
class DownloadSerializer(serializers.ModelSerializer):
endpoint = EndPointField()
class Meta:
model = Download
fields = ('id', 'endpoint')
UPD
Based on Kilian Perdomo Curbelo feedback EndPointField's to_representation value should be replaced with endpoint instance:
def to_representation(self, value):
endpoint = EndPoint.objects.get(pk=value.pk)
serializer = EndPointSerializer(endpoint)
return serializer.data

ModelViewSet - Update nested field

I'm currently working on Django with Django Rest Framwork.
I can't update my object within nested object field.
serializer.py
class OwnerSerializer(serializers.ModelSerializer):
class Meta:
model = Owner
fields = ('id', 'name')
class CarSerializer(serializers.ModelSerializer):
owner = ownerSerializer(many=False, read_only=False)
class Meta:
model = Car
fields = ('id', 'name', 'owner')
view.py
class OwnerViewSet(viewsets.ModelViewSet):
queryset = Owner.objects.all()
serializer_class = OwnerSerializer
class CarViewSet(viewsets.ModelViewSet):
serializer_class = CarSerializer
queryset = Car.objects.all()
def create(self, request):
serialized = self.serializer_class(data=request.DATA)
if serialized.is_valid():
serialized.save()
return Response(status=HTTP_202_ACCEPTED)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
When I do this :
Request URL:http://localhost:9000/api/v1/cars/1/?format=json
Request Method:PUT
Request Paylod :
{
"id":1,
"name": "TEST",
"ower": {
"id":1,
"name": "owner_test"
}
}
I get the following Response :
The `.update()` method does not support writable nestedfields by default.
Write an explicit `.update()` method for serializer `app.serializers.CarSerializer`,
or set `read_only=True` on nested serializer fields.
Knowing :
I want to keep the owner serialization on GET;
We can imagine the car nested by another object and ect...
How can I do if i want to change the owner when I update the car.
A little late, but, Try this,
class OwnerSerializer(serializers.ModelSerializer):
class Meta:
model = Owner
fields = ('id', 'name')
extra_kwargs = {
'id': {
'read_only': False,
'required': True
}
} #very important
def create(self, validated_data):
# As before.
...
def update(self, instance, validated_data):
# Update the instance
instance.some_field = validated_data['some_field']
instance.save()
# Delete any detail not included in the request
owner_ids = [item['owner_id'] for item in validated_data['owners']]
for owner in cars.owners.all():
if owner.id not in owner_ids:
owner.delete()
# Create or update owner
for owner in validated_data['owners']:
ownerObj = Owner.objects.get(pk=item['id'])
if ownerObje:
ownerObj.some_field=item['some_field']
....fields...
else:
ownerObj = Owner.create(car=instance,**owner)
ownerObj.save()
return instance
Just in-case someone stumbles on this
had the same error in my case but setting read_only to True fixed it for me.
owner = ownerSerializer(many=False, read_only=True)
Note that, this field won't appear in the form when posting data to the api.

Categories