DRF: How to create serializer that uses a field to search if object with given ID exists in database, and if so use this object as foreign key

I have following 2 models:

class Student(BaseModel):
        external_id = models.CharField(max_length=30)
        name= models.CharField(max_length=30)
        last_name= models.CharField(max_length=30)


class Book(BaseModel):
        student = models.OneToOneField(
            "student.Student",
            related_name="books",
            on_delete=models.CASCADE,
        )
        book_name = models.CharField(max_length=30)
        book_type = models.CharField(max_length=30)

I want to create such serializer that will create a Book only if there is a Student with provided external_id.

class BooksSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = [
            "student",
            "book_name",
            "book_type",
        ]

    def create(self, validated_data):
        student_external_id = validated_data.pop('student')
        st = Student.objects.get(
            external_id=student_external_id
        )
        book = Book.objects.create(
            student=st,
            book_name=validated_data["book_name"],
            book_type=validated_data["book_type"],
        )

        return book

I have written this code but obviously it doesn’t work. I used nested serializers to do similar things but I don’t want to create Student this time, only check if exisit’s and the use it as relation, otherwise throw error that such student doesn’t exists. Maybe it’s bad approach first of all? Maybe I shouldn’t try to create separete endpoint for books, and try to use patch method for Student and inside of it create object Book to given Student?

Answer

You can use PrimaryKeyRelatedField . It checks sended value exists, if not exists raises ValidationError. Try this:

class BooksSerializer(serializers.ModelSerializer):
    student = serializers.PrimaryKeyRelatedField(queryset=Student.objects.all())

    class Meta:
        model = Book
        fields = [
            "student",
            "book_name",
            "book_type",
        ]

    def create(self, validated_data):
        book = Book.objects.create(
            student=student,
            book_name=validated_data["book_name"],
            book_type=validated_data["book_type"],
        )

        return book

If you want to use different field from primary key, then you can use SlugRelatedField. You should add your lookup field with slug_field key.

class BooksSerializer(serializers.ModelSerializer):
    student = serializers.SlugRelatedField(queryset=Student.objects.all(), slug_field='external_id')

    class Meta:
        model = Book
        fields = [
            "student",
            "book_name",
            "book_type",
        ]

    def create(self, validated_data):
        book = Book.objects.create(
            student=validated_data["student"],
            book_name=validated_data["book_name"],
            book_type=validated_data["book_type"],
        )

        return book