Django Reuse Model Serializers Custom Validators

38 views Asked by At

I have the below serializer.py file. I have validate_semester() present in SubjectSerializer(), and I want to reuse the same method in my StudentSerializer() class.
validate_semester() is verifying that semester id exists in semester table before assigning it to the 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 the model level as well, but it doesn't seem to be working out. I am getting an IntegrityError.

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])
3

There are 3 answers

0
Premkumar On

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"]
0
Askar Musaev On

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'
              )
0
Sireen Gothadiya On

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.