Django 如何对嵌套的 ORM 和非 ORM 对象进行排序和分页

Django how to sort and paginate nested ORM and non-ORM object

我需要进行查询以获取一些属性,并使用其 category FK 在另一个 table 中获取每月费用的数据。有了结果,需要按天、分期、总价和到期日计算价值。

一切正常,但需要按属性模型的某些字段对数据进行排序,按总价排序并对结果进行分页。

我可以同时进行排序和分页,但不能同时进行。

另外,我想问一下那些带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

还在 list Method 尝试了分页器,但没有找到构建下一页和上一页的方法 link

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

向 return 数据显示表格

也许你稍微改变一下你的模型,使查询更容易

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)