Django REST Framework: Posting to URL with ID in URL

4.1k Views Asked by At

Given the following contrived example, how can I POST to a URL with an ID in path and have it resolve the model instance instead of including the model instance in the POST body itself?

urls.py

path("api/schools/<int:pk>/student/", views.CreateStudentView.as_view(), name="createStudent")

models.py

class School(models.Model):
    name = models.CharField(default="", max_length=128)
    address = models.CharField(default="", max_length=128)
    mascot = models.CharField(default="", max_length=128)


class StudentModel(models.Model):
    first_name = models.CharField(default="", max_length=128)
    last_name = models.CharField(default="", max_length=128)
    notes = models.CharField(default="", max_length=512)
    school = models.ForeignKey(School, on_delete=models.CASCADE)

serializers.py

class CreateStudentSerializer(serializers.ModelSerializer):
    class Meta:
        model = Student
        fields = ("first_name", "last_name")

views.py

class CreateStudentView(generics.CreateAPIView):
    serializer_class = CreateStudentSerializer
    queryset = Student.objects.all()

I want to be able to POST just the new student's first name and last name to the URL to create a new student in my database. However, because I don't provide a School object in the body of my POST I get an error. I was hoping it would be possible for my code to resolve which school I want to add the student to because the URL contains the ID.

I get an error when I POST the following body to /api/schools/1/student/. The school with an ID of 1 does exist in the database.

{
    "first_name": "John",
    "last_name": "Smith"
}
IntegrityError at /api/schools/1/student/

NOT NULL constraint failed: students_school.school_id
1

There are 1 best solutions below

3
On

The easiest way to do this is by overriding the create method of your CreateStudentView. URL parameters are stored in self.kwargs, so you can retrieve it from there, and inject it into your serializer:

class CreateStudentView(generics.CreateAPIView):
    serializer_class = CreateStudentSerializer
    queryset = Student.objects.all()

    def get_serializer(self, *args, **kwargs):
        serializer_class = self.get_serializer_class()
        kwargs["context"] = self.get_serializer_context()
        
        # Check if a pk was passed through the url.
        if 'pk' in self.kwargs:        
            modified_data = self.request.data.copy()
            modified_data['school'] = self.kwargs.get('pk')
            kwargs["data"] = modified_data
            return serializer_class(*args, **kwargs)

        # If not mind, move on.
        return serializer_class(*args, **kwargs)

Credits to this article, where you can read more about why this works.