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)