Django-filter: [рдСрд░реНрдбрд░рд┐рдВрдЧрдлрд╝рд┐рд▓реНрдЯрд░] рдкреНрд░рд╢реНрди / рдлрд╝реАрдЪрд░ рдЕрдиреБрд░реЛрдз: рдСрд░реНрдбрд░ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдордирдорд╛рдиреЗ рдврдВрдЧ рд╕реЗ Django ORM рдЕрднрд┐рд╡реНрдпрдХреНрддрд┐ рдирд┐рд░реНрджрд┐рд╖реНрдЯ рдХрд░рдиреЗ рдХреА рдЕрдиреБрдорддрд┐ рджреЗрдВ

рдХреЛ рдирд┐рд░реНрдорд┐рдд 8 рдЬрдире░ 2019  ┬╖  14рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ  ┬╖  рд╕реНрд░реЛрдд: carltongibson/django-filter

рдирдорд╕реНрдХрд╛рд░! рд╕рдмрд╕реЗ рдкрд╣рд▓реЗ, рдореИрдВ рдЖрдкрдХреЛ рдЗрд╕ рдкрд░рд┐рдпреЛрдЬрдирд╛ рдХреЗ рд▓рд┐рдП рдзрдиреНрдпрд╡рд╛рдж рджреЗрдирд╛ рдЪрд╛рд╣рддрд╛ рд╣реВрдВ, рдЬреЛ рд╣рдорд╛рд░реЗ рдЖрд╡реЗрджрди рдореЗрдВ Django REST рдлреНрд░реЗрдорд╡рд░реНрдХ рдХреЗ рд╕рдВрдпреЛрдЬрди рдореЗрдВ рдЕрддреНрдпрдВрдд рдЙрдкрдпреЛрдЧреА рд╕рд╛рдмрд┐рдд рд╣реБрдИред

рд╡рд░реНрддрдорд╛рди рдореЗрдВ рдореИрдВ рд╕рдВрдмрдВрдзрд┐рдд рдореЙрдбрд▓реЛрдВ рдХреЗ рдХреНрд╖реЗрддреНрд░реЛрдВ рджреНрд╡рд╛рд░рд╛ рдХреНрд░рдордмрджреНрдз рдХрд░рдиреЗ, рдлрд╝рд┐рд▓реНрдЯрд░ рдХрд░рдиреЗ рдФрд░ рдСрд░реНрдбрд░ рдХрд░рдиреЗ рдХреА рд╕рдорд╕реНрдпрд╛ рд╕реЗ рдЬреВрдЭ рд░рд╣рд╛ рд╣реВрдВред рдЗрд╕ рдЕрдВрдХ рдореЗрдВ рдореИрдВ рдХреЗрд╡рд▓ рдЖрджреЗрд╢ рджреЗрдиреЗ рдкрд░ рдзреНрдпрд╛рди рджреЗрдирд╛ рдЪрд╛рд╣реВрдВрдЧрд╛ред

рдореБрдЭреЗ рдирд┐рдореНрдирд▓рд┐рдЦрд┐рдд рдореЙрдбрд▓ рдХреЗ рд╕рд╛рде рдЕрдкрдиреЗ рдЗрд░рд╛рджреЗ рд╕реНрдкрд╖реНрдЯ рдХрд░рдиреЗ рджреЗрдВ:

# models.py

from django.contrib.auth.models import User
from django.db import models


class Order(models.Model):
    user = models.ForeignKey(User, on_delete=models.PROTECT, null=False, blank=False)
    created = models.DateTimeField(null=False, blank=False, auto_now_add=True)
    submitted = models.DateTimeField(null=True, blank=True)

    def __str__(self):
        return f'Order #{self.id}'


class Product(models.Model):
    name = models.CharField(null=False, blank=False, max_length=256)
    price = models.DecimalField(null=False, blank=False, decimal_places=2, max_digits=12)

    def __str__(self):
        return self.name


class OrderLine(models.Model):
    product = models.ForeignKey(Product, on_delete=models.PROTECT, null=False, blank=False, related_name='order_lines')
    quantity = models.IntegerField(null=False, blank=False)
    product_price = models.DecimalField(null=False, blank=False, decimal_places=2, max_digits=12)
    total_price = models.DecimalField(null=False, blank=False, decimal_places=2, max_digits=12)
    order = models.ForeignKey(Order, on_delete=models.CASCADE, null=False, blank=False, related_name='order_lines')

    def __str__(self):
        return f'{self.order}: {self.product.name} x{self.quantity}'

рдлрд┐рд░, рдЖрдЗрдП рдмрд┐рдирд╛ рдХрд┐рд╕реА рдлрд╝рд┐рд▓реНрдЯрд░рд┐рдВрдЧ рдФрд░ рдСрд░реНрдбрд░рд┐рдВрдЧ рдХреЙрдиреНрдлрд╝рд┐рдЧрд░ рдХрд┐рдП DRF рд╡реНрдпреВрд╕реЗрдЯ рдХреЗ рд╕рд╛рде рд╢реБрд░реВ рдХрд░реЗрдВ:

# views.py

from rest_framework import viewsets
from django_filters.rest_framework import DjangoFilterBackend, FilterSet, OrderingFilter
from .models import Order

class OrderFilterSet(FilterSet):
    pass


class OrderViewSet(viewsets.ModelViewSet):
    queryset = Order.objects.all()
    serializer_class = OrderSerializer  # the definition of OrderSerializer is irrelevant and not shown here
    filter_backends = (DjangoFilterBackend, OrderingFilter)
    filterset_class = OrderFilterSet
    ordering_fields = ()

    class Meta:
        model = Order

рдЗрд╕ рдореБрджреНрджреЗ рдХреЗ рджрд╛рдпрд░реЗ рдореЗрдВ рдореЗрд░рд╛ рд▓рдХреНрд╖реНрдп рдХреНрд▓рд╛рдЗрдВрдЯ рдХреЛ рдЗрди рдХреНрд╖реЗрддреНрд░реЛрдВ рджреНрд╡рд╛рд░рд╛ рдСрд░реНрдбрд░ рдХреА рдЕрдиреБрд░реЛрдзрд┐рдд рд╕реВрдЪреА рдХреЛ рдСрд░реНрдбрд░ рдХрд░рдиреЗ рдХреА рдЕрдиреБрдорддрд┐ рджреЗрдирд╛ рд╣реИ:

  1. ordering=[-]created - рдЖрджреЗрд╢ рдирд┐рд░реНрдорд╛рдг рддрд┐рдерд┐ рдХреЗ рдЕрдиреБрд╕рд╛рд░ рдЖрджреЗрд╢ ( [-] рдПрдХ рд╡реИрдХрд▓реНрдкрд┐рдХ - рдХрд╛ рд╕рдВрдХреЗрдд рджреЗрддрд╛ рд╣реИ рддрд╛рдХрд┐ рдЖрджреЗрд╢ рджрд┐рдпрд╛ рдЬрд╛ рд╕рдХреЗред)
  2. ordering=[-]user - рдСрд░реНрдбрд░ рджреЗрдиреЗ рд╡рд╛рд▓реЗ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдХреЗ рдкреВрд░реЗ рдирд╛рдо рд╕реЗ рдСрд░реНрдбрд░ рдХрд░реЗрдВ
  3. ordering=[-]total_quantity - рдСрд░реНрдбрд░ рдореЗрдВ рдЙрддреНрдкрд╛рджреЛрдВ рдХреА рдХреБрд▓ рдорд╛рддреНрд░рд╛ рдХреЗ рдЕрдиреБрд╕рд╛рд░ рдСрд░реНрдбрд░ рдХрд░реЗрдВ

ordering_fields ViewSet рдХреЙрдиреНрдлрд╝рд┐рдЧрд░ рдХрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИ рдЬреЛ рдХреЗрд╡рд▓ рдореЙрдбрд▓ рдлрд╝реАрд▓реНрдб рдХреЗ рдЖрдзрд╛рд░ рдкрд░ рдСрд░реНрдбрд░ рдХрд░рдиреЗ рдХреА рдЕрдиреБрдорддрд┐ рджреЗрддрд╛ рд╣реИ, p.1ред рд╕реМрднрд╛рдЧреНрдп рд╕реЗ, рд╣рдо https://django-filter.readthedocs.io/en/master/ref/filters.html#orderingfilter рдореЗрдВ рд╡рд░реНрдгрд┐рдд filters.OrderingFilter рдХреЛ рдЙрдк-рд╡рд░реНрдЧреАрдХреГрдд рдХрд░рдиреЗ рдХреА рдЕрдзрд┐рдХ рдЙрдиреНрдирдд рд╡рд┐рдзрд┐ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ:

