Search code examples
pythondjangodjango-modelsdjango-views

Django queryset - exclude True values


I am tryig to build a queryset where only the false values are added.

Models

Models
reference = models.CharField(validators=[MinLengthValidator(15)], max_length=25, primary_key=True)
h0730 = models.BooleanField(default=False)
h0800 = models.BooleanField(default=False)
h0830 = models.BooleanField(default=False)
h0900 = models.BooleanField(default=False)
h0930 = models.BooleanField(default=False)
h1000 = models.BooleanField(default=False)
h1030 = models.BooleanField(default=False)
h1100 = models.BooleanField(default=False)
h1130 = models.BooleanField(default=False)
h1200 = models.BooleanField(default=False)
h1230 = models.BooleanField(default=False)
h1300 = models.BooleanField(default=False)
h1330 = models.BooleanField(default=False)
h1400 = models.BooleanField(default=False)
h1430 = models.BooleanField(default=False)
h1500 = models.BooleanField(default=False)
h1530 = models.BooleanField(default=False)
delivery_date = models.CharField(max_length=8)
is_cancelled = models.BooleanField(default=False)

Views

taken_slots = Order.objects.filter(delivery_date__exact=delivery_date).filter(reference__icontains=code).filter(is_cancelled=False)

slots_remaining = ['h0730', 'h0800', 'h0830', 'h0900', 'h0930', 'h1000', 'h1030', 'h1100', 'h1130', 'h1200', 'h1230', 'h1300', 'h1330', 'h1400', 'h1430', 'h1500', 'h1530']

for slot in taken_slots:
    if slot.h0730 and 'h0730' in slots_remaining:
        slots_remaining.remove('h0730')
    if slot.h0800 and 'h0800' in slots_remaining:
        slots_remaining.remove('h0800')
        ...
        ...

The above for loop works as is expected but I am trying to optimize the process. For example if there are 100 references for the day, "taken_slots" will will be iterated 100 times.

The expected output after the for loop completes is that the "slots_remaining" list will only have the False values remaining, e.g.

ref1 = h0730 and h0930 is True and every other slot False
ref2 = h0900 is True and every other slot False
ref3 = h1030 is True and every other slot False
ref4 = h1230 is True and every other slot False
ref5 = h1300 and h1330 is True and every other slot False
ref6 = h1500 is True and every other slot False

If h0730, h0900, h0930, h1030, h1230, h1300, h1330, h1500 from the 6 references are True.
slots_remaining must remain with ['h0800', 'h0830', 'h1000', 'h1100', 'h1130', 'h1200', 'h1400', 'h1430', 'h1530']

Is there a way to build slots_remaining list directly from the taken_slots query or atleast avoid the for loop. I have seen examples of distinct, annotate and union but am probably doing it wrong as I haven't managed to get it working.

EDIT: To add a bit more context, an example of a queryset for one reference would be something like this:

<QuerySet [{'h0730': True, 'h0800': False, 'h0830': False, 'h0900': False, 'h0930': False, 'h1000': False, 'h1030': False, 'h1100': False, 'h1130': False, 'h1200': False, 'h1230': False, 'h1300': False, 'h1330': False, 'h1400': True, 'h1430': True, 'h1500': True, 'h1530': True}]>

What I am trying to accomplish is basically a merge of all values for multiple references. For example, the 6 references below would be part of a single query set

ref_start = ['h0730', 'h0800', 'h0830', 'h0900', 'h0930', 'h1000', 'h1030', 'h1100', 'h1130', 'h1200', 'h1230', 'h1300', 'h1330', 'h1400', 'h1430', 'h1500', 'h1530']

ref1 = ['True', 'h0800', 'h0830', 'True', 'h0930', 'h1000', 'h1030', 'h1100', 'h1130', 'h1200', 'h1230', 'h1300', 'h1330', 'h1400', 'h1430', 'h1500', 'h1530']
ref2 = ['h0730', 'h0800', 'h0830', 'h0900', 'True', 'h1000', 'h1030', 'h1100', 'h1130', 'h1200', 'h1230', 'h1300', 'h1330', 'h1400', 'h1430', 'h1500', 'h1530']
ref3 = ['h0730', 'h0800', 'h0830', 'h0900', 'h0930', 'h1000', 'True', 'h1100', 'h1130', 'h1200', 'h1230', 'h1300', 'h1330', 'h1400', 'h1430', 'h1500', 'h1530']
ref4 = ['h0730', 'h0800', 'h0830', 'h0900', 'h0930', 'h1000', 'h1030', 'h1100', 'h1130', 'h1200', 'True', 'h1300', 'h1330', 'h1400', 'h1430', 'h1500', 'h1530']
ref5 = ['h0730', 'h0800', 'h0830', 'h0900', 'h0930', 'h1000', 'h1030', 'h1100', 'h1130', 'h1200', 'h1230', 'True', 'True', 'h1400', 'h1430', 'h1500', 'h1530']
ref6 = ['h0730', 'h0800', 'h0830', 'h0900', 'h0930', 'h1000', 'h1030', 'h1100', 'h1130', 'h1200', 'h1230', 'h1300', 'h1330', 'h1400', 'h1430', 'True', 'h1530']

ref_merged = ['True', 'h0800', 'h0830', 'True', 'True', 'h1000', 'True', 'h1100', 'h1130', 'h1200', 'True', 'True', 'h1330', 'h1400', 'h1430', 'True', 'h1530']

ref_merged_without_True = ['h0800', 'h0830', 'h1000', 'h1100', 'h1130', 'h1200', 'h1330', 'h1400', 'h1430', 'h1530']
    

Is it possible to get ref_merged_without_True directly from the query or is looping necessary?


Solution

  • I don't see why you enumerate 100 times, you can work with a set and thus work with:

    slots_remaining = {
        'h0730',
        'h0800',
        'h0830',
        'h0900',
        'h0930',
        'h1000',
        'h1030',
        'h1100',
        'h1130',
        'h1200',
        'h1230',
        'h1300',
        'h1330',
        'h1400',
        'h1430',
        'h1500',
        'h1530',
    }
    
    
    for slot in taken_slots:
        slots_remaining -= {name for name in slot_remaining if getattr(slot, name)}

    this will enumerate once over the taken_slots, and for each slot, only check the remaining once each time. At the end of the loop, the remaining once are still in slots_remaining. It will thus query the database once.

    But the modeling does not look very effective: databases usually should be designed lineary. This mean that two items are of the same "category", don't store these in different columns, but in different rows. This makes querying, especially in this case a lot more convenient.