Django как сортировать и пагинация вложенных ORM и не ORM объектов

Мне нужно сделать запрос для получения некоторых свойств и с помощью его category FK получить данные для месячной стоимости в другой таблице. С помощью результата нужно рассчитать стоимость по дням, частям, общей стоимости и дате исполнения.

все работает хорошо, но есть необходимость упорядочить данные по некоторым полям модели Property, упорядочить по общей цене и постранично вывести результат.

я могу заставить работать и порядок, и пагинацию, но не вместе.

Также, я хотел бы спросить, правильно ли размещать те расчеты с installments на serializer.py, или их следует перенести на viewset.py, или куда-то еще?

Вот мой код на данный момент:

ViewSet.py с пагинацией:

class MyViewSet(viewsets.ModelViewSet):
    queryset = MyModel.Property.objects.filter(status)\
        .prefetch_related('owner__user')\
        .prefetch_related('host__user')\
        .prefetch_related('category__user')

    serializer_class = MySerializer
    pagination_class = StandardResultsSetPagination
    filterset_class = MyFilterSet
    filter_backends = [filters.OrderingFilter, dj_filters.DjangoFilterBackend]


    def get_queryset(self):
        today = str(datetime.now())
        start_date = self.request.query_params.get('start_date')
        end_date = self.request.query_params.get('end_date')

        '''
        validate start_date and end_date
        '''

        return self.queryset

ViewSet.py с сортировкой:

class MyViewSet(viewsets.ModelViewSet):
    queryset = MyModel.Property.objects.filter(status)\
        .prefetch_related('owner__user')\
        .prefetch_related('host__user')\
        .prefetch_related('category__user')

    serializer_class = MySerializer
    # pagination_class = StandardResultsSetPagination
    filterset_class = MyFilterSet
    filter_backends = [filters.OrderingFilter, dj_filters.DjangoFilterBackend]

    def get_queryset(self):
        today = str(datetime.now())
        start_date = self.request.query_params.get('start_date')
        end_date = self.request.query_params.get('end_date')

        '''
        validate start_date and end_date
        '''

        return self.queryset

    def list(self, request, *args, **kwargs):
        response = super().list(request, args, kwargs)
        ordering = request.query_params.get('ordering')

        if ordering:
            response.data = sorted(
                response.data,
                key=operator.itemgetter(ordering.replace('-', ''),)
            )

            if "-" in ordering:
                response.data = sorted(
                    response.data,
                    key=lambda k: (k[ordering.replace('-', '')], ),
                    reverse=True
                )
            else:
                response.data = sorted(
                    response.data,
                    key=lambda k: (k[ordering], )
                )
        return Response(response.data)

Serializer.py

class MySerializer(serializers.ModelSerializer):
    monthly_price = serializers.SerializerMethodField()
    total_price = serializers.SerializerMethodField()

    class Meta:
        model = MyModel.Property

    def get_monthly_price(self, instance):
        start_date = self.context['request'].query_params['start_date']
        end_date = self.context['request'].query_params['end_date']

        months=helper.calc_months(start_date, end_date)
        '''
        calc months between start_date and end_date
        '''

        prices = MyModel.Category_month_price.objects.filter(
            category=instance.category_location.id,
            month__in=months)

        installments = helper.calc_installments(dates, start_date, prices)
        '''
        installments is a list of dicts with installments due dates and values like:
        [{
            "instalment": 0,
            "value": 1000,
            "due_date": 2022-01-01,
        }, ...]
        '''

        total_price = sum([x['value'] for x in installments])
        self.total_price = total_price

        return installments

    def get_total_price(self, instance):
        return self.total_price

Также попробовал whit Paginator в list Method, но не нашел как сделать ссылку на следующую и предыдущую страницы

from django.core.paginator import Paginator
from django.core.paginator import EmptyPage
from django.core.paginator import PageNotAnInteger

def list(self, ...) :
    """
    ...
    """
    paginator = Paginator(response.data, 25)
    page = self.request.GET.get('page')
    try:
        result = paginator.page(page)
    except PageNotAnInteger:
        result = paginator.page(1)
    except EmptyPage:
        result = paginator.page(paginator.num_pages)

    return Response({
        "count": "",
        "next": "",
        "previous": "",
        "results": []
    )

Пробовали ли вы использовать order_by в своем наборе запросов?

Я думаю, что это лучший способ решить эту проблему

class MyViewSet(viewsets.ModelViewSet):
    queryset = MyModel.Property.objects.filter(status)\
        .prefetch_related('owner__user')\
        .prefetch_related('host__user')\
        .prefetch_related('category__user')

    serializer_class = MySerializer
    # pagination_class = StandardResultsSetPagination
    filterset_class = MyFilterSet
    filter_backends = [filters.OrderingFilter, dj_filters.DjangoFilterBackend]
 ...

    def list(self, request, *args, **kwargs):
        response = super().list(request, args, kwargs)
        ordering = request.query_params.get('ordering')

        if ordering:
            response.data = self.queryset.order_by('category__user')

        return Response(response.data)

Я попытался показать форму для возврата данных с использованием order_by

может быть вы немного измените вашу модель, чтобы сделать запрос более простым

class MyModel(models.Model):
    category= models.ForeignKey(User, on_delete=models.CASCADE)
    # ...

    class Meta:
        ordering = ['category']

Ссылка : https://docs.djangoproject.com/en/4.0/ref/models/options/

Я нашел простое решение:

class MyViewSet(viewsets.ModelViewSet):
    queryset = MyModel.Property.objects.filter(status)\
        .prefetch_related('owner__user')\
        .prefetch_related('host__user')\
        .prefetch_related('category__user')

    serializer_class = MySerializer
    filterset_class = MyFilterSet
    filter_backends = [filters.OrderingFilter, dj_filters.DjangoFilterBackend]


    def get_queryset(self):
        today = str(datetime.now())
        start_date = self.request.query_params.get('start_date')
        end_date = self.request.query_params.get('end_date')

        '''
        validate start_date and end_date
        '''

        return self.queryset

    def list(self, request, *args, **kwargs):
        response = super().list(request, args, kwargs)
        response.request = self.request
        ordering = request.query_params.get('ordering')

        # ORDERING WORKING
        if ordering:
            response.data = sorted(
                response.data,
                key=operator.itemgetter(ordering.replace('-', ''),)
            )

            if "-" in ordering:
                response.data = sorted(
                    response.data,
                    key=lambda k: (k[ordering.replace('-', '')], ),
                    reverse=True
                )
            else:
                response.data = sorted(
                    response.data,
                    key=lambda k: (k[ordering], )
                )

        # HERE IS ALL I NEED TO DO TO GET PAGINATION WORK
        paginator = StandardResultsSetPagination()
        result_page = paginator.paginate_queryset(response.data, request)

        return paginator.get_paginated_response(result_page)
Вернуться на верх