# views.py

from rest_framework import viewsets
from django_filters.rest_framework import DjangoFilterBackend, FilterSet, OrderingFilter
from .models import Order


class OrderOrderingFilter(OrderingFilter):

    def __init__(self):
        super().__init__(fields={
            # example: model field
            # Order by order creation date
            'created': 'created',

            # example: expression on related model
            # Order by user full name
            'user': '',  # ???

            # example: aggregate expression
            'total_quantity': ''  # ???
        })


class OrderFilterSet(FilterSet):
    ordering = OrderOrderingFilter()


class OrderViewSet(viewsets.ModelViewSet):
    queryset = Order.objects.all()
    serializer_class = OrderSerializer  # the definition of OrderSerializer is irrelevant and not shown here
    filter_backends = (DjangoFilterBackend,)
    filterset_class = OrderFilterSet

    class Meta:
        model = Order

рд╣рд╛рд▓рд╛рдВрдХрд┐, рдЗрд╕ рдЙрдиреНрдирдд рд╡рд┐рдзрд┐ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддреЗ рд╕рдордп рднреА, django-filter рдХреЛ рдлрд╝реАрд▓реНрдб рдирд╛рдо рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реЛрддреА рд╣реИред рдЗрд╕ рд╕рдорд╕реНрдпрд╛ рд╕реЗ рдмрдЪрдиреЗ рдХрд╛ рдПрдХрдорд╛рддреНрд░ рддрд░реАрдХрд╛ рдлрд╝рд┐рд▓реНрдЯрд░ рдХрд┐рдП рдЧрдП рдХреНрд╡реЗрд░реАрд╕реЗрдЯ рдореЗрдВ annotate 'рджреНрд╡рд╛рд░рд╛ рд╣реИ:

# ... the rest is omitted to brevity ...
class OrderOrderingFilter(OrderingFilter):

    def __init__(self):
        super().__init__(fields={
            # ... the rest is omitted to brevity ...
            'user_order': 'user',
        })

    def filter(self, qs, value):
        if value:
            qs = self._annotate(qs)
        return super().filter(qs, value)

    def _annotate(self, qs, value):
        if 'user' in value or '-user' in value:
            qs = qs.annotate(user_order=functions.Concat(
                models.F('user__first_name'),
                models.Value(' '),
                models.F('user__last_name'),
            ))
        return qs

рдХреБрдЫ рд▓реЛрдЧ рдХрд╣ рд╕рдХрддреЗ рд╣реИрдВ рдХрд┐ рдпрд╣рд╛рдВ .annotate рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдирд╛ рдПрдХ рднрдпрд╛рдирдХ рд╡рд┐рдЪрд╛рд░ рд╣реИ рдХреНрдпреЛрдВрдХрд┐ https://docs.djangoproject.com/en/2.1 рдореЗрдВ рд╡рд░реНрдгрд┐рдд рдЕрдиреБрд╕рд╛рд░ .annotate рдХреЛ .filter $ рдХреЗ рд╕рд╛рде рдордЬрд╝рдмреВрддреА рд╕реЗ рдирд╣реАрдВ рдорд┐рд▓рд╛рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИред /рд╡рд┐рд╖рдп/рдбреАрдмреА/рдПрдЧреНрд░реАрдЧреЗрд╢рди/#рдСрд░реНрдбрд░ -рдСрдл-рдПрдиреЛрдЯреЗрдЯ-рдПрдВрдб-рдлрд┐рд▓реНрдЯрд░-рдХреНрд▓реЙрдЬред рд╣рд╛рд▓рд╛рдВрдХрд┐, рдЬрдм рддрдХ рдЖрдк рдЕрдкрдиреЗ .annotate рдХреЙрд▓ рдореЗрдВ рдПрдХрддреНрд░реАрдХрд░рдг рдХрд╛ рдЙрдкрдпреЛрдЧ рдирд╣реАрдВ рдХрд░рддреЗ рд╣реИрдВ, рддрдм рддрдХ рдЖрдк рдареАрдХ рд╣реИрдВ, рдЗрд╕рд▓рд┐рдП рд╣рдо рдЕрдм рддрдХ рдЕрдЪреНрдЫреЗ рд╣реИрдВред (_рдореИрдВ рд╡рд╛рд╕реНрддрд╡ рдореЗрдВ рдмрд╛рдж рдореЗрдВ рдПрдХрддреНрд░реАрдХрд░рдг рдкрд░ рд▓реМрдЯреВрдВрдЧрд╛_)

рдирд╡реАрдирддрдо рдХреЛрдб рдирдореВрдиреЗ рдХреЗ рд╕рд╛рде рдПрдХ рдФрд░ рдореБрджреНрджрд╛ рдпрд╣ рд╣реИ рдХрд┐ рдХреЛрдб рджреЛрд╣рд░рд╛рд╡рджрд╛рд░ рд╣реИ рдФрд░ рдмреЗрд╕ рдХреНрд▓рд╛рд╕ рдореЗрдВ рдирд┐рдХрд╛рд▓рд╛ рдЬрд╛рдирд╛ рдмреЗрд╣рддрд░ рд╣реИ рдЬрд┐рд╕реЗ рдЖрд╕рд╛рдиреА рд╕реЗ рд╡рд┐рд░рд╛рд╕рдд рдореЗрдВ рдкреНрд░рд╛рдкреНрдд рдХрд┐рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИред рдЕрднреА рдХреЗ рд▓рд┐рдП, рдореИрдВрдиреЗ рдЕрдкрдирд╛ рдЦреБрдж OrderingFilter рд╡рд░реНрдЧ рд▓рд┐рдЦрд╛ рд╣реИ, рдЬреЛ django-filter рджреНрд╡рд╛рд░рд╛ рдкреНрд░рджрд╛рди рдХрд┐рдП рдЧрдП рдПрдХ рдХреЗ рд▓рд┐рдП рдПрдХ рдкреВрд░реНрдг рд╡рд┐рдХрд▓реНрдк рд╣реИред рдЗрд╕рдХрд╛ рдкреВрд░рд╛ рд╕реЛрд░реНрд╕ рдХреЛрдб рдиреАрдЪреЗ рд╣реИред рдЖрдк рджреЗрдЦ рд╕рдХрддреЗ рд╣реИрдВ рдХрд┐ рдпрд╣ django-filter рд╕реЗ рдмрд╣реБрдд рдХреБрдЫ рдЙрдзрд╛рд░ рд▓реЗрддрд╛ рд╣реИ:

import typing

from django.db import models
from django.db.models import expressions
from django.forms.utils import pretty_name
from django.utils.translation import ugettext_lazy as _

from django_filters import filters
from django_filters.constants import EMPTY_VALUES

__all__ = ('OrderingFilter',)


class OrderingFilter(filters.BaseCSVFilter, filters.ChoiceFilter):
    """
    An alternative to django_filters.filter.OrderingFilter that allows to specify any Django ORM "expression" for ordering.

    Usage examples:

      class MyOrderingFilter(ddf.OrderingFilter):
        def __init__(self):
          super().__init__(fields={

            # a model field
            'id': 'id'

            # an expression
            'published_by':
              functions.Concat(
                expressions.F(f'published_user__first_name'),
                expressions.Value(' '),
                expressions.F(f'published_user__last_name')
              ),

            # a complete field descriptor with custom field label
            'reported_by': {
              'label': 'Reporter',
              'desc_label': 'Reporter (descending)',  # optional, would be derived from 'label' anyway
              'expr': functions.Concat(
                expressions.F(f'reported_user__first_name'),
                expressions.Value(' '),
                expressions.F(f'reported_user__last_name')
              ),
            }
          })

    For more information about expressions, please see the official Django documentation at
    https://docs.djangoproject.com/en/2.1/ref/models/expressions/
    """

    _fields: typing.Mapping[str, 'FieldDescriptor']

    def __init__(self, fields: typing.Mapping[str, typing.Any]):
        self._fields = normalize_fields(fields)
        super().__init__(choices=build_choices(self._fields))

    # <strong i="15">@override</strong>
    def filter(self, qs: models.QuerySet, value: typing.Union[typing.List[str], None]):
        return qs if value in EMPTY_VALUES else qs.order_by(*(expr for expr in map(self.get_ordering_expr, value)))

    def get_ordering_expr(self, param) -> expressions.Expression:
        descending = param.startswith('-')
        param = param[1:] if descending else param
        field_descriptor = self._fields.get(param)
        return None if field_descriptor is None else \
            field_descriptor.expr if not descending else field_descriptor.expr.desc()


