Now, when returning a product, we can return a related object like a collection. You can do this in a few ways
First Way
First we import the collection class, and then we create a collection field, but we give the field a queryset, which is used for looking up collections
from rest_framework import serializersfrom decimal import Decimalfrom store.models import Product, Collection
class ProductSerializer(serializers.Serializer): id = serializers.IntegerField() title = serializers.CharField(max_length=255) unit_price = serializers.DecimalField(max_digits=6, decimal_places=2) price_with_tax = serializers.SerializerMethodField(method_name='calculate_tax') '''note that we give it a primarykeyrelatedfield to specify the relationship''' collection = serializers.PrimaryKeyRelatedField( queryset=Collection.objects.all() )
def calculate_tax(self, product: Product): return product.unit_price * Decimal(1.1)Now in our browser we can now see the id of our collection being returned with each product. This is one way we can serialize a relationship
Another way
Another way will be to return the collection as a string. ie instead of returning the collection id, we will return the actual name of the collection. With this one, instead of using a PrimaryKeyRelatedField you use a StringRelatedField - what this does is that Django converts each collection into a string object and return it
class ProductSerializer(serializers.Serializer): id = serializers.IntegerField() title = serializers.CharField(max_length=255) unit_price = serializers.DecimalField(max_digits=6, decimal_places=2) price_with_tax = serializers.SerializerMethodField(method_name='calculate_tax') collection = serializers.StringRelatedField()
def calculate_tax(self, product: Product): return product.unit_price * Decimal(1.1)Now when we try to list all products, it takes forever because when we are getting all products, we dont load their collections, so django is adding and extra sql query to load each collection for each product. We can fix this by loading the collection of each product when we get the products. in our view function returning all products, see below
@api_view()def product_list(request): queryset = Product.objects.select_related('collection').all() serializer = ProductSerializer(queryset, many=True) return Response(serializer.data)Note how we use the “select_related” method and pass the ‘collection’ field to it as we are getting all products.
Third Way
Another way to serialize a relationship is by including a nested object. In this scenario we can return the collection as an object in itself. This is how we do it. First create a collection serializer with the fields you want to be returned in the collection object, and then pass that serializer to the collection field in the product serializer class. see below
class CollectionSerializer(serializers.Serializer): id = serializers.IntegerField() title = serializers.CharField(max_length=255)
class ProductSerializer(serializers.Serializer): id = serializers.IntegerField() title = serializers.CharField(max_length=255) unit_price = serializers.DecimalField(max_digits=6, decimal_places=2) price_with_tax = serializers.SerializerMethodField(method_name='calculate_tax') collection = CollectionSerializer()
def calculate_tax(self, product: Product): return product.unit_price * Decimal(1.1)Another Way
Now instead of returning an object, we can return a hyperlink that contains an endpoint to viewing that collection. This one has a few more steps.
First in our product serializer, we return a hyperlink which contains 2 arguments. The first one is a queryset of us getting all collections, and the second one is the “view_name” aka the endpoint. see below
class ProductSerializer(serializers.Serializer): id = serializers.IntegerField() title = serializers.CharField(max_length=255) unit_price = serializers.DecimalField(max_digits=6, decimal_places=2) price_with_tax = serializers.SerializerMethodField(method_name='calculate_tax') collection = serializers.HyperlinkedRelatedField( queryset=Collection.objects.all(), view_name='collection-detail' )Then you go to your urls file and add a new urlpattern for the endpoint you’re about to create
urlpatterns = [ path('collections/<int:pk>/', views.collection_detail)]Now create the collection_detail view function
@api_view()def collection_detail(request, pk): return Response('ok')In our urls.py file, we are mapping the url to the collection_detail function. Now we can actually give the mapping a name, and that name has to match with the “view_name” you specified in the product serializer
urlpatterns = [ path('collections/<int:id>/', views.collection_detail, name='collection-detail')]If you get a request error, add a context to the product serializer in your view function
@api_view()def product_list(request): queryset = Product.objects.select_related('collection').all() serializer = ProductSerializer(queryset, many=True, context={'request': request}) #we set request = request return Response(serializer.data)