Say I have these models:
class House(models.model):
address = models.CharField(max_length=100)
class HouseImage(models.Model):
image = models.ImageField(...)
house = models.ForeignKey(House, related_name='images')
And this serializers:
class HouseImageSerializer(serializers.ModelSerializer):
class Meta:
model = HouseImage
fields = ('image', )
class HouseSerializer(serializers.ModelSerializer):
images = HouseImageSerializer(many=True, required=False)
class Meta:
model = House
fields = ('address', 'images', )
And this view:
class HouseDetails(generics.RetrieveUpdateDestroyAPIView):
serializer_class = HouseSerializer
queryset = House.objects.all()
I am making to calls from my frontend. One creates the House (and it works) and the second one is supposed to send the images. Therefore, in the second call I am making a PATCH request (with axios) and my request.data is {'images': [InMemoryUploadedFile1, InMemoryUploadedFile2,...]}
Questions:
Am I doing correct so far by defining the images field on HouseSerializer? If it's correct, what else should I do? Because I know I need to somehow map each of the image in images list in my request.data to the image field in HouseImage.
Is there a better approach in implementing this?
de facto you did not patch you House model, you try to create new HouseImage, so in my mind best way is new APIView for HouseImage model.
in serializers:
class HouseImageSerializer(serializers.ModelSerializer):
class Meta:
model = HouseImage
fields = ('image', 'house')
# ^^^^^^
in view
class HouseImageDetails(generics.RetrieveUpdateDestroyAPIView):
serializer_class = HouseImageSerializer
queryset = HouseImage.objects.all()
in HouseSerializer you will have extra field house in the images does it matter?
for your case you can try to override the update
class HouseSerializer(serializers.ModelSerializer):
images = HouseImageSerializer(many=True, required=False)
class Meta:
model = House
fields = ('address', 'images', )
def update(self, instance, validated_data):
request = self.context.get('request')
images = request.data.get('images', []) if request else []
for img in images:
data = {'image': img, 'house': instance}
hiSerializer = HouseImageSerializer(data)
if hiSerializer.is_valid():
hiSerializer.save()
return super(HouseSerializer, self).update(instance, validated_data)
Related
I have an example model which has a fk relation with user model and Blog model. Now I have a get api which only requires certain fields of user to be displayed.
My model:
class Example(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
null=True,
related_name="user_examples",
)
blog = models.ForeignKey(
Blog,
on_delete=models.CASCADE,
null=True,
related_name="blog_examples",
)
/................./
Now my view:
class ExampleView(viewsets.ModelViewSet):
queryset = Example.objects.all()
serializer_class = ExampleSerializer
def list(self, request, *args, **kwargs):
id = self.kwargs.get('pk')
queryset = Example.objects.filter(blog=id)
serializer = self.serializer_class(queryset,many=True)
return Response(serializer.data,status=200)
My serializer:
class ExampleSerializer(serializers.ModelSerializer):
class Meta:
model = Example
fields = ['user','blog','status']
depth = 1
Now when I call with this get api, I get all example objects that is required but all the unnecessary fields of user like password, group etc . What I want is only user's email and full name. Same goes with blog, I only want certain fields not all of them. Now how to achieve this in a best way??
You will have to specify the required fields in nested serializers. e.g.
class BlogSerializer(serializers.ModelSerializer):
class Meta:
model = Blog
fields = ['title', 'author']
class ExampleSerializer(serializers.ModelSerializer):
blog = BlogSerializer()
class Meta:
model = Example
fields = ['user','blog','status']
are you setting depth in serializer's init method or anywhere else? beacause ideally it should only display id's and not anything else. if yes then set depth to zero and use serializer's method field to return data that you need on frontend. I can provide you with example code samples
In my app I have a nested many to many relation like the following:
Models.py
class ReturnKitsProducts(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE)
quantity = models.IntegerField(default=0)
class ReturnKits(models.Model):
kit = models.ForeignKey(Kit, on_delete=models.CASCADE)
quantity = models.IntegerField(default=0)
items = models.ManyToManyField(ReturnKitsProducts)
class Return(models.Model):
transaction_date = models.DateTimeField(default=datetime.now)
transaction_no = models.IntegerField(default=0, blank=True, null=True)
flow = models.ForeignKey(Flow, on_delete=models.CASCADE)
kits = models.ManyToManyField(ReturnKits)
warehouse = models.ForeignKey(Warehouse, on_delete=models.CASCADE)
In this ReturnKitsProducts is connected to ReturnKits as M2M and ReturnKits is connected to Return as M2M. I have handles only single level of M2M serialization for updatation and creation like this:
Serializers.py
class ReturnKitsSerializer(serializers.ModelSerializer):
class Meta:
model = ReturnKits
fields = "__all__"
class ReturnSerializer(serializers.ModelSerializer):
kits = ReturnKitsSerializer(many=True)
class Meta:
model = Return
fields = "__all__"
def create(self, validated_data):
items_objects = validated_data.pop('kits', None)
prdcts = []
for item in items_objects:
i = ReturnKits.objects.create(**item)
prdcts.append(i)
instance = Return.objects.create(**validated_data)
print("prdcts", prdcts)
instance.items.set(prdcts)
return instance
But I am not sure how to do serialization in the above mentioned scenario. Please Help!!
You can try something like this:
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = ReturnKitsProducts
fields = "__all__"
class ReturnKitsSerializer(serializers.ModelSerializer):
items = ItemSerializer(many=True) # same as
class Meta:
model = ReturnKits
fields = "__all__"
class ReturnSerializer(serializers.ModelSerializer):
kits = ReturnKitsSerializer(many=True)
class Meta:
model = Return
fields = "__all__"
def create(self, validated_data):
items_objects = validated_data.pop('kits', None)
instance = Return.objects.create(**validated_data)
for item in item_objects:
return_products = item.pop('items')
i = ReturnKits.objects.create(**item)
for data in return_products:
return_product = ReturnKitsProducts.objects.create(**data)
i.items.add(return_product)
instance.items.add(i)
return instance
What I did was pulling out the data from the validated_data dictionary and create instances as necessary.
While the other answer works (for create and get), it is always better to use serializers to save data when possible. In future you may need some kind of custom validation or you may need to override default create method, for nested serializer.
If you don't do following, you will miss out all functions serializer class provides and may need to write your all code on your own in create function of your main serializer, which will be dirty.
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = ReturnKitsProducts
fields = "__all__"
class ReturnKitsSerializer(serializers.ModelSerializer):
items = ItemSerializer(many=True) # same as
class Meta:
model = ReturnKits
fields = "__all__"
def validate_items(self, value):
serializer = ItemSerializer(data=value, many=True)
if serializer.is_valid():
return serializer # this will be finally received and used in create function of main serializer
else:
raise serializers.ValidationError(serializer.errors)
def create(self, validated_data):
items = validated_data.pop('items')
r = ReturnKits.objects.create(**validated_data)
for item in items:
r.items.add(item.save()) # save each serializer (already validated) and append saved object
return r
class ReturnSerializer(serializers.ModelSerializer):
kits = ReturnKitsSerializer(many=True)
def validate_kits(self, value):
serializer = ReturnKitsSerializer(data=value, many=True)
if serializer.is_valid():
return serializer
else:
raise serializers.ValidationError(serializer.errors)
class Meta:
model = Return
fields = "__all__"
def create(self, validated_data):
# IMP: At this point all the data (including nested) will have been validated, so no error will throw when saving the data
kits = validated_data.pop('kits', None)
instance = Return.objects.create(**validated_data)
for kit in kits:
i = kits.save() # save each serializer (already validated) and get a object
instance.kits.add(i)
return instance
This code will work for create and get. If you want to update all your data at once, you should develop a approach.
You can try approach I use:
Updating data with its nested data
If the dictionary contains id field, the corresponding nested data will be updated.
If the dictionary does not contain id, field , new nested data will be created.
If the dictionary contains only id as key, the nested data will be deleted.
There is no need to provide nested data which do not need to be updated or deleted.
https://github.com/SagarKAdhikari/drf-nested-relations : A library where you can validate and save/update nested data to any depth, though it works for generic relation and foreign keys only for now( since only that was required in my project). You can try understanding the code and implment, if your nested relations are much deeper than at current and you have too many of them.
I'm working on a real estate app and want the listings to show only the first image of the Listing. Currently it is showing all images.
class Image(models.Model):
photo = models.ImageField(blank=True, upload_to=get_image_filename)
listing = models.ForeignKey(Listing, on_delete=models.CASCADE)
class ImageSerializerForListingList(serializers.ModelSerializer):
photo = serializers.ImageField()
class Meta:
model = Image
fields = ('photo', )
class ListingListSerializer(serializers.HyperlinkedModelSerializer):
image = ImageSerializerForListingList(many=True, required=False, source='image_set')
class Meta:
model = Listing
fields = ('url', 'address', 'image', )
def get_first_image(self, obj):
image = obj.image_set.all().first()
return ImageSerializerForListingList(image, many=True).data
This is still showing all images,
Any advice on this?
solution
"Although you define the get_first_image method on the ListingListSerializer, you are not using it anywhere.
The methods defined on the serializers only get used implicitly when there is a corresponding SerializerMethodField defined on the serializer."
class ListingListSerializer(serializers.HyperlinkedModelSerializer):
first_image = serializers.SerializerMethodField()
class Meta:
model = Listing
fields = ('url', 'address', 'first_image', )
"""Much like our web app our listing view should only show one Image"""
def get_first_image(self, obj):
image = obj.image_set.first()
return FirstImageSerializer(image).data
I am pretty stuck working with DRF for the first time. I am looking to upload multiple Images to a single real estate Listing.
My image model
class Image(models.Model):
photo = models.ImageField(blank=True, upload_to=get_image_filename)
listing = models.ForeignKey(Listing, on_delete=models.CASCADE)
my Image, Listing, and Listing detail serializers
class ListingSerializer(serializers.HyperlinkedModelSerializer):
image_set = ImageSerializerForListingDetail(many=True, required=False)
class Meta:
model = Listing
fields = ['url', 'address', 'image_set', ]
class ListingDetailSerializer(serializers.HyperlinkedModelSerializer):
user = AccountSerializer(read_only=True)
image_set = ImageSerializerForListingDetail(many=True, required=False)
class Meta:
model = Listing
fields = '__all__'
depth = 1
class ImageSerializerForListingDetail(serializers.ModelSerializer):
image_url = serializers.SerializerMethodField()
class Meta:
model = Image
fields = ('image_url', )
def get_image_url(self, listing):
return listing.photo.url
My view
class ListingViewSet(viewsets.ModelViewSet):
queryset = Listing.objects.all()
serializer_class = ListingSerializer
detail_serializer_class = ListingDetailSerializer
permission_classes = [IsOwnerOrReadOnly, ]
'''Show detailed Listing view'''
def get_serializer_class(self):
if self.action == 'retrieve':
if hasattr(self, 'detail_serializer_class'):
return self.detail_serializer_class
return super(ListingViewSet, self).get_serializer_class()
I am having trouble figuring out how to upload/edit multiple Images, to a single Listing, and where to override. I would like it possible when both creating and editing listings. Any help is greatly appreciated. Thanks!
This specific use case does have a section dedicated in the docs for "Writable nested objects"
https://www.django-rest-framework.org/api-guide/serializers/#writable-nested-representations
If you're supporting writable nested representations you'll need to write .create() or .update() methods that handle saving multiple objects.
The doc should cover the appropriate example you are looking for!
It seems like this should do the trick, and then I still need to work on the update method.
class ListingSerializer(serializers.HyperlinkedModelSerializer):
user = UsernameSerializer(read_only=True)
image_set = ImageSerializerForListingDetail(many=True, required=False,
read_only=True)
class Meta:
model = Listing
exclude = ('url', )
depth = 1
def create(self, validated_data):
images_data = validated_data.pop('image_set')
listing = Listing.objects.create(**validated_data)
for image_data in images_data:
Image.objects.create(listing=listing, **image_data)
return listing
Is there anything special that needs to me done with Images, that my one big concern? I always thought I needed to request.FILES, but I am seeing that that has been depreciated in DRF 3?
How to limit images of request.user to be linked with node. I wish I could do something like:
photo = models.ForeignKey(
Image,
limit_choices_to={'owner': username},
)
but request.user rather than username and I don't want to use local threads.
models.py
class Node(models.Model):
owner = models.ForeignKey(User)
content = models.TextField()
photo = models.ForeignKey(Image)
class Image(models.Model):
owner = models.ForeignKey(User)
file = models.ImageField(upload_to=get_upload_file_name)
serializers.py
class ImageSerializer(serializers.ModelSerializer):
owner = serializers.Field('owner.username')
class Meta:
model = Image
fields = ('file', 'owner')
class NodeSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Node
fields = ('content', 'photo', 'owner')
I would deal with this by overriding get_serializer_class to dynamically return a serializer class at runtime, setting the choices option on the field there:
def get_serializer_class(self, ...):
user = self.request.user
owner_choices = ... # However you want to restrict the choices
class ImageSerializer(serializers.ModelSerializer):
owner = serializers.Field('owner.username', choices=owner_choices)
class Meta:
model = Image
fields = ('file', 'owner')
return ImageSerializer
You can create a custom foreign key field and define get_queryset() method there to filter related objects to only those of your user. The current user can be retrieved from the request in the context:
class UserPhotoForeignKey(serializers.PrimaryKeyRelatedField):
def get_queryset(self):
return Image.objects.filter(owner=self.context['request'].user)
class NodeSerializer(serializers.HyperlinkedModelSerializer):
photo = UserPhotoForeignKey()
class Meta:
model = Node
fields = ('content', 'photo', 'owner')
This example is using Django REST Framework version 3.
class CustomForeignKey(serializers.PrimaryKeyRelatedField):
def get_queryset(self):
return Table.objects.filter(is_active=True)
class Serializer(serializers.ModelSerializer):
(...)
table= CustomForeignKey()
class Meta:
(...)
even more easy is :
class Serializer(serializers.ModelSerializer):
(...)
table = serializers.PrimaryKeyRelatedField(queryset=Table.objects.filter(is_active=True))
class Meta:
(...)
Because I am sure this logic will be used across an entire Django application why not make it more generic?
class YourPrimaryKeyRelatedField(serializers.PrimaryKeyRelatedField):
def __init__(self, **kwargs):
self.model = kwargs.pop('model')
assert hasattr(self.model, 'owner')
super().__init__(**kwargs)
def get_queryset(self):
return self.model.objects.filter(owner=self.context['request'].user)
serializers.py
class SomeModelSerializersWithABunchOfOwners(serializers.ModelSerializer):
photo = YourPrimaryKeyRelatedField(model=Photo)
categories = YourPrimaryKeyRelatedField(model=Category,
many=True)
# ...
from rest_framework import serializers
class CustomForeignKey(serializers.PrimaryKeyRelatedField):
def get_queryset(self):
return Table.objects.filter(user=self.context['request'].user)
# or: ...objects.filter(user=serializers.CurrentUserDefault()(self))
class Serializer(serializers.ModelSerializer):
table = CustomForeignKey()