У меня есть две модели, которые наследуются от другой модели. Пример:
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')
. Но имя находится в подклассах.
Можно ли объединить столбцы «имени» двух подклассов и затем выполнить сортировку по ним? Или есть другое решение?
🤔 А знаете ли вы, что...
Основной принцип Django — DRY (Don't Repeat Yourself), что способствует уменьшению дублирования кода.
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)