如何创建一个基于Django中的单个条件过滤多个字段的查询集?

时间:2017-03-29 08:06:41

标签: django django-models

我的模特是

class TestModel(models.Model) 
    field1 = models.IntegerField()
    field2 = models.IntegerField()
    field3 = models.IntegerField()
    field4 = models.IntegerField()
    field5 = models.IntegerField()

我需要一个简单的查询集,它可以在模型的所有五个字段上应用单个条件,而无需编写每个字段组合并对其进行过滤。

例如,我想将检查None的条件应用于两个或更多字段

TestModel.objects.filter(two_or_more_fields=None)

我不想编写5个字段的每个可能组合来查找任何两个或多个字段为None的查询集。换句话说,有没有比这更好的方法:

from django.db.models import Q
TestModel.objects.filter(
    #condition for exactly 2 None
    Q(field1=None & field2=None) |
    Q(field2=None & field3=None) |
    Q(field3=None & field4=None) |
    Q(field4=None & field5=None) |
    Q(field5=None & field1=None) |
    #condition for more than 2 None
    Q(field1=None & field2=None & field3 = None) |
    '''''
    .
    .
    #so on to cover all possible cases of any two or more fields as None
     )

我认为应该有一种更好,更简单的方法来实现这一目标。

3 个答案:

答案 0 :(得分:3)

花了好几个小时后,我找不到使用内置Django过滤器结构的简单方法。但是我发现这个解决方案更接近我所寻找的目标:

field_list = ['field1', 'field2', 'field3', 'field4', 'field5']

def get_all_possible_filter_dict_list_for_a_condition(field_list):

    all_possible_filter_dict_for_a_condition = []
    for field_1, field_2 in combinations(field_list, 2):
        all_possible_filter_dict_for_a_condition.append(
        {
         field_1:None,
         field_2:None 
         }
        )
    return all_possible_filter_dict_for_a_condition


def get_qs_list_to_perform_or_operation(all_possible_filter_dict_list_for_a_condition):

    qs_list_to_perform_or_operation = []
    for i, filter_dict in enumerate(all_possible_filter_dict_list_for_a_condition):
       qs_to_append = qs.filter(**filter_dict)
       qs_list_to_perform_or_operation.append(qs_to_append)
    return qs_list_to_perform_or_operation


def get_qs_to_filter_fields_with_more_than_1_none(qs_list_to_perform_or_operation ):

    final_qs = qs_list_to_perform_or_operation [0]
    for i in range(len(qs_list_to_perform_or_operation ) - 1):
        final_qs = final_qs | qs_list[i + 1]
    return final_qs

all_possible_filter_dict_list_for_a_condition 
   = get_all_possible_filter_dict_list_for_a_condition(field_list)
qs_list_to_perform_or_operation = get_qs_list_to_perform_or_operation(all_possible_filter_dict_list_for_a_condition)
final_qs_to_filter_multiple_fields_with_same_condtion = get_qs_to_filter_fields_with_more_than_1_none(qs_list_to_perform_or_operation)

答案 1 :(得分:1)

我不知道任何按Django中的非空字段数过滤的方法。

我建议的一个不那么笨拙且查找效率高的解决方法是将模型本身中的空字段数(null_count)存储为字段。

您可以通过轻松覆盖save的{​​{1}}方法来实现这一目标。

TestModel

在视图中,过滤def save(self, *args, **kwargs): self.null_count = 0 self.null_count += 1 if self.field1 is None else 0 # update null_count for 4 remaining fields super(TestModel, self).save(*args, **kwargs)

null_count

答案 2 :(得分:0)

由于此处使用的功能,此解决方案特定于POSTGRESQL。但是,如果您使用其他数据库,则可以使用该想法。

您可以混合使用RawSQLARRAY魔法。

我们的想法是使用非空字段的数量来注释查询集中的每个项目:

from django.db.models import Func
from django.db.models.expressions import RawSQL

field_list = ['field1', 'field2', 'field3', 'field4', 'field5']

# field1 IS NULL, field2 IS NULL, field3 IS NULL, .....
statements = ", ".join("{} IS NULL".format(field) for field in field_list)

# create an array for each field by checking if those are NULL
# then, remove the False values e.g. not null ones.
# ARRAY_REMOVE(ARRAY[field1 IS NULL, field2 IS NULL, .....], false)
raw_sql = "ARRAY_REMOVE(ARRAY[{statements}], false)".format(statements=statements)

# now annotate each item with the number of null fields
qs = TestModel.objects.annotate(num_null=Func(RawSQL(sql, ()), function='CARDINALITY')

# on this queryset you can filter by the number of null items you want to check
qs = qs.filter(num_null__gte=2)