Eduardo
Eduardo

Reputation: 258

Django conditional queries: How can I avoid this?

I have multiple conditional queries based on whether a variable is set or not and the value of 'doc_type'. But I think it looks ugly and I'm repeating code. Is there a clearer way to get this done?

if status is not None:
    if doc_type == 4:
        invoices = Invoice.objects.filter(Q(type=4) | Q(type=5) | Q(status=status))
    else:
        invoices = Invoice.objects.filter(type=doc_type, status=status)
else:
    if doc_type == 4:
        invoices = Invoice.objects.filter(Q(type=4) | Q(type=5))
    else:
        invoices = Invoice.objects.filter(type=doc_type)

Upvotes: 0

Views: 267

Answers (3)

okm
okm

Reputation: 23871

If one status is OR and another status is AND, remember QuerySet supports | and & as well:

qs = Invoice.objects.filter(doc_type__in=(doc_type, 5) if doc_type == 4 else (doc_type,))
if status is not None:
    if doc_type == 4:
        qs |= Invoice.objects.filter(status=status)
    else:
        qs = qs.filter(status=status)

Upvotes: 0

Alasdair
Alasdair

Reputation: 308799

Are you sure this is what you want?

    invoices = Invoice.objects.filter(Q(type=4) | Q(type=5) | Q(status=status))

This will return all invoices where type=4 OR type=5 OR status=Status.

I think you mean (type is 4 or 5) AND status=status? If so, you can do your Query as follows:

types = [doc_type]
if doc_type == 4:
    types.append(5)
invoices = Invoice.objects.filter(type__in=types)
if status is not None:
    invoices = invoices.filter(status=status)

You can use Q() objects if you prefer, but I think that chaining filters is easier to understand in this case.

Upvotes: 4

falsetru
falsetru

Reputation: 368944

Q objects can be combined using | or &.

Using operator.or_ (|) and operator.and_ (&) which works as functions instead of operator, the code can be expressed as:

import operator

op = operator.and_
q = Q(type=doc_type)  # This is always included.
if doc_type == 4:
    # When doc_type == 4, conditions are combine with `|`, otherwise `&`
    op = operator.or_
    q = Q(type=doc_type) | Q(type=5)

if status is not None:
    q = op(q, status=status)

invoices = Invoice.objects.filter(q)

Upvotes: 4

Related Questions