Проблема с результатами заказа Django 1.11 - аннотирование счетчика возвращает неправильное значение

Я использую django 1.11:

  • У меня есть определение модели, которое вычисляет количество ложных значений атрибута внешнего ключа, например:

Модель:

class Model(models.Model):
    .
    . 
    . 

    def count_total(self):
        return self.anothermodel_set.filter(val=False).count()

Вид:

class ModelViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = Model.objects.all()
    serializer_class = ModelSerializerClass
    permissions = AuthenticatedReadOnly
    pagination_class = StandardResultsSetPagination

    def list(self, request):
        queryset = self.get_queryset()

        # Other annotations...

        # Attempt 1: returns wrong count
        queryset = queryset.annotate(
            a_count=Count(
                Case(
                    When(anothermodel__val=False, then=1), 
                    default=0, 
                    output_field=IntegerField()
                )
            )
        )

        # Attempt 2: returns wrong count, same as attempt 1
        queryset = queryset.annotate(
            b_count=Count(Q(anothermodel__val=False))
        )

        # Ideally I want to do 
        queryset = queryset.order_by('count_total')

Когда сделаю заказ по count_total, я дам

FieldError at /api/endpoint/ Cannot resolve keyword 'count_total' into field.

Потому что count_total - это определение модели.

Сериализатор:

В моем сериализаторе я изменил определение to_represantation для отладки:

def to_representation(self, instance):
    return {'id': instance.pk, 'a_count': instance.a_count, 'b_count' : instance.b_count, 'correct_count': instance.count_total()}

В противном случае в моем сериализаторе у меня есть:

class Meta:
    model = Model
    fields = ('id', 'title', 'bunch-of-other-stuff', 'count_total')

instance.count_total() возвращает правильный результат, но я не могу просто использовать его как queryset.order_by('count_total'). Мне нужно аннотировать правильное значение, чтобы отсортировать результаты, а также избежать проблем с запросом n + 1.

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
3
0
182
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

def count_total(self):
        return self.anothermodel_set.filter(val=False).count()

Я также не понимаю, почему у вас могут быть вычисления как в модели, так и в вашем представлении. Либо используйте определение модели, либо аннотируйте свой набор запросов, но вам не нужно делать и то, и другое. Если вы пойдете по маршруту определения модели, ваше представление должно выглядеть примерно так:

class ModelViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = Model.objects.all()
    serializer_class = ModelSerializer
    permissions = AuthenticatedReadOnly
    pagination_class = StandardResultsSetPagination

    def list(self, request):

        queryset = self.queryset.order_by('count_total')
        serializer = self.get_serializer(queryset, many=True)

        return Response(serializer.data)

Если вы используете определение модели, вам нужно установить в сериализаторе что-то вроде атрибута read_only:

count_total = serializers.ReadOnlyField(allow_null=True)

class Meta:
    model = Model
    fields = ('id', 'title', 'bunch-of-other-stuff', 'count_total')

Если вы по-прежнему получаете сообщение об ошибке «Невозможно разрешить общее количество в поле», перед запуском сериализатора введите оператор печати в набор представлений, например:

print(str(queryset))

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

Вы имеете в виду что-то вроде count_total = serializers.ReadOnlyField()?

cch 27.10.2018 16:57

Да, это именно то, что я имел в виду. У вас есть это на вашем ModelSerializerClass?

Braden Holt 27.10.2018 17:07

Я только что отредактировал свой ответ, включив в него правильный код сериализатора. В вставленном вами коде отсутствовало поле только для чтения. Какую ошибку вы получаете?

Braden Holt 27.10.2018 17:10
FieldError at /api/endpoint/ Cannot resolve keyword 'count_total' into field.
cch 27.10.2018 17:12

Сообщите мне, помогут ли приведенные выше изменения. Если они не обязательно взглянут на набор запросов, как показано в конце моего ответа.

Braden Holt 27.10.2018 17:43

Позвольте нам продолжить обсуждение в чате.

cch 27.10.2018 18:23
Ответ принят как подходящий

Почти 3 года спустя; но это может сэкономить время для тех, кто столкнется с этим.

# Attempt 2: returns wrong count, same as attempt 1
queryset = queryset.annotate(b_count=Count(Q(anothermodel__val=False)))

Проблема связана с тем, что несколько аннотаций были объединены. Недостающая часть из приведенного выше заключалась в том, чтобы указать, что мы хотим только подсчитывать значения distinct=True, чтобы избежать Эта проблема. Вот так:

# Success
queryset = queryset.annotate(
    b_count=Count(
        Q(anothermodel__val=False), distinct=True
    )
) 

Другие вопросы по теме