def normalize_fields(fields: typing.Mapping[str, typing.Any]) -> typing.Mapping[str, 'FieldDescriptor']:
    return dict((
        param_name,
        FieldDescriptor(param_name, {'expr': normalize_expr(field)} if isinstance(field, (str, expressions.Expression)) else field)
    ) for param_name, field in fields.items())


def normalize_expr(expr: typing.Union[str, expressions.Expression]):
    return models.F(expr) if isinstance(expr, str) else expr


descending_fmt = _('%s (descending)')


class FieldDescriptor:
    expr: models.Expression

    def __init__(self, param_name: str, data: typing.Mapping[str, typing.Any]):
        self.expr = normalize_expr(data['expr'])
        self.label = data.get('label', _(pretty_name(param_name)))
        self.desc_label = data.get('desc_label', descending_fmt.format(self.label))


def build_choices(fields: typing.Mapping[str, 'FieldDescriptor']):
    choices = []
    for param_name, field_descriptor in fields.items():
        choices.append((param_name, field_descriptor.label))
        choices.append((f'-{param_name}', field_descriptor.desc_label))
    return choices

рдЗрд╕ рд╡рд░реНрдЧ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ, OrdersOrderingFilter рдЕрдЪреНрдЫрд╛ рдФрд░ рд╕рдВрдХреНрд╖рд┐рдкреНрдд рд╣реЛ рдЬрд╛рддрд╛ рд╣реИ:

# ... the rest is omitted to brevity ...
class OrderOrderingFilter(ddf.OrderingFilter):

    def __init__(self):
        super().__init__(fields={
            # ... the rest is omitted to brevity ...
            'user': functions.Concat(
                models.F('user__first_name'),
                models.Value(' '),
                models.F('user__last_name'),
            ),
        })

рдпрд╣ рдЙрд╕ рдкреНрд░рд╢реНрди рдХреЛ рд╕рд╛рдордиреЗ рд▓рд╛рддрд╛ рд╣реИ рдЬрд┐рд╕рдХреЗ рд╕рд╛рде рдореИрдВ рдпрд╣рд╛рдВ рдЖрдпрд╛ рдерд╛:

_ django_filters.rest_framework.OrderingFilter рдореЗрдВ Django ORM рдЕрднрд┐рд╡реНрдпрдХреНрддрд┐ рдХреЗ рд╕рд╛рде рдСрд░реНрдбрд░рд┐рдВрдЧ рдирд┐рд░реНрджрд┐рд╖реНрдЯ рдХрд░рдиреЗ рдХреА рдХреНрд╖рдорддрд╛ рдкреНрд░рджрд╛рди рдХрд░рдиреЗ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдЖрдк рдХреНрдпрд╛ рд╕реЛрдЪрддреЗ рд╣реИрдВ? _

рд▓реЗрдХрд┐рди рдЗрд╕рд╕реЗ рдкрд╣рд▓реЗ рдХрд┐ рдЖрдк рдЙрддреНрддрд░ рджреЗрдВ, рдореИрдВ рдЖрдкрдХреЛ рдпрд╛рдж рджрд┐рд▓рд╛ рджреВрдВ рдХрд┐ рд╣рдорд╛рд░реЗ рдкрд╛рд╕ рдХреЗрд╡рд▓ рдкреАрдкреА 1 рдФрд░ 2 рд╣рд▓ рд╣реИрдВред p.3 рдХреЛ рд╣рд▓ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд╣рдореЗрдВ рдПрдХ рдПрдХрддреНрд░реАрдХрд░рдг рд╡реНрдпрдВрдЬрдХ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реЛрдЧреА:

aggregates.Sum(models.F('order_lines__quantity'))

рд╣рдо рдЗрд╕ рд╡реНрдпрдВрдЬрдХ рдХрд╛ рдЙрдкрдпреЛрдЧ .annotate рдореЗрдВ рдирд╣реАрдВ рдХрд░ рд╕рдХрддреЗ, рдХреНрдпреЛрдВрдХрд┐ рдЗрд╕реЗ QuserySet рдлрд╝рд┐рд▓реНрдЯрд░рд┐рдВрдЧ рдХреЗ рд╕рд╛рде рдорд┐рд▓рд╛рдиреЗ рдХреЗ рдкрд░рд┐рдгрд╛рдо рдЕрдкреНрд░рддреНрдпрд╛рд╢рд┐рдд рд╣реЛрдВрдЧреЗред рд╣рдо рдЗрд╕ рдЕрднрд┐рд╡реНрдпрдХреНрддрд┐ рдХрд╛ рдЙрдкрдпреЛрдЧ рд╕реАрдзреЗ Query.order(...) рдкрд░ рдХреЙрд▓ рдореЗрдВ рдирд╣реАрдВ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ, рдХреНрдпреЛрдВрдХрд┐

django.core.exceptions.FieldError: Using an aggregate in order_by() without also including it in annotate() is not allowed: Sum(F(order_lines__quantity))

рддреЛ p.3 рдкрд╣реБрдВрдЪ рд╕реЗ рдмрд╛рд╣рд░ рд╣реИ, рдПрдХ рд╕рдорд╛рдзрд╛рди рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ рдЬреЛ рд╡реНрдпреВрд╕реЗрдЯ рд╕реЗ рд╢реБрд░реВ рд╣реЛрдиреЗ рд╡рд╛рд▓реА рд╣рд░ рдЪреАрдЬ рдХреЛ рдЫреЗрдж рджреЗрдЧрд╛ред рдЗрд╕рдХрд╛ django-filters рдХреА рддреБрд▓рдирд╛ рдореЗрдВ Django REST рдлреНрд░реЗрдорд╡рд░реНрдХ рд╕реЗ рдЕрдзрд┐рдХ рд▓реЗрдирд╛-рджреЗрдирд╛ рд╣реИред рдХреНрдпрд╛ рд╣рдореЗрдВ рд╡рд╛рд╕реНрддрд╡ рдореЗрдВ django_filters.rest_framework.OrderingFilter рдореЗрдВ рд╕рдореЗрдХрди рдЕрднрд┐рд╡реНрдпрдХреНрддрд┐ рд╕рдорд░реНрдерди рдХреЗ рдмрд┐рдирд╛ рдЕрднрд┐рд╡реНрдпрдХреНрддрд┐ рд╕рдорд░реНрдерди рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ? рдпрд╣ рдЕрдзрд┐рдХ рд▓рд╛рдн рд▓рд╛рдП рдмрд┐рдирд╛ рдХреЗрд╡рд▓ django-filter рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛рдУрдВ рдХреЛ рднреНрд░рдорд┐рдд рдХрд░ рд╕рдХрддрд╛ рд╣реИред

рдореИрдВ рдЖрдкрдХреА рд░рд╛рдп рд╕реБрдирдиреЗ рдХреЗ рд▓рд┐рдП рдЙрддреНрд╕реБрдХ рд╣реВрдВред рдореБрдЭреЗ рдкрддрд╛ рд╣реИ рдХрд┐ рдпрд╣ рдкрдЪрд╛рдиреЗ рдХреЗ рд▓рд┐рдП рдмрд╣реБрдд рд╕реА рдЬрд╛рдирдХрд╛рд░реА рд╣реИред рдЙрдореНрдореАрдж рд╣реИ, рдореЗрд░реА рдкрд░реАрдХреНрд╖рдг рдкрд░рд┐рдпреЛрдЬрдирд╛ рдорджрдж рдХрд░ рд╕рдХрддреА рд╣реИ: https://github.com/earshinov/django_sample/tree/master/django_sample/ordering_by_expression

рд╕рдмрд╕реЗ рдЙрдкрдпреЛрдЧреА рдЯрд┐рдкреНрдкрдгреА

