Сериализатор получает null для столбцов таблицы в обратном направлении внешнего ключа

В сценарии обработки транзакций мы имеем:

  • таблицу order для регистрации входящих транзакций, и
  • таблица order_log для регистрации обновлений по записанному заказу, с внешним ключом к таблице order.

Зарегистрированный заказ может иметь от нуля до нескольких обновлений в журнале.

Мы хотим сделать уплощенное представление двух таблиц, как SQL запрос, выбирающий из order left outer join order_log, со следующим поведением:

  • если у заказа нет обновления, перечислите заказ в совместном списке с нулевыми значениями;
  • если ордер имеет одно обновление, перечислите ордер в совместном списке с журналом обновлений;
  • если заказ имеет несколько обновлений, перечислите заказ несколько раз совместно с каждым журналом обновлений.

В приведенном ниже примере исходного кода мы использовали .prefetch_related('orderlog_set'), и это дало желаемый эффект сглаживания, а журнал бэкенда Django показывает SQL-запрос left-outer-join, отправленный в базу данных, как и ожидалось. Таким образом, взаимное отношение работает корректно для всех трех условий, описанных выше.

Однако мы не можем получить столбцы в order_item, так как таблица находится в обратном направлении внешнего ключа.

Сериализатор FlatOrderLogSerializer основывает свою мета-модель на таблице order, поэтому он обращается к таблице order_log по обратному или обратному направлению внешнего ключа. В результате сериализатор не может получить нужный столбец и всегда получает нулевые значения.

Мы правильно извлекли столбцы в родной таблице и таблицах, на которые ссылается обычный внешний ключ. К сожалению, просто обратный/направленный внешний ключ не работает.

Мы новички в этой части Django и не уверены, как правильно установить атрибут source объекта field. Мы пробовали serializers.ReadOnlyField(source='orderlog_set__update_ts', ... и несколько других вариантов, но пока ничего не получается. Смотрите комментарии в коде примера для более подробной информации.

Просто дайте мне знать, если вам нужна дополнительная информация, и любые предложения будут высоко оценены.

# Table 'order'
class Order(models.Model):
    ...

# Table 'order_log'
class OrderLog(models.Model):
    order = models.ForeignKey('Order')
    update_ts = models.DateTimeField(auto_now=True, editable=False, verbose_name="Last Updated On")
    ...

# The serializer
class FlatOrderLogSerializer(serializers.ModelSerializer):
    # Standing at table 'order', it refers to table 'order_log' by a reverse (backward) foreign-key.
    # The field with source='orderlog_set__update_ts' always gets null, and
    # we tried other source settings, e.g. 'orderlog_set.update_ts', or 'orderlog.update_ts', 
    # unfortunately nothing works so far.
    update_ts = serializers.ReadOnlyField(source='orderlog_set__update_ts', allow_null=True)
    ...
    class Meta:
        model = Order
        fields = (
            'update_ts',
            ...
        )

# The view
class FlatOrderLogView(generics.ListAPIView):
    serializer_class = rest_models.FlatOrderLogSerializer
    ...
    def get_queryset(self):
        flat_orderlogs = Order.active_objects.filter(
            ...
        ).prefetch_related(
            'orderlog_set',
        ).all()
        return flat_orderlogs

После некоторого поиска в Google, это выглядит следующим образом:

  • Фреймворк Django предоставляет стандартную функцию select_related() для объединения по внешнему ключу в прямом направлении. Бэкэнд реализует эту функцию как SQL-запрос с внутренним соединением.
  • Однако мы не нашли элегантного способа просмотра назад по внешнему соединению. Вместо этого в большинстве статей рекомендуется использовать функцию prefetch_related() и делать вложенный сериализатор. И этот шаблон проектирования, похоже, является обычной практикой.

Ниже приведены некоторые предварительные соображения, и мы надеемся получить отзывы или поправки от экспертов:

С помощью SQL-запроса мы можем обратиться к другой таблице по внешнему ключу в обратном направлении с помощью outer-join. Однако, похоже, что Django не поощряет этот интуитивный шаблон проектирования, поэтому не реализовал стандартный метод. Если это подтвердится, мы откажемся от запроса outer-join и будем искать другие решения.

Кстати, мы также можем сделать небольшое уточнение по терминологии:

Мы не нашли в учебниках определения направленного атрибута внешнего ключа, например, прямого, обратного или обратного направления; однако эти слова встречаются в сетевой литературе.

На ER-диаграмме ниже таблица app_orderitemlog содержит внешний ключ, указывающий на таблицу app_orderitem. Таким образом, с помощью этого отношения внешнего ключа:

  • Таблица app_orderitemlog ссылается на таблицу app_orderitem в направлении внешнего ключа forward.
  • Таблица app_orderitem ссылается на таблицу app_orderitemlog в направлении backward, также называемом reverse направлением.

ER-Diagram

Вернуться на верх