You are dealing with the issue of nested serialization . Please read the related documentation before proceeding.
Your question relates to a complex area of โโissues in DRF, and therefore requires some explanation and discussion to understand how serializers and sets of views work.
I will discuss the problem of presenting the data of your Subject and Class through the same endpoint, using different data representations for different HTTP methods, because this is usually a problem when people want to represent their data in nested formats; they want to provide their user interfaces with enough information for clean use, for example with dropdown selectors.
By default, Django and Django REST Framework (DRF) refer to related objects (your Subject and Class ) by their primary keys. By default, these are Django auto-incrementing keys. If you want to access them in other ways, you must write overrides for this. There are several different options.
- The first option is to specialize the creation and update logic: refer to your class through some other attribute and manually write a search to create, or set the key that you are accessing as the primary key of your class. You can set your class name, UUID, or any other attribute as the primary key of the database, if this is a unique single field (the reason I mention is that at the moment you are looking up your
Class models with a compound search, which consists of a composite (number, letter) search term). For example, you can override the search for related objects in your view's create method (for POST), but then you will have to handle similar searches in your view's update method (for PUT and PATCH). - Secondly, in my opinion, the preferred option is to specialize the representations of your object: usually access your classes through the primary key and create one serializer to read the object and one to create and update it. This can easily be achieved by inheriting the serializer class and overriding your views. Use the primary key in POST, PUT, PATCH, etc. To update class references and foreign keys.
Option 1. Look at the class and theme with an arbitrary attribute when creating and updating:
Install your nested class serializers read-only:
class ExamSerializer(serializers.ModelSerializer): subject = SubjectSerializer(read_only=True) clazz = ClassSerializer(read_only=True)
Override your create view to find related classes in freeform attributes. Also, check out how DRF implements this with mixins . You will also have to override your update method in order to handle them correctly, and consider PATCH support (partial update) in addition to PUT (update) if you choose this route:
def create(self, request): # Look up objects by arbitrary attributes. # You can check here if your students are participating # the classes and have taken the subjects they sign up for. subject = get_object_or_404(Subject, title=request.data.get('subject')) clazz = get_object_or_404( Class, number=request.data.get('clazz_number') letter=request.data.get('clazz_letter') ) serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) serializer.save(clazz=clazz, subject=subject) headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
Option 2: Specialize your serializers for reading and writing and use primary keys; This is an idiomatic approach:
First, define the default ModelSerializer that you want to use for normal operations (POST, PUT, PATCH):
class ExamSerializer(serializers.ModelSerializer) class Meta: model = Exam fields = ('id', 'subject', 'clazz', 'topic', 'date', 'details')
Then replace the required fields with the type of view you want to give them for reading data (GET):
class ExamReadSerializer(ExamSerializer): subject = SubjectSerializer(read_only=True) clazz = ClassSerializer(read_only=True)
Then specify the serializer that you want to use for the various operations of your ViewSet. Here we return the nested Subject and Class data for read operations, but use only their primary keys for update operations (much simpler):
class ExamViewSet(viewsets.ModelViewSet): queryset = Exam.objects.all() def get_serializer_class(self):
As you can see, option 2 seems rather less complicated and error prone and contains only 3 lines of handwritten code on top of DRF (implementation of get_serializer_class). Just let the logic of the framework figure out the views and create and update objects for you.
I saw many other approaches, but so far they have created the least amount of code that I would support, and have carefully taken advantage of the development of DRF.