рдЕрдЪ - рдЗрд╕ рдкрд░ рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛ рджреЗрдирд╛ рд╢реБрд░реВ рдХрд░ рджрд┐рдпрд╛ рд▓реЗрдХрд┐рди рдореЗрд░рд╛ рдХрдВрдкреНрдпреВрдЯрд░ рдЦрд░рд╛рдм рд╣реЛ рдЧрдпрд╛ред

рдореБрдЭреЗ рд▓рдЧрддрд╛ рд╣реИ рдХрд┐ рдпрд╣рд╛рдВ рдкреНрд░рд╕реНрддрд╛рд╡рд┐рдд рдкрд░рд┐рд╡рд░реНрддрди рд╕рдордЭрджрд╛рд░ рд╣реИрдВред model_field - parameter_name рдЬреЛрдбрд╝реА рдЙрд╕ рд╕рдордп рд╕рдордЭ рдореЗрдВ рдЖрдИ рдХреНрдпреЛрдВрдХрд┐ рд╣рдо рдЖрдо рддреМрд░ рдкрд░ рдореЙрдбрд▓ рд╕реЗ рдЙрдЬрд╛рдЧрд░ рдкреИрд░рд╛/рдлреЙрд░реНрдо/рдЖрджрд┐ рдкреНрд░рд╛рдкреНрдд рдХрд░рддреЗ рд╣реИрдВ, рд▓реЗрдХрд┐рди рдРрд╕рд╛ рдХреЛрдИ рдХрд╛рд░рдг рдирд╣реАрдВ рд╣реИ рдХрд┐ рдпрд╣ рдЖрд╡рд╢реНрдпрдХ рдХреНрдпреЛрдВ рд╣реИред рдореИрдкрд┐рдВрдЧ рдХреА рдЕрджрд▓рд╛-рдмрджрд▓реА рдХрд░рдирд╛ рд╕рдордЭ рдореЗрдВ рдЖрддрд╛ рд╣реИ, рдФрд░ рд╣рдореЗрдВ рдЕрдзрд┐рдХ рдЬрдЯрд┐рд▓ рдЕрднрд┐рд╡реНрдпрдХреНрддрд┐рдпреЛрдВ рдХрд╛ рд▓рд╛рдн рдЙрдард╛рдиреЗ рдХреА рдЕрдиреБрдорддрд┐ рджреЗрддрд╛ рд╣реИред

рд╕рд╛рде рд╣реА, рдореБрдЭреЗ рдирд╣реАрдВ рд▓рдЧрддрд╛ рдХрд┐ рдмрд╣рд┐рд╖реНрдХрд░рдг рдкреНрд░рдХреНрд░рд┐рдпрд╛ рдмреЗрд╣рдж рдХрдард┐рди рд╣реЛрдЧреА, рдФрд░ рдореБрдЭреЗ рдЗрд╕рдореЗрдВ рдорджрдж рдХрд░рдиреЗ рдореЗрдВ рдЦреБрд╢реА рд╣реЛрдЧреАред рдореВрд▓ рд░реВрдк рд╕реЗ, fields рдФрд░ field_labels params рдФрд░ param_labels рдореЗрдВ рдкрд░рд┐рд╡рд░реНрддрд┐рдд рд╣реЛ рдЬрд╛рдПрдВрдЧреЗред рдкрд╢реНрдЪ рд╕рдВрдЧрддрддрд╛ рдмрдирд╛рдП рд░рдЦрддреЗ рд╣реБрдП, рдФрд░ рдкреБрд░рд╛рдиреЗ рддрд░реНрдХреЛрдВ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдиреЗ рд╡рд╛рд▓реЗ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛рдУрдВ рдХреЗ рд▓рд┐рдП рдмрд╣рд┐рд╖реНрдХрд░рдг рдиреЛрдЯрд┐рд╕ рдЬрд╛рд░реА рдХрд░рддреЗ рд╣реБрдП, рдПрдХ рд╕реЗ рджреВрд╕рд░реЗ рдореЗрдВ рдХрдирд╡рд░реНрдЯ рдХрд░рдирд╛ рдХрд╛рдлреА рдЖрд╕рд╛рди рд╣реИред

рдЕрд╡рд░реЛрд╣реА рдорд╛рдорд▓реЗ ( param рдмрдирд╛рдо -param ) рдХреЗ рд▓рд┐рдП рдЬрдЯрд┐рд▓ рдЕрднрд┐рд╡реНрдпрдХреНрддрд┐рдпреЛрдВ рдХрд╛ рд╕реНрд╡рдЪрд╛рд▓рд┐рдд рд░реВрдкрд╛рдВрддрд░рдг рд╡рд┐рдЪрд╛рд░ рдХрд░рдиреЗ рд╡рд╛рд▓реА рдПрдХ рдмрд╛рдд рд╣реИред рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рдпрджрд┐ рдЖрд░реЛрд╣реА .asc(nulls_last=True) рд╣реИ, рддреЛ рдХреНрдпрд╛ рдЗрд╕рдХрд╛ рд╡реНрдпреБрддреНрдХреНрд░рдо .desc(nulls_first=True) рд╣реЛрдирд╛ рдЪрд╛рд╣рд┐рдП, рдпрд╛ рдирд▓ рдХреЛ рдХреНрд░рдо рдХреА рджрд┐рд╢рд╛ рдХреА рдкрд░рд╡рд╛рд╣ рдХрд┐рдП рдмрд┐рдирд╛ рдЕрдВрддрд┐рдо рд░рд╣рдирд╛ рдЪрд╛рд╣рд┐рдП?

рд╕рднреА 14 рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ

рд╣рд╛рдп @earshinovред рд░рд┐рдкреЛрд░реНрдЯ рдХреЗ рд▓рд┐рдП рдзрдиреНрдпрд╡рд╛рджред рдмрд╣реБрдд рд╣реА рд░реЛрдЪрдХред рдореБрдЭреЗ рдЗрд╕рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рд╕реЛрдЪрдиреЗ рджреЛред рдорд╛рд░реНрдЪ рдореЗрдВ рдореБрдЭреЗ рдкрд┐рдВрдЧ рдХрд░реЗрдВ рдЕрдЧрд░ рдореИрдВрдиреЗ рддрдм рддрдХ рдЬрд╡рд╛рдм рдирд╣реАрдВ рджрд┐рдпрд╛ рд╣реИред рдореИрдВ

рдПрдХ рдФрд░ рд╡рд┐рдЪрд╛рд░: рдПрдХ рдХрджрдо рдЖрдЧреЗ рдЬрд╛рдирд╛ рдФрд░ рдлрд╝рд┐рд▓реНрдЯрд░ рдХреЛ рдПрдХ рдирд╣реАрдВ, рдмрд▓реНрдХрд┐ рдХрдИ рдЕрднрд┐рд╡реНрдпрдХреНрддрд┐рдпреЛрдВ рдпрд╛ рдлрд╝реАрд▓реНрдб рдирд╛рдореЛрдВ рдХреЛ рдирд┐рд░реНрджрд┐рд╖реНрдЯ рдХрд░рдиреЗ рдХреА рдЕрдиреБрдорддрд┐ рджреЗрдирд╛ рд╕рдВрднрд╡ рд╣реИред

рдкрд╣рд▓реЗ (рд╡рд┐рдХрд▓реНрдк 1):

class OrderOrderingFilter(ddf.OrderingFilter):

    def __init__(self):
        super().__init__(fields={
            'user': functions.Concat(
                models.F('user__first_name'),
                models.Value(' '),
                models.F('user__last_name'),
            ),
        })

рдХреЗ рдмрд╛рдж (рд╡рд┐рдХрд▓реНрдк 2):

class OrderOrderingFilter(ddf.OrderingFilter):

    def __init__(self):
        super().__init__(fields={
            'user': ('user__first_name', 'user__last_name'),
        })

рджреЛрдиреЛрдВ рд╣реА рдорд╛рдорд▓реЛрдВ рдореЗрдВ ordering=user рдкрд╣рд▓реЗ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдХреЗ рдкрд╣рд▓реЗ рдирд╛рдо рд╕реЗ рдбреЗрдЯрд╛ рдСрд░реНрдбрд░ рдХрд░рддрд╛ рд╣реИ, рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдХрд╛ рджреВрд╕рд░рд╛ рдирд╛рдо рджреВрд╕рд░рд╛, рд▓реЗрдХрд┐рди рд╡рд┐рдХрд▓реНрдк 2 рд▓рд┐рдЦрдирд╛ рдЖрд╕рд╛рди рд╣реЛрддрд╛ рд╣реИ, рдФрд░ рдЬрдм рдбреАрдмреА рдкреНрд░рд╢реНрдиреЛрдВ рдХреА рдмрд╛рдд рдЖрддреА рд╣реИ рддреЛ рд╢рд╛рдпрдж рдЕрдзрд┐рдХ рдХреБрд╢рд▓ рд╣реЛрддрд╛ рд╣реИред

