Как исключить результаты с помощью get_object_or_404?

В Django вы можете использовать exclude для создания SQL, аналогичного not equal. Пример может быть.

Model.objects.exclude(status='deleted')

Теперь это отлично работает и исключает очень гибкий. Поскольку я немного ленив, я хотел бы получить эту функциональность при использовании get_object_or_404, но я не нашел способ сделать это, так как вы не можете использовать exclude на get_object_or_404.

Я хочу сделать что-то вроде этого:

model = get_object_or_404(pk=id, status__exclude='deleted')

Но, к сожалению, это не работает, поскольку нет фильтра запроса исключения или подобного. Самое лучшее, что я придумал до сих пор, - это что-то вроде этого:

object = get_object_or_404(pk=id)
if object.status == 'deleted':
    return HttpResponseNotfound('text')

Выполняя что-то подобное, действительно наносит ущерб использованию get_object_or_404, поскольку он больше не является удобным однострочным.

В качестве альтернативы я мог бы сделать:

object = get_object_or_404(pk=id, status__in=['list', 'of', 'items'])

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

Мне интересно, если мне не хватает трюка или функции в django, чтобы использовать get_object_or_404 для получения желаемого результата?

Ответ 1

Используйте django.db.models.Q:

from django.db.models import Q

model = get_object_or_404(MyModel, ~Q(status='deleted'), pk=id)

Объекты Q позволяют использовать NOT (с оператором ~) и OR (с оператором |) в дополнение к AND.

Обратите внимание, что объект Q должен быть до pk=id, потому что аргументы ключевого слова должны быть последними в Python.

Ответ 2

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

queryset = Model.objects.exclude(status='deleted')
get_object_or_404(queryset, pk=1)

Пример Django docs: https://docs.djangoproject.com/en/1.10/topics/http/shortcuts/#id2

Ответ 3

Существует другой способ вместо использования объектов Q. Вместо передачи модели на get_object_or_404 просто передайте QuerySet вместо функции:

model = get_object_or_404(MyModel.objects.filter(pk=id).exclude(status='deleted'))

Один из побочных эффектов этого, однако, заключается в том, что он вызывает исключение MultipleObjectsReturned, если QuerySet возвращает несколько результатов.

Ответ 4

get_object_or_404 использует метод get_queryset диспетчера объектов. Если вы переопределите метод get_queryset, чтобы возвращать только те элементы, которые не "удалены", тогда get_object_or_404 будет автоматически вести себя так, как вы хотите. Однако переопределение get_queryset, как это, вероятно, будет иметь проблемы в другом месте (возможно, на страницах администратора), но вы можете добавить альтернативного менеджера, когда вам нужно получить доступ к мягким удаленным элементам.

from django.db import models

class ModelManger(models.Manger):
    def get_queryset(self):
        return super(ModelManger, self).get_queryset().exclude(status='deleted')

class Model(models.Model):
    # ... model properties here ...

    objects = ModelManager()
    all_objects = models.Manager()

Итак, если вам нужны только не удаленные элементы, вы можете сделать get_object_or_404(Models, id=id), но если вам нужны все элементы, вы можете сделать get_object_or_404(Models.all_objects, id=id).