Django Reuse Model Serializers Custom Validators
I have below serializer.py file. I have validate_semester() present in SubjectSerializer(), I want to reuse the same method in my StudentSerializer() class. Any help is appreciated. validate_semester() is verifying that semester id exists in semester table before assigning it to Student or Subject object.
from rest_framework import serializers
from .models import *
from .helpers import SEMESTER_NOT_STORED
class SemesterSerializer(serializers.Serializer):
id = serializers.IntegerField(required=False)
name = serializers.CharField(max_length=100, required=False)
class SubjectSerializer(serializers.ModelSerializer):
semester = SemesterSerializer()
class Meta:
model = Subject
fields = ["id", "name", "code", "semester"]
def create(self, validated_data):
semester = validated_data.pop("semester")
serializer = SemesterSerializer(semester)
subject = Subject.objects.create(
semester_id=serializer.data["id"], **validated_data
)
return subject
def validate_semester(self, value):
id = value.get("id")
try:
Semester.objects.get(id=id)
except Semester.DoesNotExist:
raise serializers.ValidationError({"id":[SEMESTER_NOT_STORED.format(id)]})
return value
class SemesterSubjectSerializer(serializers.ModelSerializer):
subjects = SubjectSerializer(many=True, read_only=True)
class Meta:
model = Semester
fields = ["id", "name", "subjects"]
class StudentSerializer(serializers.ModelSerializer):
semester = SemesterSerializer()
class Meta:
model = Student
fields = ["id", "name", "email", "semester"]
def create(self, validated_data):
semester = validated_data.pop("semester")
serializer = SemesterSerializer(semester)
student = Student.objects.create(
id=None, semester_id=serializer.data["id"], **validated_data
)
return student
I have try adding validation at model level as well, it doesn't seem to be working out. I am getting IntegrityError anyways.
from django.core.validators import MinLengthValidator, MaxLengthValidator
from .helpers import SEMESTER_NOT_STORED
def validate_semester(id):
semesters = list(Semester.objects.all().values_list("id", flat=True))
if id not in semesters:
raise ValueError({"id":[SEMESTER_NOT_STORED.format(id)]})
class Semester(models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return self.name
class Student(models.Model):
name = models.CharField(max_length=30)
email = models.EmailField()
semester = models.ForeignKey(Semester, on_delete=models.DO_NOTHING, default=None, validators=[validate_semester])
Create a common serializer with serializer PrimaryKeyRelatedField. It will only allow the existing semester instance. No need to validate manually. Inherit the common serializer class in both semester & student serializer classes. No need to write validate & create methods. Refer rest framework's serializer documentation for more knowledge about serializer Fields. click here for documentaion
from rest_framework import serializers
from .models import *
class CommonSemesterValidateSerializer(serializerz.ModelSerializer):
semester = serializers.PrimaryKeyRelatedField(queryset=Semester.objects.all(), required=True)
class Meta:
fields = ["id", "name", "semester"]
class SubjectSerializer(CommonSemesterValidateSerializer):
class Meta(CommonSemesterValidateSerializer.Meta):
model = Subject
fields = CommonSemesterValidateSerializer.Meta.fields + ["code"]
class StudentSerializer(CommonSemesterValidateSerializer):
class Meta(CommonSemesterValidateSerializer.Meta):
model = Student
fields = CommonSemesterValidateSerializer.Meta.fields + ["email"]
additionally to the above answer, I wouldn't recommend you inherit class Meta and write it manually, with this approach you may face some troubles in the future with serializer compatibility and it's less readable to other developers. Moreover, if you want to serializer your semester model in response, you can do like this:
semester = serializers.SemesterSerializer()
semester_id = serializers.PrimaryKeyRelatedField(
queryset=Semester.objects.all(), write_only=True,
required=True, source='semester'
)
I did find a simpler solution that does the job easily. let me know if it can get better or there is any cache with it.
class SemesterSerializer(serializers.Serializer):
id = serializers.ChoiceField(choices=Semester.objects.all().values_list('id', flat=True), required=False)
name = serializers.CharField(max_length=100, required=False)
Here we are using choice field to limit consumers to use any data other than stored ones.