Эффективный способ упорядочить поле Annotate в DRF
У меня есть три модели в models.py
,
class AAA(models.Model):
name = models.CharField(max_length=300, null=False, blank=False)
class BBB(models.Model):
aaa = models.ForeignKey(AAA, on_delete=models.CASCADE, null=False, blank=False)
content = models.CharField(max_length=300, null=False, blank=False)
class CCC(models.Model):
aaa = models.ForeignKey(AAA, on_delete=models.CASCADE, null=False, blank=False)
name = models.CharField(max_length=300, null=False, blank=False)
и я хочу заказать в views.py
,
class AAAListCreateView(generics.ListAPIView):
class AAAPagination(CursorPagination):
page_size = 2
queryset = AAA.objects.all().annotate(
b_count=Count("bbb", distinct=True),
c_count=Count("ccc", distinct=True),
total=F('b_count')+('c_count')
)
serializer_class = AAAListSerializer
pagination_class = AAAPagination
filter_backends = [DjangoFilterBackend, OrderingFilter]
filterset_class = AAAListFilterSet
ordering_fields = ['b_count', 'c_count', 'total']
ordering = 'b_count'
Проблемы, которые я хочу задать...
- Я хочу упорядочить по убыванию все мои поля упорядочивания (ordering_fields). Нисходящий порядок без указания
-
. - В
views.py
я аннотирую все поля упорядочивания. Но я думаю, что это неэффективный способ, потому что получает все сортировочные аннотированные поля.
Что мне делать?
Так как вы используете django-filter для упорядочивания, возможно, стоит переопределить класс OrderingFilter... в основном рассматривая метод filter()
Вот что я придумал. Нужно еще поработать, чтобы сделать его более динамичным, если для вас это обычная проблема; но для 1-2 случаев, в теории, это должно работать:
Фильтр по заказу
{project_root}/utils/django_filters_overrides.py
- Примечание: Я прогнал это через ruff, но не тестировал .
- TODO: Фактические аннотации в
def filter
from django.db.models import Count, F
from django.forms.utils import pretty_name
from django.utils.translation import gettext_lazy as _
from django_filters import OrderingFilter
from django_filters.constants import EMPTY_VALUES
class FancyOrderingFilter(OrderingFilter):
"""OrderingFilter Override, always descending order + dynamic annotate"""
def get_ordering_value(self, param):
"""Override order, always descend"""
# Keep all this so actually putting "-" still works
descending = param.startswith("-")
param = param[1:] if descending else param
field_name = self.param_map.get(param, param)
# return "-%s" % field_name if descending else field_name
return "-%s" % field_name # always descend
def filter(self, qs, value):
"""Override filter, dynamically annotate"""
if value in EMPTY_VALUES:
return qs
ordering = [
self.get_ordering_value(param) # <- call override
for param in value
if param not in EMPTY_VALUES
]
# manually annotate (sucks, but you've only got to do it once!)
annotate_dict = {}
if "-total" in ordering:
annotate_dict["b_count"] = Count("bbb", distinct=True)
annotate_dict["c_count"] = Count("ccc", distinct=True)
annotate_dict["total"] = F("b_count")+("c_count")
if "-b_count" in ordering and "-b_count" not in annotate_dict:
annotate_dict["b_count"] = Count("bbb", distinct=True)
if "-c_count" in ordering and "-c_count" not in annotate_dict:
annotate_dict["c_count"] = Count("ccc", distinct=True)
# NOTE: `**` on a dict turns them into keyword args
# I personally like this trick as it's only a single `.filter()` or
# `.annotate()` call
qs = qs.annotate(**annotate_dict)
# Normal Django ordering
return qs.order_by(*ordering)
def build_choices(self, fields, labels):
"""
Override build choices, so labels are correct.
See source, this no_dash label.get() is iffy.
Not sure if this method is important :shrugs:
"""
no_dash = [
(param, labels.get("%s" % param, self.descending_fmt % labels.get(
field, _(pretty_name(param))
)))
for field, param in fields.items()
]
with_dash = [
("-%s" % param, labels.get("-%s" % param, self.descending_fmt % label))
for param, label in no_dash
]
# interleave the no dash and with dash choices
return [val for pair in zip(no_dash, with_dash) for val in pair]
Новый набор видов
from utils.django_filters_overrides import FancyOrderingFilter
class AAAListCreateView(generics.ListAPIView):
class AAAPagination(CursorPagination):
page_size = 2
queryset = AAA.objects.all() # <- change
serializer_class = AAAListSerializer
pagination_class = AAAPagination
filter_backends = [DjangoFilterBackend, FancyOrderingFilter] # <- change
filterset_class = AAAListFilterSet
ordering_fields = ['b_count', 'c_count', 'total']
ordering = 'b_count'
.all()
аннотируйте вопрос
Теперь filter_backends = [DjangoFilterBackend, FancyOrderingFilter]
заставляет меня думать, что он буквально запускает base_qs -> DjangoFilterBackend -> FancyOrderingFilter
. Так что если бы вы могли сделать print(qs.count())
внутри этого FancyOrderingFilter.filter
метода и проверить его на AAA.objects.all().count()
, было бы здорово. Надеюсь, что да! Потому что это означает, что мы закончили, за вычетом обычной отладки.