[Answered ]-Django REST get query with symmetric fields

1👍

You can make a QuerySet that renders with:

from django.db.models import Q

class ComparisonView(viewsets.ModelViewSet):  
    serializer_class = ComparisonSerializer   
    queryset = Comparison.objects.all()  
    
    def get_queryset(self):
        h1 = self.request.query_params.get('hash1')
        h2 = self.request.query_params.get('hash2')
        return self.queryset.filter(
            Q(hash1=h1, hash2=h2) |
            Q(hash1=h2, hash2=h1)
        )

Finally, one other small annoyance is that ideally I want to return a single object, while currently filter returns a list containing a single object for a unique hash pair.

This is because you use the list function of the ModelViewSet. The viewset will retrieve an object if you specify a primary key (of the Comparison).

What you can do is working with a RetrieveAPIView [drf-doc], and override the .get_object() method [drf-doc]:

from django.db.models import Q
from django.shortcuts import get_object_or_404
from rest_framework.generics import RetrieveAPIView

class ComparisonView(RetrieveAPIView):  
    serializer_class = ComparisonSerializer   
    queryset = Comparison.objects.all()  
    
    def get_object(self):
        h1 = self.request.query_params.get("hash1")
        h2 = self.request.query_params.get("hash2")
        return get_object_or_404(
            self.queryset,
            Q(hash1=h1, hash2=h2) |
            Q(hash1=h2, hash2=h1)
        )

You might want to add an extra constraint that hash1 is always less than or equal to hash2, this thus prevent retrieving two items, since it might be possible that there exists a Comparison for hash1=h1 and hash2=h2; and one for hash1=h2 and hash2=h1. You can add a CheckConstraint with:

from django.db.models import Q, F

class Comparison(models.Model):
    result = models.FloatField()
    hash1 = models.CharField(max_lModelViewSetength=100)
    hash2 = models.CharField(max_length=100)

    class Meta:
        constraints=[
            models.UniqueConstraint(fields=['hash1','hash2'], name='compared'),
            models.CheckConstraint(check=Q(hash1__lte=F('hash2')), name='symmetry')
        ]

In that case the create logic should determine which of the two hashes is the smallest. It also means that you can query simply by first ordering h1 and h2 with:

from rest_framework.generics import RetrieveAPIView

class ComparisonView(RetrieveAPIView):  
    serializer_class = ComparisonSerializer   
    queryset = Comparison.objects.all()  
    
    def get_object(self):
        h1 = self.request.query_params.get('hash1')
        h2 = self.request.query_params.get('hash2')
        if h1 is not None and h2 is not None:
            h1, h2 = sorted((h1, h2))
        return get_object_or_404(
            self.queryset, hash1=h1, hash2=h2
        )

Leave a comment