Search code examples
pythondjangodjango-rest-frameworknested-routesdrf-nested-routers

DRF - Nested Routers - Create/Update nested object on POST/PUT/PATCH


I'm currently starting a simple Task App and I'm using Django 2.0.7, DRF 3.8.2 and drf-nested-routes 0.90.2

I have these models :

class Client(TimeStampedModel):
    """
    This model describes a client for the railroader. It can be created by the manager in the back office
    We have at least one internal Client, which is Seelk, for internal projects
    """
    name = models.CharField(max_length=255, unique=True)
    description = models.TextField(null=True)
    is_active = models.BooleanField(default=True)

    def __str__(self):
        return "{} : {}".format(self.name, self.description)

class Project(TimeStampedModel):
    """
    This model represents a project for a client, which we are gonna track actions on
    """
    client = models.ForeignKey(
        'railroader.Client', on_delete=models.PROTECT, related_name='projects')
    name = models.CharField(max_length=255, unique=True)
    description = models.TextField(null=True)
    is_active = models.BooleanField(default=True)

    def __str__(self):
        return "{} for client {}".format(self.name, self.client.name)

So, following the documentation of drf-nested-routers, I set up my serializers like this :

class ClientSerializer(serializers.ModelSerializer):
    class Meta:
        model = Client
        fields = ("id", "name", "description", "is_active", "projects")


class ProjectSerializer(serializers.ModelSerializer):
    class Meta:
        model = Project
        fields = ("id", "name", "description", "is_active")

And my viewsets like this :

class ClientViewset(viewsets.ModelViewSet):
    serializer_class = ClientSerializer
    permission_classes = (permissions.IsAuthenticated, AccountPermission)

    def get_queryset(self):
        queryset = Client.objects.all()
        is_active = self.request.query_params.get("is_active")
        if is_active:
            queryset = queryset.filter(is_active=is_active)
        return queryset


class ProjectViewset(viewsets.ModelViewSet):
    serializer_class = ProjectSerializer
    permission_classes = (permissions.IsAuthenticated, AccountPermission)

    def get_queryset(self):
        queryset = Project.objects.filter(client=self.kwargs["client_pk"])
        is_active = self.request.query_params.get("is_active")
        if is_active:
            queryset = queryset.filter(is_active=is_active)
        return queryset

And finally, my urls like so :

router = routers.SimpleRouter()
router.register(r"clients", viewsets.ClientViewset, base_name="clients")

projects_router = routers.NestedSimpleRouter(router, r"clients", lookup="client")
projects_router.register(r"projects", viewsets.ProjectViewset, base_name="projects")
urlpatterns = [
    re_path(r"^", include(router.urls)),
    re_path(r"^", include(projects_router.urls))
]

With this setup, I'm able to have the desired nested routes, but I can't have my routes to create a new object if I post on a nested route.

I've seen an issue on the github speaking about it, but as it was 2 years ago, I wonder if anyone knows how to do it.

Thanks in advance.


Solution

  • Found out I just forgot that returning an instance in DRF create method of the serializer would not create the object in base. At the end I have this serializer :

    class ProjectSerializer(serializers.ModelSerializer):
        def create(self, validated_data):
            client = Client.objects.get(pk=self.context["view"].kwargs["client_pk"])
            validated_data["client"] = client
            return Project.objects.create(**validated_data)
    
        class Meta:
            model = Project
            fields = ("id", "name", "description", "is_active")