Catch-all field for unserialisable data of serializer
Question:
I have a route where meta-data can be POSTed. If known fields are POSTed, I would like to store them in a structured manner in my DB, only storing unknown fields or fields that fail validation in a JSONField
.
Let’s assume my model to be:
# models.py
from django.db import models
class MetaData(models.Model):
shipping_address_zip_code = models.CharField(max_length=5, blank=True, null=True)
...
unparseable_info = models.JSONField(blank=True, null=True)
I would like to use the built-in serialisation logic to validate whether a zip_code
is valid (5 letters or less). If it is, I would proceed normally and store it in the shipping_address_zip_code
field. If it fails validation however, I would like to store it as a key-value-pair in the unparseable_info
field and still return a success message to the client calling the route.
I have many more fields and am looking for a generic solution, but only including one field here probably helps in illustrating my problem.
Answers:
def validate_shipping_address_zip_code(self, value):
if value >= 5:
return value
else:
raise serializers.ValidationError("Message Here")
there’s much more validators in serializer look into more detail
https://www.django-rest-framework.org/api-guide/serializers/
You can use Django serializer that store fields that fail validation in JSONField.
Here is an example that worked for me:
from rest_framework import serializers
class MetaDataSerializer(serializers.ModelSerializer):
class Meta:
model = MetaData
fields = 'all'
def validate_shipping_address_zip_code(self, value):
if len(value) > 5:
raise serializers.ValidationError("Zip code must be 5 characters or less.")
return value
def create(self, validated_data):
unparseable_info = {}
for field, value in self.initial_data.items():
try:
validated_data[field] = self.fields[field].run_validation(value)
except serializers.ValidationError as e:
unparseable_info[field] = value
instance = MetaData.objects.create(**validated_data)
if unparseable_info:
instance.unparseable_info = unparseable_info
instance.save()
return instance
As you are looking for a generic solution, there are a few points that you should consider:
- Make sure not to place any
model-level
validations in your model as you want it to get saved irrespective of the validation status.
- Only validate on the
serializer-level
with custom validation methods.
- Make
unparseable_info
field read-only
as it is something we don’t want the user to send but receive.
- Make use of the
errors
dictionary provided by the serializer as it gets populated with field-specific errors when we call is_valid
.
This is how it might translate into code, inside models.py
:
class MetaData(models.Model):
shipping_address_zip_code = models.CharField(blank=True, null=True)
...
unparseable_info = models.JSONField(blank=True, null=True)
then inside serializers.py
:
class MetaDataSerializer(serializers.ModelSerializer):
class Meta:
model = MetaData
read_only_fields = ('unparseable_info', )
fields = '__all__'
# Write validators for all of your fields.
finally inside your views.py
method, something like this (you can do this inside serializer’s save
method as well):
meta_data = MetaDataSerializer(data=request.data)
if not meta_data.is_valid():
meta_data.unparseable_info = meta_data.errors
meta_data.save()
# Return meta_data.data in JSONResponse.
I have a route where meta-data can be POSTed. If known fields are POSTed, I would like to store them in a structured manner in my DB, only storing unknown fields or fields that fail validation in a JSONField
.
Let’s assume my model to be:
# models.py
from django.db import models
class MetaData(models.Model):
shipping_address_zip_code = models.CharField(max_length=5, blank=True, null=True)
...
unparseable_info = models.JSONField(blank=True, null=True)
I would like to use the built-in serialisation logic to validate whether a zip_code
is valid (5 letters or less). If it is, I would proceed normally and store it in the shipping_address_zip_code
field. If it fails validation however, I would like to store it as a key-value-pair in the unparseable_info
field and still return a success message to the client calling the route.
I have many more fields and am looking for a generic solution, but only including one field here probably helps in illustrating my problem.
def validate_shipping_address_zip_code(self, value):
if value >= 5:
return value
else:
raise serializers.ValidationError("Message Here")
there’s much more validators in serializer look into more detail
https://www.django-rest-framework.org/api-guide/serializers/
You can use Django serializer that store fields that fail validation in JSONField.
Here is an example that worked for me:
from rest_framework import serializers
class MetaDataSerializer(serializers.ModelSerializer):
class Meta:
model = MetaData
fields = 'all'
def validate_shipping_address_zip_code(self, value):
if len(value) > 5:
raise serializers.ValidationError("Zip code must be 5 characters or less.")
return value
def create(self, validated_data):
unparseable_info = {}
for field, value in self.initial_data.items():
try:
validated_data[field] = self.fields[field].run_validation(value)
except serializers.ValidationError as e:
unparseable_info[field] = value
instance = MetaData.objects.create(**validated_data)
if unparseable_info:
instance.unparseable_info = unparseable_info
instance.save()
return instance
As you are looking for a generic solution, there are a few points that you should consider:
- Make sure not to place any
model-level
validations in your model as you want it to get saved irrespective of the validation status. - Only validate on the
serializer-level
with custom validation methods. - Make
unparseable_info
fieldread-only
as it is something we don’t want the user to send but receive. - Make use of the
errors
dictionary provided by the serializer as it gets populated with field-specific errors when we callis_valid
.
This is how it might translate into code, inside models.py
:
class MetaData(models.Model):
shipping_address_zip_code = models.CharField(blank=True, null=True)
...
unparseable_info = models.JSONField(blank=True, null=True)
then inside serializers.py
:
class MetaDataSerializer(serializers.ModelSerializer):
class Meta:
model = MetaData
read_only_fields = ('unparseable_info', )
fields = '__all__'
# Write validators for all of your fields.
finally inside your views.py
method, something like this (you can do this inside serializer’s save
method as well):
meta_data = MetaDataSerializer(data=request.data)
if not meta_data.is_valid():
meta_data.unparseable_info = meta_data.errors
meta_data.save()
# Return meta_data.data in JSONResponse.