Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create a queryset that filters multiple fields based on a single condition in Django?

My model is

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

I need a simple queryset that applies a single condition on all the five fields of the model without writing every combination of fields and filtering them.

For example I want to apply a condition of checking for None to two or more fields

TestModel.objects.filter(two_or_more_fields=None)

I don't want write every possible combination of 5 fields to find the queryset with any two or more fields as None. In other words, is there a better way to accomplish this than:

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
     )

I think there should be a better and simple way to accomplish this.

like image 724
javed Avatar asked Mar 29 '17 08:03

javed


2 Answers

After spending hours I could not find a simple way to do this using built in Django filter constructs. However I found this solution which is closer to what I was searching for:

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)
like image 115
javed Avatar answered Oct 06 '22 17:10

javed


I'm not aware of any method for filtering by number of non null fields in Django.

One less clumsy and lookup-efficient workaround I would suggest is to store number of null fields (null_count) in Model itself as a field.

You can do that by easily overriding save method of 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)

And in view, filter on null_count.

TestModel.objects.filter(null_count_gt=2)
like image 28
narendra-choudhary Avatar answered Oct 06 '22 18:10

narendra-choudhary