рд╣реИрд▓реЛ @carlongibson ,

рдЖрдкрдиреЗ рдореБрдЭреЗ рдореИрдЪ рдореЗрдВ рдЖрдкрдХреЛ рдкрд┐рдВрдЧ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдХрд╣рд╛ред рдпрд╣ рдкрд╣рд▓реЗ рд╕реЗ рд╣реА рдордИ рд╣реИ :)

рднрд╛рд╡реЛрдВ (рдПрдХ рдпрд╛ рдПрдХрд╛рдзрд┐рдХ) рджреНрд╡рд╛рд░рд╛ рдЖрджреЗрд╢ рджреЗрдиреЗ рдХреЗ рдЕрдкрдиреЗ рдкреНрд░рд╕реНрддрд╛рд╡ рдкрд░ рд╡рд╛рдкрд╕ рд▓реМрдЯрддреЗ рд╣реБрдП, рдореБрдЭреЗ рд╡рд╛рд╕реНрддрд╡ рдореЗрдВ рд▓рдЧрддрд╛ рд╣реИ рдХрд┐ рдпрд╣ рдкрд╣реЗрд▓реА рдХрд╛ рдПрдХ рд▓рд╛рдкрддрд╛ рд╣рд┐рд╕реНрд╕рд╛ рд╣реИред

рдореИрдВ рдЪрд╛рд░ рдмреБрдирд┐рдпрд╛рджреА рддрд╛рд▓рд┐рдХрд╛ рд╕рдВрдЪрд╛рд▓рди рджреЗрдЦрддрд╛ рд╣реВрдВ:
рдПред рдХреНрд░рдордмрджреНрдзрддрд╛
рдмреАред ~рдкреЗрдЬрд┐рдиреЗрд╢рди~ (рдЗрд╕ рдЪрд░реНрдЪрд╛ рдХреЗ рд▓рд┐рдП рдЕрдкреНрд░рд╛рд╕рдВрдЧрд┐рдХ)
рд╕реАред рдЫрд╛рдирдиреЗ
рдбреАред рдЫрдВрдЯрд╛рдИ

DRF рдФрд░ django-filter рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдЗрди рд╕рднреА рдХреЛ рд╕рдВрдмрдВрдзрд┐рдд рдореЙрдбрд▓реЛрдВ рдХреЗ рд╕рдорд░реНрдерди рдХреЗ рд╕рд╛рде рд▓рд╛рдЧреВ рдХрд░рдирд╛ рд╕рдВрднрд╡ рд╣реИ, рдЫрдБрдЯрд╛рдИ рдХреЛ рдЫреЛрдбрд╝рдХрд░ :

рдПред рдХреНрд░рдордмрджреНрдзрддрд╛ рдХреЗ рд▓рд┐рдП, рд╣рдорд╛рд░реЗ рдкрд╛рд╕ рдиреЗрд╕реНрдЯреЗрдб рдзрд╛рд░рд╛рд╡рд╛рд╣рд┐рдХ рд╣реИрдВред рд╕рд╛рде рд╣реА, рдЕрдЧрд░ рд╣рдореЗрдВ рдПрдХ рд╕рдореЗрдХрд┐рдд рдлрд╝реАрд▓реНрдб рд╡рд╛рдкрд╕ рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ ( count рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рд╕реЛрдЪреЗрдВ), рддреЛ рд╣рдо рдХреНрд╡реЗрд░реАрд╕реЗрдЯ .annotate() рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ:

from django.db.models import aggregates, functions, F, Value

class ModelViewSet(...):

    def get_queryset(self):
        qs = Model.objects.all()

        if self.action == 'list':
            qs = qs.annotate(
                author_full_name=functions.Trim(functions.Concat(
                    F('author__first_name'),
                    Value(' '),
                    F('author__last_name'),
                )),
                submodel_count=aggregates.Count('submodel'))
            )

        return qs

рд╕реАред Django рдЕрднрд┐рд╡реНрдпрдХреНрддрд┐рдпреЛрдВ рдХреЗ рд╕рд╛рде рдлрд╝рд┐рд▓реНрдЯрд░ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рдХреЛрдИ рдХрд╕реНрдЯрдо рдлрд╝рд┐рд▓реНрдЯрд░ рд▓рд╛рдЧреВ рдХрд░ рд╕рдХрддрд╛ рд╣реИ (рдиреАрдЪреЗ рдПрдХ рдЙрджрд╛рд╣рд░рдг рджреЗрдЦреЗрдВ)ред рд╕рд╛рдорд╛рдиреНрдп рд╕реНрдХреЗрд▓рд░ рдлрд╝рд┐рд▓реНрдЯрд░ ( filters.NumberFilter ) рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдПрдХ рд╕рдореЗрдХрд┐рдд рдлрд╝реАрд▓реНрдб ( submodel_count ) рджреНрд╡рд╛рд░рд╛ рдлрд╝рд┐рд▓реНрдЯрд░ рдХрд░рдирд╛ рд╕рдВрднрд╡ рд╣реИред

from django_filters import filters
from django_filters.rest_framework import FilterSet

class ModelFilterSet(FilterSet):

    author = UserFilter(field_name='author', label='author', lookup_expr='icontains')


