What happens is that the exclude () request is used for you. Basically, it excludes any Order_type that has at least one Order without status, which is almost certainly not what you want.
The simplest solution in your case is to use order__status__gt='' in your filter () arguments. However, you also need to add distinct() to the end of your query, because otherwise you will get a QuerySet with multiple instances of the same Order_type if it has multiple orders matching the query. This should work:
qs = Order_type.objects.filter( order__order_date__lte=datetime.date.today(), order__processed_time__isnull=True, order__status__gt='').distinct()
On the side of the note, in the qs request that you gave at the end of the question, you do not need to say order__id__in=[o.id for o in orders_qs] , you can just use order__in=orders_qs (you will also need distinct() ). This will also work:
qs = Order_type.objects.filter(order__in=Order.objects.filter( order_date__lte=datetime.date.today(), processed_time__isnull=True).exclude(status='')).distinct()
Adding (change):
Here is the actual SQL that Django produces for the above queries:
SELECT DISTINCT "testapp_order_type"."id", "testapp_order_type"."description" FROM "testapp_order_type" LEFT OUTER JOIN "testapp_order" ON ("testapp_order_type"."id" = "testapp_order"."type_id") WHERE ("testapp_order"."order_date" <= E'2010-07-18' AND "testapp_order"."processed_time" IS NULL AND "testapp_order"."status" > E'' ); SELECT DISTINCT "testapp_order_type"."id", "testapp_order_type"."description" FROM "testapp_order_type" INNER JOIN "testapp_order" ON ("testapp_order_type"."id" = "testapp_order"."type_id") WHERE "testapp_order"."id" IN (SELECT U0."id" FROM "testapp_order" U0 WHERE (U0."order_date" <= E'2010-07-18' AND U0."processed_time" IS NULL AND NOT (U0."status" = E'' )));
EXPLAIN shows that the second query is always a bit more expensive (cost 28.99 versus 28.64 with a very small data set).
source share