Search code examples
djangodjango-rest-frameworkdatatablesdjango-datatable

How to query related object in DRF viewsets.ModelViewSet


I have a serialised model (Job) which I am using with datatables. The "Job" model is related to another model (Board) and here is where my problem is. I followed the doc here to filter jobs that are related to the "Board" model which is currently being viewed, but I can't get it to work as intended.

models.py

class Board(models.Model):
    name = models.CharField(_('board name'), max_length=256)
    slug = models.SlugField(_('unique url'), null=True, blank=True)
...


class Job(models.Model):
    board = models.ForeignKey(Board, on_delete=models.CASCADE, verbose_name=_('board'))
...

views.py

 class JobDataTablesViewSet(viewsets.ModelViewSet):
        queryset = Job.objects.all().order_by('-date_posted')
        serializer_class = JobDatatablesSerializer
        filter_backends = (DatatablesFilterBackend,)
        filterset_class = JobGlobalFilter
    
        def get_queryset(self):
            slug = self.kwargs['slug']
            queryset = Job.objects.filter(board__slug=slug)
            return queryset

urls.py

path('<slug:slug>/', views.BoardPublicView.as_view(), name='public-board')

Solution

  • For anyone with a similar issue, kindly take a look at alanjds/drf-nested-routers. This is what I used and it worked perfectly.

    Here is an overview of what I did.

    After installing the package with pip install drf-nested-routers.

    According to the docs,

    It is not needed to add this library in your Django project's settings.py file, as it does not contain any app, signal or model.

    In my app urls.py file, I created parent and child routers

    router = routers.SimpleRouter()
    router.register(r'boards', views.BoardViewSet, basename="boards_api_list")
    
    boards_router = routers.NestedSimpleRouter(router, r'boards', lookup='board')
    boards_router.register(r'jobs', views.JobViewSet, basename='board_jobs_list')
    
    urlpatterns = [
        path('api/', include(router.urls)),
        path('api/', include(boards_router.urls)),
    ]
    

    Then in my serializers.py, I created a HyperlinkedModelSerializer

    class BoardSerializer(serializers.HyperlinkedModelSerializer):
        owner = UserSerializer()
        jobs = NestedHyperlinkedRelatedField(
            many=True,
            read_only=True,
            view_name='jobs_view_name',
            parent_lookup_kwargs={'slug': 'board__slug'}
        )
    
        class Meta:
            model = Board
            fields = [...]
            extra_kwargs = {
                'url': {'view_name': 'my_custom_view_name', 'lookup_field': 'slug'},
                ...other desired kwargs...
            }
    
    class JobViewSet(serializers.ModelSerializer):
        ...standard serializer settings...
    

    I went on to my views.pyto create my viewsets and added the get_queryset params

    class JobViewSet(viewsets.ModelViewSet):
        queryset = Job.objects.all().order_by('-date_posted')
        serializer_class = JobSerializer
        filter_backends = (DatatablesFilterBackend,)
        filterset_class = JobGlobalFilter
    
        def get_queryset(self):
            return Job.objects.filter(board__slug=self.kwargs['board_slug'])
    

    Once you do these, you should be able to access your APIs via

    /parent_url/{{parent_pk}}/child_list/{{child_pk}}
    

    Now, because of my peculiar need (I wanted to use this on datatables to filter related objects), I added the URLs of my APIs to the head of my base.html so that the URLs can be loaded dynamically since I can't load such in my javascript file

    ...
    <head>
        <script>var boardListUrl= "{% url 'boards:boards_api_list-list' %}?format=datatables"</script>
        {% if board.slug %}
        <script>var jobListUrl= "{% url 'boards:board_jobs_list-list' board.slug %}?format=datatables"</script>
        {% endif %}
    <head>
    ...
    

    And in my main.py file, I did the following

     var table = $('#tableName').DataTable({
            ...
            "ajax": jobListUrl,
            ...
    });