class UserFilter(filters.Filter):
    """A django_filters filter that implements filtering by user's full name.

    def filter(self, qs: QuerySet, value: str) -> QuerySet:
        # first_name <lookup_expr> <value> OR last_name <lookup_expr> <value>
        return qs if not value else qs.filter(
            Q(**{f'{self.field_name}__first_name__{self.lookup_expr}': value}) |
            Q(**{f'{self.field_name}__last_name__{self.lookup_expr}': value})
        )

рдбреАред рдЫрдБрдЯрд╛рдИ рдХрд╛ рдХреЛрдИ рд╕рдорд╛рдзрд╛рди рдирд╣реАрдВ рд╣реИ :-(

рдпрд╣рд╛рдВ рд╣рдорд╛рд░реЗ рдХрд╕реНрдЯрдо OrderingFilter рдХрд╛ рдкреВрд░реНрдг рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рд╣реИ, рдЬрд┐рд╕рдХрд╛ рд╣рдо рдЕрдм рддрдХ рдЙрдкрдпреЛрдЧ рдХрд░ рд░рд╣реЗ рд╣реИрдВ:

class OrderingFilter(filters.BaseCSVFilter, filters.ChoiceFilter):
    """An alternative to :class:`django_filters.filters.OrderingFilter` that allows to specify any Django ORM expression for ordering.

    Usage example:

    .. code-block:: python

      from django.db import models
      from django.db.models import aggregates, expressions, fields

      import ddl

      class OrderOrderingFilter(ddl.OrderingFilter):
        def __init__(self):
          super().__init__(fields={

            # a model field
            'created': 'created'

            # an expression
            'submitted': expressions.ExpressionWrapper(
                models.Q(submitted_date__isnull=False),
                output_field=fields.BooleanField()
            ),

            # multiple fields or expressions
            'user': ('user__first_name', 'user__last_name'),

            # a complete field descriptor with custom field label
            'products': {
              'label': 'Total number of items in the order',
              # if not specified, `desc_label` would be derived from 'label' anyway
              'desc_label': 'Total number of items in the order (descending)',
              'expr': aggregates.Sum('order_lines__quantity'),
              # it is also possible to filter by multiple fields or expressions here
              #'exprs': (...)
            },
          })

    For more information about expressions, see the official Django documentation at
    https://docs.djangoproject.com/en/dev/ref/models/expressions/
    """

    _fields: typing.Mapping[str, 'FieldDescriptor']

    def __init__(self, fields: typing.Mapping[str, typing.Any]):
        self._fields = normalize_fields(fields)
        super().__init__(choices=build_choices(self._fields))

    # <strong i="7">@override</strong>
    def filter(self, qs: models.QuerySet, value: typing.Union[typing.List[str], None]):
        return qs if value in EMPTY_VALUES else qs.order_by(*(itertools.chain(*(self.__get_ordering_exprs(param) for param in value))))

    def __get_ordering_exprs(self, param) -> typing.Union[None, typing.List[expressions.Expression]]:
        descending = param.startswith('-')
        param = param[1:] if descending else param
        field_descriptor = self._fields.get(param)
        return () if field_descriptor is None else \
            field_descriptor.exprs if not descending else \
            (expr.desc() for expr in field_descriptor.exprs)



def normalize_fields(fields: typing.Mapping[str, typing.Any]) -> typing.Mapping[str, 'FieldDescriptor']:
    return dict((
        param_name,
        FieldDescriptor(param_name, field if isinstance(field, collections.Mapping) else {'exprs': normalize_exprs(field)})
    ) for param_name, field in fields.items())

def normalize_exprs(exprs: typing.Union[
        typing.Union[str, expressions.Expression],
        typing.List[typing.Union[str, expressions.Expression]]
    ]) -> typing.List[expressions.Expression]:
    # `exprs` is either a single expression or a Sequence of expressions
    exprs = exprs if isinstance(exprs, collections.Sequence) and not isinstance(exprs, str) else (exprs,)
    return [normalize_expr(expr) for expr in exprs]

def normalize_expr(expr: typing.Union[str, expressions.Expression]) -> expressions.Expression:
    return models.F(expr) if isinstance(expr, str) else expr


descending_fmt = _('%s (descending)')


class FieldDescriptor:
    exprs: typing.List[models.Expression]

    def __init__(self, param_name: str, data: typing.Mapping[str, typing.Any]):
        exprs = data.get('exprs') or data.get('expr')
        if not exprs:
            raise ValueError("Expected 'exprs' or 'expr'")
        self.exprs = normalize_exprs(exprs)
        self.label = data.get('label', _(pretty_name(param_name)))
        self.desc_label = data.get('desc_label', descending_fmt.format(self.label))


def build_choices(fields: typing.Mapping[str, 'FieldDescriptor']):
    choices = []
    for param_name, field_descriptor in fields.items():
        choices.append((param_name, field_descriptor.label))
        choices.append((f'-{param_name}', field_descriptor.desc_label))
    return choices

_Update_: normalize_fields рдФрд░ рдЕрдиреНрдп рд╕рд╣рд╛рдпрдХ рд╡рд┐рдзрд┐рдпреЛрдВ рдХрд╛ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рд╢рд╛рдорд┐рд▓ рд╣реИред

рдЕрдЪреНрдЫрд╛ред рдореИрдВ

рдкрд┐рдВрдЧ рдХреЗ рд▓рд┐рдП рдзрдиреНрдпрд╡рд╛рджред

рдЯреАрдмреАрдПрдЪ рдореЗрд░реЗ рдкрд╛рд╕ рдЗрд╕ рдмрд╛рд░реЗ рдореЗрдВ рд╕реЛрдЪрдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рдкрд▓ рднреА рдирд╣реАрдВ рд╣реИред

рдЖрдкрдХреЗ рд╕рднреА рдорд╣рд╛рди рд╡рд┐рдЪрд╛рд░реЛрдВ рдореЗрдВ рдХреНрдпрд╛ рдЖрдкрдХреЗ рдкрд╛рд╕ рдПрдХ рдЫреЛрдЯреЗ рд╕реЗ рдмрджрд▓рд╛рд╡ рдХреЗ рд▓рд┐рдП рдХреЛрдИ рд╕реБрдЭрд╛рд╡ рд╣реИ рдЬреЛ рд╣рдо рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ? (рд╢рд╛рдпрдж рдкреАрдЖрд░ рдХреЗ рд╕рд╛рде рдЖрдЧреЗ рдмрдврд╝рдирд╛ рдЖрд╕рд╛рди рд╣реИ)ред

рд╣рд╛рдБ, рдпрд╣ рддреБрд░рдВрдд рд╕реНрдкрд╖реНрдЯ рдирд╣реАрдВ рд╣реИ рдХрд┐ рдРрд╕реЗ рдкрд░рд┐рд╡рд░реНрддрдиреЛрдВ рдХреЛ рдкрд┐рдЫрдбрд╝реЗ рд╕рдВрдЧрдд рдХреИрд╕реЗ рдмрдирд╛рдпрд╛ рдЬрд╛рдП, рдореБрдЭреЗ рдЗрд╕ рдкрд░ рд╡рд┐рдЪрд╛рд░ рдХрд░рдирд╛ рд╣реЛрдЧрд╛ред рдЬрд▓реНрдж рд╣реА рдПрдХ рдкреАрдЖрд░ рдХреА рдЙрдореНрдореАрдж рди рдХрд░реЗрдВ (рдЙрди рдЬрдЧрд╣реЛрдВ рдкрд░ рдЫреБрдЯреНрдЯреА рд╣реЛрдЧреА рдЬрд╣рд╛рдВ рдЗрдВрдЯрд░рдиреЗрдЯ рджреБрд░реНрд▓рдн рд╣реИ)ред

рдХреЛрдИ рдмрд╛рдд рдирд╣реАрдВред рдореИрдВ

рдпрд╣рд╛рдВ рдХреЛрдИ рдЬрд▓реНрджреА рдирд╣реАрдВ рд╣реИред рдмреЗрд╣рддрд░ рд╣реЛрдЧрд╛ рдХрд┐ рд╣рдо рд╡рд┐рдЪрд╛рд░ рдХрд░реЗрдВ, рдпрджрд┐ рдмрд┐рд▓реНрдХреБрд▓ред

@carltongibson , рдареАрдХ рд╣реИ, рд╣рдордиреЗ рдереЛрдбрд╝рд╛ рд╡рд┐рдЪрд╛рд░ рдХрд┐рдпрд╛, рд▓реЗрдХрд┐рди OrderingFilter рдХреЗ рдкреБрд░рд╛рдиреЗ рдФрд░ рдирдП рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдХреЗ рд╕рдВрдпреЛрдЬрди рдХрд╛ рдПрдХ рд╕реНрд╡реАрдХрд╛рд░реНрдп рддрд░реАрдХрд╛ рдирд╣реАрдВ рдорд┐рд▓рд╛ред

рдЕрдЧрд░ рд╣рдо рд╕рдм рдХреБрдЫ рдПрдХ рд╡рд░реНрдЧ рдореЗрдВ рд░рдЦрдиреЗ рдЬрд╛ рд░рд╣реЗ рд╣реИрдВ, рддреЛ рд╕рдмрд╕реЗ рдмрдбрд╝реА рд╕рдорд╕реНрдпрд╛ рдпрд╣ рд╣реИ рдХрд┐ рдкреБрд░рд╛рдиреЗ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдореЗрдВ рдлрд╝реАрд▓реНрдб model_field - parameter_name рдЬреЛрдбрд╝реЗ рдХреЛ рд╕реНрдЯреЛрд░ рдХрд░рддреЗ рд╣реИрдВ, рдЬрдмрдХрд┐ рдирдП рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдореЗрдВ рдпрд╣ рд╡рд┐рдкрд░реАрдд parameter_name рд╣реИред model_field (рдпрд╣ рд╣реЛрдирд╛ рдЪрд╛рд╣рд┐рдП, рдХреНрдпреЛрдВрдХрд┐ model_field рдХреЗ рдмрдЬрд╛рдп рдПрдХ рдЕрднрд┐рд╡реНрдпрдХреНрддрд┐ рдкрд╛рд░рд┐рдд рдХреА рдЬрд╛ рд╕рдХрддреА рд╣реИ, рдЬрд┐рд╕реЗ рдХреЗрд╡рд▓ рдПрдХ рдореВрд▓реНрдп рдореЗрдВ рд╕рдВрдЧреНрд░рд╣реАрдд рдХрд┐рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИ, рд▓реЗрдХрд┐рди рдПрдХ рд╢рдмреНрджрдХреЛрд╢ рдореЗрдВ рдПрдХ рдХреБрдВрдЬреА рдореЗрдВ рдирд╣реАрдВ) .

рддрдХрдиреАрдХреА рд░реВрдк рд╕реЗ, "рдкреБрд░рд╛рдиреЗ рддрд░реАрдХреЗ рд╕реЗ" рд╢рдмреНрджрдХреЛрд╢ рдХреА рд╡реНрдпрд╛рдЦреНрдпрд╛ рдХрд░рдХреЗ рдЗрд╕ рд╕рдорд╕реНрдпрд╛ рдХреЛ рджреВрд░ рдХрд░рдирд╛ рд╕рдВрднрд╡ рд╣реИ рдпрджрд┐ рдЗрд╕рдореЗрдВ рдХреЗрд╡рд▓ рддрд╛рд░ рд╣реЛрдВ, рдФрд░ "рдирдП рддрд░реАрдХреЗ рд╕реЗ" рдЕрдиреНрдпрдерд╛ред рдЗрд╕ рдорд╛рдорд▓реЗ рдореЗрдВ рдЕрднрд┐рд╡реНрдпрдХреНрддрд┐ рджреНрд╡рд╛рд░рд╛ рдЖрджреЗрд╢ рджреЗрдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рдЙрддреНрдкрдиреНрди рд╣реЛрдиреЗ рдкрд░ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдХреЛ рд╢рдмреНрджрдХреЛрд╢ рдкреНрд░рд╡рд┐рд╖реНрдЯрд┐рдпреЛрдВ рдХреЛ "рдлреНрд▓рд┐рдк" рдХрд░рдирд╛ рд╣реЛрдЧрд╛ред рдФрд░ рд╢рдмреНрджрдХреЛрд╢ рдкреНрд░рд╡рд┐рд╖реНрдЯрд┐рдпреЛрдВ рдХреЛ рд╡рд╛рдкрд╕ "рдлреНрд▓рд┐рдк" рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд╕рд╛рд╡рдзрд╛рди рд░рд╣реЗрдВ, рдХреНрдпрд╛ рдСрд░реНрдбрд░рд┐рдВрдЧ рдЕрднрд┐рд╡реНрдпрдХреНрддрд┐ рдХреЛ рд╣рдЯрд╛ рджрд┐рдпрд╛ рдЬрд╛рдирд╛ рдЪрд╛рд╣рд┐рдП ... рдпрд╣ рдореЗрд░реЗ рд▓рд┐рдП рдПрдХ рднрдпрд╛рдирдХ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдЕрдиреБрднрд╡ рдХреА рддрд░рд╣ рд▓рдЧрддрд╛ рд╣реИред рдпрд╣ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдХреЛ рднреА рдЬрдЯрд┐рд▓ рдмрдирд╛ рджреЗрдЧрд╛ред

рдХреНрдпрд╛ рдЖрдкрдХреЛ рд▓рдЧрддрд╛ рд╣реИ рдХрд┐ ExpressionOrderingFilter рдЬреИрд╕реЗ рдХрд┐рд╕реА рднрд┐рдиреНрди рдирд╛рдо рд╕реЗ рдкреБрд░рд╛рдиреЗ рдХреЗ рд╕рд╛рде рдирдпрд╛ рдСрд░реНрдбрд░рд┐рдВрдЧрдлрд╝рд┐рд▓реНрдЯрд░ рдкреЗрд╢ рдХрд░рдирд╛ рд╕рдВрднрд╡ рд╣реИ?

рдпрд╣ рдмрд╣реБрдд рдЕрдЪреНрдЫрд╛ рд╣реИ, рдареАрдХ рдЗрд╕ рд╕рдордп рдореБрдЭреЗ рдЗрд╕рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рдереА! рдзрдиреНрдпрд╡рд╛рдж @earshinov!

рдореИрдВ рдХреБрдЫ рдРрд╕рд╛ рдХреИрд╕реЗ рдХрд░реВрдВрдЧрд╛:

MyModel.objects.all().order_by(F('price').desc(nulls_last=True))

рдЖрдкрдХреЗ рдСрд░реНрдбрд░рд┐рдВрдЧ рдлрд╝рд┐рд▓реНрдЯрд░ рдХреЗ рд╕рд╛рде?

    o = ExpressionOrderingFilter(

        fields={
                'price': F('price').desc(nulls_last=True)
        }

    )

рдмрд╣реБрдд рдЕрдЪреНрдЫрд╛ рдХрд╛рдо рдХрд░рдиреЗ рд▓рдЧрддрд╛ рд╣реИ!
рдЕрдм рдХреЗрд╡рд▓ рдПрдХ рдЪреАрдЬ рдмрдЪреА рд╣реИ, рдпрд╣ рдкрддрд╛ рд▓рдЧрд╛рдирд╛ рд╣реИ рдХрд┐ рдПрдХрд╛рдзрд┐рдХ рдлрд┐рд▓реНрдЯрд░ рдХреЛ рдХреИрд╕реЗ рд╕рдВрдпреЛрдЬрд┐рдд рдХрд┐рдпрд╛ рдЬрд╛рдП, рдПрдХреЗред 'рдХреАрдордд' рдФрд░ 'рд╕реНрдЯреЙрдХ' рдПрдХ рдореЗрдВред

рдЕрдЪ - рдЗрд╕ рдкрд░ рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛ рджреЗрдирд╛ рд╢реБрд░реВ рдХрд░ рджрд┐рдпрд╛ рд▓реЗрдХрд┐рди рдореЗрд░рд╛ рдХрдВрдкреНрдпреВрдЯрд░ рдЦрд░рд╛рдм рд╣реЛ рдЧрдпрд╛ред

рдореБрдЭреЗ рд▓рдЧрддрд╛ рд╣реИ рдХрд┐ рдпрд╣рд╛рдВ рдкреНрд░рд╕реНрддрд╛рд╡рд┐рдд рдкрд░рд┐рд╡рд░реНрддрди рд╕рдордЭрджрд╛рд░ рд╣реИрдВред model_field - parameter_name рдЬреЛрдбрд╝реА рдЙрд╕ рд╕рдордп рд╕рдордЭ рдореЗрдВ рдЖрдИ рдХреНрдпреЛрдВрдХрд┐ рд╣рдо рдЖрдо рддреМрд░ рдкрд░ рдореЙрдбрд▓ рд╕реЗ рдЙрдЬрд╛рдЧрд░ рдкреИрд░рд╛/рдлреЙрд░реНрдо/рдЖрджрд┐ рдкреНрд░рд╛рдкреНрдд рдХрд░рддреЗ рд╣реИрдВ, рд▓реЗрдХрд┐рди рдРрд╕рд╛ рдХреЛрдИ рдХрд╛рд░рдг рдирд╣реАрдВ рд╣реИ рдХрд┐ рдпрд╣ рдЖрд╡рд╢реНрдпрдХ рдХреНрдпреЛрдВ рд╣реИред рдореИрдкрд┐рдВрдЧ рдХреА рдЕрджрд▓рд╛-рдмрджрд▓реА рдХрд░рдирд╛ рд╕рдордЭ рдореЗрдВ рдЖрддрд╛ рд╣реИ, рдФрд░ рд╣рдореЗрдВ рдЕрдзрд┐рдХ рдЬрдЯрд┐рд▓ рдЕрднрд┐рд╡реНрдпрдХреНрддрд┐рдпреЛрдВ рдХрд╛ рд▓рд╛рдн рдЙрдард╛рдиреЗ рдХреА рдЕрдиреБрдорддрд┐ рджреЗрддрд╛ рд╣реИред

рд╕рд╛рде рд╣реА, рдореБрдЭреЗ рдирд╣реАрдВ рд▓рдЧрддрд╛ рдХрд┐ рдмрд╣рд┐рд╖реНрдХрд░рдг рдкреНрд░рдХреНрд░рд┐рдпрд╛ рдмреЗрд╣рдж рдХрдард┐рди рд╣реЛрдЧреА, рдФрд░ рдореБрдЭреЗ рдЗрд╕рдореЗрдВ рдорджрдж рдХрд░рдиреЗ рдореЗрдВ рдЦреБрд╢реА рд╣реЛрдЧреАред рдореВрд▓ рд░реВрдк рд╕реЗ, fields рдФрд░ field_labels params рдФрд░ param_labels рдореЗрдВ рдкрд░рд┐рд╡рд░реНрддрд┐рдд рд╣реЛ рдЬрд╛рдПрдВрдЧреЗред рдкрд╢реНрдЪ рд╕рдВрдЧрддрддрд╛ рдмрдирд╛рдП рд░рдЦрддреЗ рд╣реБрдП, рдФрд░ рдкреБрд░рд╛рдиреЗ рддрд░реНрдХреЛрдВ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдиреЗ рд╡рд╛рд▓реЗ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛рдУрдВ рдХреЗ рд▓рд┐рдП рдмрд╣рд┐рд╖реНрдХрд░рдг рдиреЛрдЯрд┐рд╕ рдЬрд╛рд░реА рдХрд░рддреЗ рд╣реБрдП, рдПрдХ рд╕реЗ рджреВрд╕рд░реЗ рдореЗрдВ рдХрдирд╡рд░реНрдЯ рдХрд░рдирд╛ рдХрд╛рдлреА рдЖрд╕рд╛рди рд╣реИред

рдЕрд╡рд░реЛрд╣реА рдорд╛рдорд▓реЗ ( param рдмрдирд╛рдо -param ) рдХреЗ рд▓рд┐рдП рдЬрдЯрд┐рд▓ рдЕрднрд┐рд╡реНрдпрдХреНрддрд┐рдпреЛрдВ рдХрд╛ рд╕реНрд╡рдЪрд╛рд▓рд┐рдд рд░реВрдкрд╛рдВрддрд░рдг рд╡рд┐рдЪрд╛рд░ рдХрд░рдиреЗ рд╡рд╛рд▓реА рдПрдХ рдмрд╛рдд рд╣реИред рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рдпрджрд┐ рдЖрд░реЛрд╣реА .asc(nulls_last=True) рд╣реИ, рддреЛ рдХреНрдпрд╛ рдЗрд╕рдХрд╛ рд╡реНрдпреБрддреНрдХреНрд░рдо .desc(nulls_first=True) рд╣реЛрдирд╛ рдЪрд╛рд╣рд┐рдП, рдпрд╛ рдирд▓ рдХреЛ рдХреНрд░рдо рдХреА рджрд┐рд╢рд╛ рдХреА рдкрд░рд╡рд╛рд╣ рдХрд┐рдП рдмрд┐рдирд╛ рдЕрдВрддрд┐рдо рд░рд╣рдирд╛ рдЪрд╛рд╣рд┐рдП?

рдареАрдХ рд╣реИ, рд╕реБрдкрд░ @rpkilbyред

рд╣рдореЗрдВ рдпрд╣рд╛рдВ рдЖрдЧреЗ рдмрдврд╝рддреЗ рд╣реБрдП рджреЗрдЦрдХрд░ рдЦреБрд╢реА рд╣реБрдИред рдореЗрд░реА рдореБрдЦреНрдп рдЪрд┐рдВрддрд╛ рдпрд╣ рд╣реИ рдХрд┐ рд╣рдо рдЬреЛ рдХрд░ рд░рд╣реЗ рд╣реИрдВ рдЙрд╕рдХрд╛ рд╕рд╣реА рддрд░реАрдХреЗ рд╕реЗ рджрд╕реНрддрд╛рд╡реЗрдЬреАрдХрд░рдг рдХрд░реЗрдВред рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдкрд╣рд▓реЗ рд╕реЗ рд╣реА df рдбреЙрдХреНрд╕ рдХреЛ рдереЛрдбрд╝рд╛ _terse_ рдвреВрдВрдврддреЗ рд╣реИрдВ, рдХреНрдпрд╛ рд╣рдо рдХрд╣реЗрдВрдЧреЗ ЁЯЩВ - рдПрдкреАрдЖрдИ рдЬреЛрдбрд╝рдиреЗ рдореЗрдВ рдЦреБрд╢реА рд╣реИ рд▓реЗрдХрд┐рди рд╣рдореЗрдВ рдпрд╣ рд╕реБрдирд┐рд╢реНрдЪрд┐рдд рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ рдХрд┐ рдпрд╣ рд╕реНрдкрд╖реНрдЯ рд╣реИред

рдРрд╕реА рдЪреАрдЬ рдХреЗ рд▓рд┐рдП рдПрдХ рд╡реНрдпрдХреНрддрд┐ рдХрд╛ рдкреНрд░рдпрд╛рд╕ рд╣реЛрдирд╛ рдЬрд░реВрд░реА рдирд╣реАрдВ рд╣реИред

@rpkilby , @carltongibson , рдЕрдЧрд░ рдЖрдкрдХреЛ рдкрд░рд┐рдпреЛрдЬрдирд╛ рдореЗрдВ рдореЗрд░реЗ рдкрд░рд┐рд╡рд░реНрддрдиреЛрдВ рдХреЛ рдПрдХреАрдХреГрдд рдХрд░рдиреЗ рдореЗрдВ рдореЗрд░реА рдорджрдж рдХреА рдЬрд╝рд░реВрд░рдд рд╣реИ, рддреЛ рдореБрдЭреЗ рд▓рдЧрддрд╛ рд╣реИ рдХрд┐ рдореИрдВ рдХреБрдЫ рд╕рдордп рд▓рдЧрд╛ рд╕рдХрддрд╛ рд╣реВрдВред рд▓реЗрдХрд┐рди рдореБрдЭреЗ рджрд┐рд╢рд╛-рдирд┐рд░реНрджреЗрд╢ рдЪрд╛рд╣рд┐рдПред рдореИрдВ рдХрд╣рд╛рдВ рдФрд░ рдХреИрд╕реЗ рд╢реБрд░реВ рдХрд░реВрдВ?

рд╣рд╛рдп @earshinovред рдореИрдВ рджрд╕реНрддрд╛рд╡реЗрдЬрд╝реЛрдВ рдХрд╛ рдорд╕реМрджрд╛ рддреИрдпрд╛рд░ рдХрд░рдХреЗ рд╢реБрд░реВ рдХрд░реВрдВрдЧрд╛ред рд╣рдо рдХреНрдпрд╛ рдХрд╣рд╛рдиреА рд╕реБрдирд╛рддреЗ рд╣реИрдВ? рд╡рд╣рд╛рдВ рд╕реЗ рдХреЛрдб рдмрджрд▓ рдЬрд╛рддрд╛ рд╣реИред рдПрдХ рдЬрдирд╕рдВрдкрд░реНрдХ, рднрд▓реЗ рд╣реА рд╕рд┐рд░реНрдл рдПрдХ рдорд╕реМрджрд╛ рд╣рдореЗрдВ рдмрд╛рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдХреБрдЫ рджреЗрддрд╛ рд╣реИред

рдЗрд╕рдХреЗ рдЕрд▓рд╛рд╡рд╛, рдпрджрд┐ рдЖрдкрдХреЗ рдкрд╛рд╕ рдорд╛рдорд▓реЗ рд╣реИрдВ рддреЛ рдкрд░реАрдХреНрд╖рдг рдХрд░реЗрдВред рдЕрдВрддрд┐рдо рдкрд░рд┐рд╡рд░реНрддрдиреЛрдВ рд╕реЗ рдореЗрд▓ рдЦрд╛рдиреЗ рдХреЗ рд▓рд┐рдП рдЙрдиреНрд╣реЗрдВ рд╕рдорд╛рдпреЛрдЬрд┐рдд рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реЛ рд╕рдХрддреА рд╣реИ, рд▓реЗрдХрд┐рди рд╡рд┐рднрд┐рдиреНрди рдорд╛рдорд▓реЛрдВ рдкрд░ рд╡рд┐рдЪрд╛рд░ рдХрд░рдирд╛ рдмрд╣реБрдд рдорджрджрдЧрд╛рд░ рд╣реЛрдЧрд╛ (рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, .asc рдХреЛ .desc рдореЗрдВ рдкрд░рд┐рд╡рд░реНрддрд┐рдд рдХрд░рдирд╛, nulls_fist / nulls_last рдХреЛ рд╕рдВрднрд╛рд▓рдирд╛

рдХреНрдпрд╛ рдпрд╣ рдкреГрд╖реНрда рдЙрдкрдпреЛрдЧреА рдерд╛?
0 / 5 - 0 рд░реЗрдЯрд┐рдВрдЧреНрд╕

рд╕рдВрдмрдВрдзрд┐рдд рдореБрджреНрджреЛрдВ

hakib picture hakib  ┬╖  3рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ

Alexx-G picture Alexx-G  ┬╖  4рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ

blueyed picture blueyed  ┬╖  4рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ

GuillaumeCisco picture GuillaumeCisco  ┬╖  3рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ

xtrinch picture xtrinch  ┬╖  4рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