Working with serializers
Serialization is the process of converting complex data types into a format that can be easily transmitted and stored. In this guide, we will show you how to use Django Rest Framework serializers with ShipFast.
Create Django models
For the sake of this example let's use the Customer and Product models inside store
package.
Those models are related through a foreign key, where a customer can have multiple products associated with them.
The HashidAutoField is used for the primary key of both models to provide a unique and secure identifier for the objects.
Check out the "How to create a new Django app and model in back-end?" guide if you've missed it.
import hashid_field
from django.db import models
class Customer(models.Model):
id = hashid_field.HashidAutoField(primary_key=True)
email = models.EmailField()
class Product(models.Model):
id = hashid_field.HashidAutoField(primary_key=True)
name = models.CharField(max_length=255)
price = models.DecimalField(max_digits=8, decimal_places=2)
customer = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name='products')
We have also added a related_name
attribute to the foreign key field.
This allows us to access the related products of a customer by calling "customer.products" instead of "product_set" which is the default related name.
Create a Django Rest Framework serializer
If you're not familiar with Django Rest Framework serializers here's the official documentation.
In this example, we'll create a serializer for the "Product" model:
from hashid_field import rest as hidrest
from rest_framework import serializers
from .models import Product, Customer
class ProductSerializer(serializers.ModelSerializer):
id = hidrest.HashidSerializerCharField(source_field="store.Product.id", read_only=True)
customer = serializers.PrimaryKeyRelatedField(
queryset=Customer.objects.all(),
pk_field=rest.HashidSerializerCharField(),
write_only=True,
)
class Meta:
model = Product
fields = ('id', 'name', 'price', 'customer')
The id
is a custom serializer field used for hashing the ID of a model.
Here's a breakdown of the key components of this field:
HashidSerializerCharField
: This is a custom serializer field provided by thehashid_field
package that hashes an integer ID field using the Hashids algorithm and returns the hashed value as a string.source_field="store.Product.id"
: This attribute specifies the name of the source field that the serializer field should use to hash the ID. In this case, it's set to"store.Product.id"
, which means that the serializer field should use theid
field of theProduct
model.
customer
is a field that is used to relate the instance being serialized to a Customer
instance in the database.
PrimaryKeyRelatedField
is a serializer field that represents the target of the relationship as a primary key.
The queryset
parameter specifies the set of objects that can be selected.
pk_field
specifies the serializer field to use when serializing the primary key value. In this case, HashidSerializerCharField
is used to serialize the primary key as a Hashid string.
Data validation
More detailed overview on validation can be found in Django Rest Framework Validation section.
Field validators
Field validators are functions that take a value, perform some checks on it, and return the value if it's valid or raise a serializers.ValidationError
if it's not.
You can write a custom validation method for the name
field in the ProductSerializer
to disallow special characters.
Here's an example implementation:
from hashid_field import rest as hidrest
from rest_framework import serializers
from .models import Product, Customer
class ProductSerializer(serializers.ModelSerializer):
id = hidrest.HashidSerializerCharField(source_field="store.Product.id", read_only=True)
customer = serializers.PrimaryKeyRelatedField(
queryset=Customer.objects.all(),
pk_field=rest.HashidSerializerCharField(),
write_only=True,
)
class Meta:
model = Product
fields = ('id', 'name', 'price', 'customer')
def validate_name(self, value):
"""
Check that the name field does not contain special characters.
"""
special_chars = "!@#$%^&*()-+?_=,<>/"
if any(char in special_chars for char in value):
raise serializers.ValidationError("Product name cannot contain special characters.")
return value
In the above code, we have added a validate_name
method to the ProductSerializer
to validate the name
field.
The method checks if the value
parameter contains any special characters and raises a serializers.ValidationError
if it does.
Object validation
The validate
method in DRF serializers allows you to perform custom validations on multiple fields at once.
It provides an opportunity to perform more complex validation logic that involves multiple fields or attributes.
Here's an example implementation on how to override the validate
method of the ProductSerializer
to validate both the name
and price
fields.
The method checks if the price
field is greater than zero and raises a serializers.ValidationError
if it's not.
It also checks if the name
field only contains letters and numbers
The validate
method is called after all the individual field-level validations have been performed.
from hashid_field import rest as hidrest
from rest_framework import serializers
from .models import Product, Customer
class ProductSerializer(serializers.ModelSerializer):
id = hidrest.HashidSerializerCharField(source_field="store.Product.id", read_only=True)
customer = serializers.PrimaryKeyRelatedField(
queryset=Customer.objects.all(),
pk_field=rest.HashidSerializerCharField(),
write_only=True,
)
class Meta:
model = Product
fields = ('id', 'name', 'price', 'customer')
def validate(self, data):
"""
Check that the price is greater than zero and the name only contains letters and numbers.
"""
if data['price'] <= 0:
raise serializers.ValidationError("Price must be greater than zero.", code="invalid_price")
if not data['name'].isalnum():
raise serializers.ValidationError("Product name can only contain letters and numbers.", code="invalid_product_name")
return data
Returning errors to frontend
Field validators
In a GraphQL API, if a validation error is raised inside a field validator, the response will include an error message formatted like this:
{
"message": "GraphQlValidationError",
"locations": [{"line": 3, "column": 11}],
"path": ["createProduct"],
"extensions": {
"name": [{"message": "Product name cannot contain special characters.", "code": "invalid"}]
}
}
See the "Errors format: Field validators" for a detailed description.
Object validation
When it comes to errors raised inside the validate
method, the error message will look like below:
{
"message": "GraphQlValidationError",
"locations": [{"line": 3, "column": 11}],
"path": ["createProduct"],
"extensions": {
"non_field_errors": [
{"message": "Price must be greater than zero.", "code": "invalid_price"},
{"message": "Product name can only contain letters and numbers.", "code": "invalid_product_name"}
]
}
}
See the "Errors format: Object validation" for a detailed description.