Комбинированный столбец order_by в django
У меня есть две модели, которые наследуют от другой модели. Пример:
class Parent(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, verbose_name="ID")
class A(Parent):
name = models.CharField(max_length=255, verbose_name="Name")
class BProxy(Parent):
target = models.OneToOneField('B', on_delete=models.CASCADE)
class B(models.Model):
name = models.CharField(max_length=255, verbose_name="Name")
В настоящее время мой запрос выглядит следующим образом:
Parent.objects.all()
В своем сериализаторе я проверяю, к какому подклассу относится родительский объект (hasattr(obj, 'a')
), а затем использую либо name = obj.a.name
, либо name = obj.b.target.name
для сериализованных данных.
Но теперь я хочу отсортировать набор запросов для вывода.
Обычно я использую здесь Parent.objects.all().order_by('name')
. Но имя находится в подклассах.
Можно ли объединить столбцы "имя" двух подклассов и затем сортировать по ним? Или есть другое решение?
order_by
может принимать имя поля в подклассах, т.е. Parent.objects.all().order_by('-bproxy__target__name', '-a__name')
. В результате получится следующий запрос, который будет упорядочен по имени A и имени B
SELECT "tmp_parent"."id" FROM "tmp_parent" LEFT OUTER JOIN "tmp_bproxy" ON ("tmp_parent"."id" = "tmp_bproxy"."parent_ptr_id") LEFT OUTER JOIN "tmp_b" ON ("tmp_bproxy"."target_id" = "tmp_b"."id") LEFT OUTER JOIN "tmp_a" ON ("tmp_parent"."id" = "tmp_a"."parent_ptr_id") ORDER BY "tmp_b"."name" DESC, "tmp_a"."name" DESC
это упорядочит сначала A, затем B, если вы хотите упорядочить и A, и B, рассмотрите ответ @temunel, или используйте
from django.db.models.functions import Coalesce
Parent.objects.annotate(
name=Coalesce("a__name", "bproxy__target__name")
).order_by('name')
означает, что если a__name
равен null или не существует, будет использовано значение bproxy__target__name
.
Примечание: не во всех базах данных есть COALESCE() и a__name
и bproxy__target__name
значения должны быть совместимого типа для упорядочивания
Чтобы отсортировать Parent
объекты по name
полям своих подклассов, нужно сделать annotating
кверисет с name
полем из обоих подклассов, а затем упорядочить по этому аннотированному полю.
Вот как вы можете это сделать:
from django.db.models import Case, When, Value, CharField
queryset = Parent.objects.annotate(
name=Case(
When(a__isnull=False, then='a__name'),
When(bproxy__isnull=False, then='bproxy__target__name'),
default=Value(''),
output_field=CharField(),
)
).order_by('name')
Надеюсь, это решит вашу проблему.
По-моему, это почти похоже на проблему XY, но трудно сказать без дополнительной информации о том, чего вы пытаетесь достичь. Например, вы спрашиваете, как сделать X, но на самом деле вам нужен ответ, как лучше сделать Y. Однако я отвечу, предполагая, что вам нужно сделать X.
Из вашего запроса, Parent.objects.all()
, вы не сможете упорядочить данные по имени, поскольку вы запрашиваете только таблицу Parent
. Вам все равно придется искать нужные данные в A
или BProxy
(что позволит вам затем каскадно искать B
). Мой вопрос заключается в том, должна ли Parent
действительно быть собственной таблицей или вам следует использовать абстрактную таблицу и/или прокси-таблицу. Я не могу дать вам совет, если вы не знаете больше о том, что вы пытаетесь сделать, но вы можете прочитать некоторые статьи на эту тему.
Одним из подходов, помогающих преобразовать Parent
в его дочерние A
или BProxy
, было бы создание вспомогательной функции типа такой:
class Parent(models.Model):
...
def child(self):
try:
return A.objects.get(id=self.id)
except A.DoesNotExist:
return BProxy.objects.get(id=self.id)
В том виде, в котором он написан, он не идеален, поскольку будет выдавать ошибку BProxy.DoesNotExist
, если строка Parent
не совпадает ни с одной из строк A
или BProxy
. Однако это может быть нормально, если все Parent
всегда будут теми или иными. Теперь вы можете преобразовать QuerySet
в список дочерних объектов:
children = [x.child() for x in m.Parent.objects.all()]
Это означает, что в данный момент будут получены все запрашиваемые строки, и вам придется сортировать их в Python, но на данный момент это относительно просто. Чтобы помочь сделать BProxy
более похожим на A
, вы можете определить для него свойство следующим образом:
class BProxy(Parent):
...
@property
def name(self):
return self.target.name
Теперь вы можете использовать sorted()
на свойстве name, которое есть у обоих:
ordered_children = sorted(children, key=lambda x: x.name)