Search code examples
django-rest-frameworkdjango-filters

rest_framework custom order_by


let's see if I can ask the questions correctly?

model.py

class Point(models.Model):

    name = models.CharField(
        "nome del sistema",
        max_length=128,
    )
    x = models.FloatField(
        "cordinata spaziale x",
    )
    y = models.FloatField(
        "cordinata spaziale y",
    )
    z = models.FloatField(
        "cordinata spaziale z",
    )
    distance = float(0)

    
    def __str__(self) -> str:
        return f"{self.name}"

    @property
    def get_distance(self):
        return self.distance

    @get_distance.setter
    def get_distance(self, point):
        """
        ritorna la distanza che ce tra due sistemi
        """
        ad = float((point.x-self.x) if point.x > self.x else (self.x-point.x))
        bc = float((point.y-self.y) if  point.y > self.y else (self.y- point.y))
        af = float((point.z-self.z) if point.z > self.z else (self.z-point.z))
        return (pow(ad,2)+pow(bc,2)+pow(af,2))**(1/2)

    class Meta:
        verbose_name = "point"
        verbose_name_plural = "points"

in the particular model there are two defs which calculate, save and return the distance with respect to the point we pass them

wenws.py

class PointViewset(viewsets.ModelViewSet):
    """
    modelo generico per un systema
    """
    queryset = Point.objects.all()
    serializer_class = PointSerializers
    filterset_class = PointFilter

in the wenws not that much particular to explain and base base the only thing we have to say and that as filters I use 'django_filters'

filters.py

import django_filters as filters
import Point

class CustomOrderFilter(filters.CharFilter):

    def filter(self, qs:QuerySet, value):
        if value in ([], (), {}, '', None):
            return qs
        try:
            base =  qs.get(name=value)
            for point in qs:
                point.get_distance = base
            qs = sorted(qs, key= lambda x: x.get_distance)  
        except Point.DoesNotExist:
            qs = qs.none()
        return qs


class PointFilter(filters.rest_framework.FilterSet):

    security = filters.ChoiceFilter(choices=security_choices
    point= CustomCharFilter(
        label = "point"
    )

    class Meta:
        model = Point
        fields = {
            'name':['exact'],
        }  

now the complicated thing with 'CustomCharFilter' I pass in the http request the name of the system which then returns to me in the filter as value after I check that it is not empty and I start with returning the point that I have passed with base = qs.get ( name = value) to then calculate and save the distance for each point with point.get_distance = base '' on the inside of the for, at the end I reorder the QuerySet with qs = sorted (qs, key = lambda x: x.get_distance) '' the problem that both with this way and with another that I have tried the QuerySet it 'transforms' into a list and this does not suit me since I have to return a QuerySet in the order of here I want. I don't know how to do otherwise, since order_by I can't use it since the distance is not inside the database can someone help me?


Solution

  • So the problem is that you want to filter from a python function which cant be done in a query, as they only speak SQL.

    The easy slow solution is to do the filtering in python, this might work if there are just a few Points.

    points = list(Point.objects.all())
    points.sort(cmp=comparison_function)
    

    The actual real solution is to port that math to Djangos ORM and annotate your queryset with the distance to a given point, this is quite an advanced query, If you tell us your database server you use we can probably help you with that too.

    ps. There is an abs() function in python to get absolute value instead of the if/else in get_distance