Home > Software engineering >  Is there a way to filter out items from RelatedManager in a ModelViewSet?
Is there a way to filter out items from RelatedManager in a ModelViewSet?

Time:04-30

I'm using DRF for a simple API, and I was wondering if there's a way to achieve this behavior:

  • I've got two models similar to the following:
class Table(models.Model):
    name = models.CharField(max_length=100)
    ...

class Column(models.Model):
    original_name = models.CharField(max_length=100)
    name = models.CharField(max_length=100, blank=True, null=True)
    ...
    table = models.ForeignKey(Table, on_delete=models.CASCADE, related_name="columns")
  • And their serializers as follows:
class ColumnSerializer(serializers.HyperlinkedModelSerializer):
    table = serializers.HyperlinkedRelatedField(
        read_only=True, view_name="table-detail"
    )

    class Meta:
        model = Column
        fields = ["url", "name", "table"]

class TableSerializer(serializers.HyperlinkedModelSerializer):
    dataset = serializers.HyperlinkedRelatedField(
        read_only=True, view_name="dataset-detail"
    )
    tags = serializers.SlugRelatedField(
        many=True, slug_field="name", queryset=Tag.objects.all()
    )
    columns = ColumnSerializer(many=True, read_only=True)

    class Meta:
        model = Table
        fields = [
            "url",
            "name",
            ...
            "columns",
        ]
  • This returns me an output similar to
{
    ...
    "results": [
        {
            "url": "http://0.0.0.0:8001/api/tables/1/",
            "name": "some-name",
            "columns": [
                {
                    "url": "http://0.0.0.0:8001/api/columns/1/",
                    "name": "id",
                    "table": "http://0.0.0.0:8001/api/tables/1/"
                },
    ...
}

which is totally fine. But what I'd really want to do is, if a Column has name=None, it's filtered out from every API ViewSet. I've managed to do it on the ColumnViewSet by doing queryset = queryset.filter(name__isnull=False), but I can't do it for the TableViewSet or others that might show a Column list.

I've tried tinkering with the ColumnSerializer, but the best I could get from it was to show nulls on the Column list.

I wonder if there's a way of hiding those.

EDIT 1: Adding my ViewSets

class TableViewSet(viewsets.ModelViewSet):
    serializer_class = TableSerializer

    def get_queryset(self):
        queryset = Table.objects.all().order_by("name")
        # some query_params filtering
        return queryset

class ColumnViewSet(viewsets.ModelViewSet):
    serializer_class = ColumnSerializer

    def get_queryset(self):
        queryset = Column.objects.all().order_by("id")
        queryset = queryset.filter(name__isnull=False)
        # some query_params filtering
        return queryset

CodePudding user response:

You can work with a Prefetch object [Django-doc] to filter the related object collection, so:

from django.db.models import Prefetch

class TableViewSet(viewsets.ModelViewSet):
    serializer_class = TableSerializer

    def get_queryset(self):
        queryset = Table.objects.prefetch_related(
            Prefetch('columns', Column.objects.filter(name__isnull=False))
        ).order_by('name')
        # some query_params filtering
        return queryset
  • Related