Использование Django 1.8

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

class MyModel(models.Model):
    field_a = models.IntegerField(null=True)
    field_b = models.IntegerField(null=True)
    field_c = models.IntegerField(null=True)
    field_d = models.IntegerField(null=True)
(field_a__isnull=False, field_b__isnull=True, field_c__isnull=True, field_d__isnull=True)
OR
(field_a__isnull=True, field_b__isnull=False, field_c__isnull=True, field_d__isnull=True)
OR
(field_a__isnull=True, field_b__isnull=True, field_c__isnull=False, field_d__isnull=True)
OR
(field_a__isnull=True, field_b__isnull=True, field_c__isnull=True, field_d__isnull=False)

Таким образом, набор запросов должен возвращать все объекты, которые имеют только одно из полей в заполненной модели, а остальные поля равны нулю. Есть ли способ добиться этого с помощью запросов Django?

1
Mikey Lockwood 15 Апр 2019 в 14:30

2 ответа

Лучший ответ

Я смог построить это динамически с Q

fields = ['field_a', 'field_b', 'field_c', 'field_d']

q_query = reduce(operator.or_, (
    (reduce(operator.and_, (
        eval('Q({}__isnull={})'.format(f, False if f == field else True))
        for f in fields
    )))
    for field in fields
))

MyModel.objects.filter(q_query)

Это создает объект Q с фильтрами AND, вложенными в фильтр OR, и выполняет запросы к ним.

<Q: (
  OR: (
    AND: ('field_a__isnull', False), ('field_b__isnull', True), ('field_c__isnull', True), ('field_d__isnull', True)
  ), (
    AND: ('field_a__isnull', True), ('field_b__isnull', False), ('field_c__isnull', True), ('field_d__isnull', True)
  ), (
    AND: ('field_a__isnull', True), ('field_b__isnull', True), ('field_c__isnull', False), ('field_d__isnull', True)
  ), (
    AND: ('field_a__isnull', True), ('field_b__isnull', True), ('field_c__isnull', True), ('field_d__isnull', False)
  )
)>

ИЗМЕНИТЬ

Я еще раз взглянул на это и изменил создание q_query, чтобы не использовать eval()

q_query = reduce(operator.or_, (
    (reduce(operator.and_, [
        Q(**{'{}__isnull'.format(f): False if f == field else True})
        for f in fields
    ]))
    for field in fields
))
1
Mikey Lockwood 3 Окт 2019 в 14:57

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

class MyModel(models.Model):
    field_a = models.IntegerField(null=True)
    field_b = models.IntegerField(null=True)
    field_c = models.IntegerField(null=True)
    field_d = models.IntegerField(null=True)

    @property
    def populated_field_count(self):
        fields = [self.field_a, self.field_b, self.field_c, self.field_d]
        return len([f for f in fields if f != None])
objects_with_one_field = [obj for obj in MyModel.objects.all() if obj.populated_field_count == 1)]

Обратите внимание, что свойство populated_field_count нельзя использовать в запросе Django, то есть оно не работает на уровне базы данных, что ухудшает производительность.

# THIS WILL NOT WORK IF USING OPTION ONE
objs = MyModel.objects.filter(populated_field_count=1)

РЕДАКТИРОВАТЬ: В качестве второго варианта, если вам нужно сделать это с помощью запросов, вы можете использовать Django Q, чтобы сделать расширенный запрос. Ответы на этот вариант уже даны.

В качестве третьего варианта, если вы не хотите использовать Django Q, вы можете добавить populated_field_count в качестве свойства IntegerField модели. Затем перезапишите функцию сохранения, чтобы вычислить количество полей, заполняемых при каждом сохранении. Это позволит вам сделать запрос, используя:

MyModel.objects.filter(populated_field_count=1)
0
labennett 15 Апр 2019 в 16:27