That should work properly. It works for me using exactly your models
with a few different Host/Domain combinations.
The SQL we currently generate for Domain.objects.exclude(hosts=None) is
a little inefficient, but it's not fundamentally incorrect. It just uses
a couple more tables than it needs to be because the "NULL" case can
take advantage of one particular extra optimisation that isn't in the
code yet. I'm surprised it isn't returning the correct result.
Another to get the same answer, which generates simpler SQL but is
potentially a little less efficient on the database side is
Domain.objects.filter(hosts__isnull=False).distinct()
That's essentially the only case where Django has a "not" filter
available: you can actually filter for "not NULL". The distinct() call
there is important, as for many-to-many fields, you would otherwise get
back one domain object for every host (so repetitions for domains with
multiple hosts), not just the set of domains that have at least one
host.
In any case, I suspect there's something else going on in your
particular case, as the SQL generated should be pulling back the results
you're after and it works with a small example using your models. If you
can use those models, as pasted, to generate an incorrect result, what
is the smallest amount of data (Host & Domain objects) you need to
create to demonstrate the problem. That would be interesting to see.
Regards,
Malcolm