开发者

Django: remove a filter condition from a queryset

I have a third-party function which gives me a filtered queryset (e.g. records with 'valid'=True) but I w开发者_运维问答ant to remove a particular condition (e.g. to have all records, both valid and invalid).

Is there a way to remove a filter condition to an already-filtered queryset?

E.g.

only_valid = MyModel.objects.filter(valid=True)
all_records = only_valid.**remove_filter**('valid')

(I know that it would be better to define 'all_records' before 'only_valid', but this is just an example...)


Although there is no official way to do this using filter notation, you may easily do it with Q-notation. For example, if you ensure that third-part function returns a Q object, not a filtered QuerySet, you may do the following:

q = ThirdParty()
q = q | Q(valid=False)

And the resulting SQL conditions will be joined using OR operator.


From the docs:

Each time you refine a QuerySet, you get a brand-new QuerySet that is in no way bound to the previous QuerySet. Each refinement creates a separate and distinct QuerySet that can be stored, used and reused.

I doubt therefore, that there is a standard way to do it. You could dig into the code, see, what filter() does and try a bit. If that doesn't help, my assumption is, you're out of luck and need to re-build the query yourself.


Use this function

from django.db.models import Q

def remove_filter(lookup, queryset):
    """
    Remove filter lookup in queryset
    ```
    >>> queryset = User.objects.filter(email='user@gmail.com')
    >>> queryset.count()
    1
    >>> remove_filter('email', queryset)
    >>> queryset.count()
    1000
    ```
    """
    query = queryset.query
    q = Q(**{lookup: None})
    clause, _ = query._add_q(q, self.used_aliases)

    def filter_lookups(child):
        return child.lhs.target != clause.children[0].lhs.target

    query.where.children = list(filter(filter_lookups, query.where.children))


Here's what I did in a similar case.

all_records = MyModel.objects.all()
only_valid = MyModel.objects.filter(valid=True)
only_valid.original = all_records

...

all_records = only_valid.original

Obviously this clears any other filters too so it won't be right for every case.


original_query_set = MyModel.objects.filter(**conditions)
model_class = orginal_query_set.model
new_query_set = model_class.objects.filter(**new_conditions)

You can use the .model attribute on a QuerySet to get the model class, then use the model class to make a brand new QuerySet.


Thanks for making me check the source code Boldewyn. So, it seems there's a new method right below filter() with the same parameters... called exclude() (as of commit mini-hash ef6c680, for when it loses its line number)

Return a new QuerySet instance with NOT (args) ANDed to the existing set.

So, to answer the original question:

only_valid = MyModel.objects.filter(valid=True)
filtered_results = only_valid.exclude(the_condition_to_remove=True)
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