Search code examples
pythondjangodjango-modelsdjango-rest-frameworkdjango-filter

Having both .filter() on query_param and .all() functionality without adding another ViewSet and end-point


I'm trying to avoid making another end-point to handle this query, but thinking there isn't a way around it. Wanted to run it by ya'll before doing so.

Basically, I have Documents related to Customers and Products. I want to retrieve only the Documents for a specific Customer and Product.

Here are the views.py:

class DocumentsViewSet(viewsets.ModelViewSet):
    filter_backends = [StrictDjangoFilterBackend]
    filterset_fields = [
        'id',
        'filename',
    ]
    queryset = Documents.objects.all()
    serializer_class = DocumentsSerializer


class DocumentToCustomerViewSet(viewsets.ModelViewSet):
    filter_backends = [StrictDjangoFilterBackend]
    filterset_fields = [
        'id',
        'customer_id',
        'document_id'
    ]
    queryset = DocumentToCustomer.objects.all()
    serializer_class = DocumentToCustomerSerializer


class DocumentToProductViewSet(viewsets.ModelViewSet):
    filter_backends = [StrictDjangoFilterBackend]
    filter_fields = [
        'id',
        'product_id',
        'document_id'
    ]
    queryset = DocumentToProduct.objects.all()
    serializer_class = DocumentToProductSerializer

I'm thinking I can do something like this shorthand:

class DocumentsViewSet(viewsets.ModelViewSet):
    filter_backends = [StrictDjangoFilterBackend]
    filterset_fields = [
        'id',
        'filename'
    ]
    queryset = Documents.objects.filter(document_to_product__product_id=id, document_to_customer__customer_id=id)
    serializer_class = DocumentsSerializer

Which does seem to work when I tested it.

But then it I think I'd need to make another end-point to accommodate both the .all() and .filter()... I think.

Additionally, haven't been able to get the filter_fields = [] to work with the relationship either. Trying to do something like /api/documents/?customer_id=123&product_id=123.

I get a: TypeError: 'Meta.fields' must not contain non-model field names:

Here are the models for additional information:

class Documents(models.Model):
    id = models.UUIDField(primary_key=True, null=False)
    filename = models.CharField(max_length=255, null=False)

    class Meta:
        db_table = 'documents'

class DocumentToProduct(models.Model):

    product_id = models.ForeignKey(
        Product,
        on_delete=models.CASCADE,
        db_column='product_id'
    )
    document_id = models.ForeignKey(
        Documents,
        on_delete=models.CASCADE,
        db_column='document_id'
    )
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = 'document_to_product'
        unique_together = [['product_id', 'document_id']]


class DocumentToCustomer(models.Model):
    customer_id = models.ForeignKey(
        Customer,
        on_delete=models.CASCADE,
        db_column='customer_id'
    )
    document_id = models.ForeignKey(
        Documents,
        on_delete=models.CASCADE,
        db_column='document_id'
    )
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = 'document_to_customer'
        unique_together = [['document_id', 'customer_id']]

Any suggestions for how to keeps this clean or will just have to add another end-point to handle this request?


Solution

  • Ok, think I have it sorted out after getting some sleep, RTFM, and stumbling across this response. Really was quite easy...

    In order to get the relationships working with django-filter I had to add related_name attribute to the models like:

    class Documents(models.Model):
        id = models.UUIDField(primary_key=True, null=False)
        filename = models.CharField(max_length=255, null=False)
    
        class Meta:
            db_table = 'documents'
    
    class DocumentToProduct(models.Model):
    
        product_id = models.ForeignKey(
            Product,
            on_delete=models.CASCADE,
            db_column='product_id',
            related_name='document_to_product'
        )
        document_id = models.ForeignKey(
            Documents,
            on_delete=models.CASCADE,
            db_column='document_id',
            related_name='document_to_product'
        )
        created = models.DateTimeField(auto_now_add=True)
        updated = models.DateTimeField(auto_now=True)
    
        class Meta:
            db_table = 'document_to_product'
            unique_together = [['product_id', 'document_id']]
    
    
    class DocumentToCustomer(models.Model):
        customer_id = models.ForeignKey(
            Customer,
            on_delete=models.CASCADE,
            db_column='customer_id',
            related_name='document_to_customer'
        )
        document_id = models.ForeignKey(
            Documents,
            on_delete=models.CASCADE,
            db_column='document_id',
            related_name='document_to_customer'
        )
        created = models.DateTimeField(auto_now_add=True)
        updated = models.DateTimeField(auto_now=True)
    
        class Meta:
            db_table = 'document_to_customer'
            unique_together = [['document_id', 'customer_id']]
    

    Then I could update my filter_fields like so:

    class DocumentsViewSet(viewsets.ModelViewSet):
        filter_backends = [StrictDjangoFilterBackend]
        filterset_fields = [
            'id',
            'filename',
            'document_to_customer__customer_id',
            'document_to_product__product_id'
        ]
        queryset = Documents.objects.all()
        serializer_class = DocumentsSerializer
    
    
    class DocumentToCustomerViewSet(viewsets.ModelViewSet):
        filter_backends = [StrictDjangoFilterBackend]
        filterset_fields = [
            'id',
            'customer_id',
            'document_id'
        ]
        queryset = DocumentToCustomer.objects.all()
        serializer_class = DocumentToCustomerSerializer
    
    
    class DocumentToProductViewSet(viewsets.ModelViewSet):
        filter_backends = [StrictDjangoFilterBackend]
        filter_fields = [
            'id',
            'product_id',
            'document_id'
        ]
        queryset = DocumentToProduct.objects.all()
        serializer_class = DocumentToProductSerializer
    

    Which allowed me allowed me to pass query parameters instead of adding additional ViewSets, end-points, etc.

    /api/documents/?document_to_customer__customer_id=2&document_to_product__product_id=10
    

    This does what I was after and only returns the Documents for a specific customer and product.