190

Given a class:

from django.db import models

class Person(models.Model):
    name = models.CharField(max_length=20)

Is it possible, and if so how, to have a QuerySet that filters based on dynamic arguments? For example:

 # Instead of:
 Person.objects.filter(name__startswith='B')
 # ... and:
 Person.objects.filter(name__endswith='B')

 # ... is there some way, given:
 filter_by = '{0}__{1}'.format('name', 'startswith')
 filter_value = 'B'

 # ... that you can run the equivalent of this?
 Person.objects.filter(filter_by=filter_value)
 # ... which will throw an exception, since `filter_by` is not
 # an attribute of `Person`.
Brian M. Hunt
  • 81,008
  • 74
  • 230
  • 343

6 Answers6

362

Python's argument expansion may be used to solve this problem:

kwargs = {
    '{0}__{1}'.format('name', 'startswith'): 'A',
    '{0}__{1}'.format('name', 'endswith'): 'Z'
}

Person.objects.filter(**kwargs)

This is a very common and useful Python idiom.

Daniel Naab
  • 22,690
  • 8
  • 54
  • 55
  • 7
    Just a quick gotcha heads-up: make sure the strings in the kwargs are of type str not unicode, else filter() will grumble. – Steve Jalim Apr 04 '11 at 09:30
  • 1
    @santiagobasulto It's also referred to a parameter packing/unpacking, and variations thereof. – Daniel Naab Dec 10 '11 at 17:25
  • 5
    @DanielNaab but this will work only on kwargs working on AND condition filtering, any alternative for OR condition. – Prateek099 Aug 16 '16 at 12:55
  • 3
    @prateek you can always use Q objects: http://stackoverflow.com/questions/13076822/django-dynamically-filtering-with-q-objects – deecodameeko Feb 24 '17 at 21:50
  • 1
    @deecodameeko how to Q objects inside kwargs? – Kelvin Barsana Mar 01 '17 at 03:15
  • @deecodameeko You don't need Q objects inside kwargs, you just need a vice versa approach: Q(**kwargs1) | Q(**kwargs2) etc. Thus you may combine as many "OR" dynamic conditions as you need. – abcdn Sep 18 '17 at 22:24
  • You can collapse this further into: Person.objects.filter(**{ '{0}__{1}'.format('name', 'startswith'): 'A', '{0}__{1}'.format('name', 'endswith'): 'Z' }) – Paul Kenjora Jul 29 '18 at 18:43
  • Well, no offense to you, but this seems like a hack to achieve something that seems to be **naturally** possible. Shouldn't there be a way to perform a lookup based on a dynamic field name provided directly by Django (and not using raw sql) – user3245268 Feb 27 '19 at 16:19
  • Is it necessary to use the intermediate variable (kwargs), or can you just put the filter criteria dictionary directly as an argument to filter()? And if so, does it have to be called "kwargs"? – JoeMjr2 Jan 15 '20 at 21:26
  • 1
    @JoeMjr2 `kwargs` can be any variable name, and you can also do it inline: `Person.objects.filter(**{'{0}__{1}'.format('name', 'startswith'): 'A'})` – Daniel Naab Jan 16 '20 at 22:04
8

A simplified example:

In a Django survey app, I wanted an HTML select list showing registered users. But because we have 5000 registered users, I needed a way to filter that list based on query criteria (such as just people who completed a certain workshop). In order for the survey element to be re-usable, I needed for the person creating the survey question to be able to attach those criteria to that question (don't want to hard-code the query into the app).

The solution I came up with isn't 100% user friendly (requires help from a tech person to create the query) but it does solve the problem. When creating the question, the editor can enter a dictionary into a custom field, e.g.:

{'is_staff':True,'last_name__startswith':'A',}

That string is stored in the database. In the view code, it comes back in as self.question.custom_query . The value of that is a string that looks like a dictionary. We turn it back into a real dictionary with eval() and then stuff it into the queryset with **kwargs:

kwargs = eval(self.question.custom_query)
user_list = User.objects.filter(**kwargs).order_by("last_name")   
shacker
  • 14,712
  • 8
  • 89
  • 89
  • I'm wondering what it would take to create a custom ModelField/FormField/WidgetField that implemented the behavior to allow the user to, on the GUI side, basically "build" a query, never seeing the actual text, but using an interface to do so. Sounds like a neat project... – T. Stone Sep 23 '09 at 20:23
  • 1
    T. Stone - I'd imagine it would be easy to build such a tool in a simplistic way if the models that need querying were simple, but very difficult to do in a thorough way that exposed all possible options, especially if the models were complex. – shacker Sep 28 '09 at 06:59
  • 6
    -1 calling `eval()` on user import is a bad idea, even if you trust your users completely. A JSON field would be a better idea here. – John Carter Sep 20 '17 at 02:59
6

Additionally to extend on previous answer that made some requests for further code elements I am adding some working code that I am using in my code with Q. Let's say that I in my request it is possible to have or not filter on fields like:

publisher_id
date_from
date_until

Those fields can appear in query but they may also be missed.

This is how I am building filters based on those fields on an aggregated query that cannot be further filtered after the initial queryset execution:

# prepare filters to apply to queryset
filters = {}
if publisher_id:
    filters['publisher_id'] = publisher_id
if date_from:
    filters['metric_date__gte'] = date_from
if date_until:
    filters['metric_date__lte'] = date_until

filter_q = Q(**filters)

queryset = Something.objects.filter(filter_q)...

Hope this helps since I've spent quite some time to dig this up.

Edit:

As an additional benefit, you can use lists too. For previous example, if instead of publisher_id you have a list called publisher_ids, than you could use this piece of code:

if publisher_ids:
    filters['publisher_id__in'] = publisher_ids
Branko Radojevic
  • 660
  • 1
  • 5
  • 14
5

Django.db.models.Q is exactly what you want in a Django way.

Brent81
  • 1,152
  • 1
  • 13
  • 19
  • 12
    Could you (or someone) provide an example of how to use Q objects in using dynamic field names ? – jackdbernier Aug 26 '14 at 23:32
  • 4
    It's the same as in [Daniel Naab's answer](http://stackoverflow.com/a/310785/4021086) The only difference being that you pass the arguments into the Q object constructor. `Q(**filters)`, if you wanted to dynamically build up Q objects you can put them in a list and use `.filter(*q_objects)`, or use the bitwise operators to combine the Q objects. – Will S Mar 02 '16 at 15:58
  • 10
    This answer should really include an example of using Q to solve OP's problem. – pdoherty926 Mar 05 '19 at 15:55
  • I have an example here, but it will probably break inside the comment, so I wrote additional answer here. – Branko Radojevic Aug 31 '21 at 07:26
0

This looks much more understandable to me:

kwargs = {
    'name__startswith': 'A',
    'name__endswith': 'Z',
    ***(Add more filters here)***

}
Person.objects.filter(**kwargs)

amarse
  • 19
  • 3
-3

A really complex search forms usually indicates that a simpler model is trying to dig it's way out.

How, exactly, do you expect to get the values for the column name and operation? Where do you get the values of 'name' an 'startswith'?

 filter_by = '%s__%s' % ('name', 'startswith')
  1. A "search" form? You're going to -- what? -- pick the name from a list of names? Pick the operation from a list of operations? While open-ended, most people find this confusing and hard-to-use.

    How many columns have such filters? 6? 12? 18?

    • A few? A complex pick-list doesn't make sense. A few fields and a few if-statements make sense.
    • A large number? Your model doesn't sound right. It sounds like the "field" is actually a key to a row in another table, not a column.
  2. Specific filter buttons. Wait... That's the way the Django admin works. Specific filters are turned into buttons. And the same analysis as above applies. A few filters make sense. A large number of filters usually means a kind of first normal form violation.

A lot of similar fields often means there should have been more rows and fewer fields.

S.Lott
  • 384,516
  • 81
  • 508
  • 779
  • Thanks for the reply. The data model given as an example is just that, for easy illustration. I prefer not to imagine anyone putting anything that heinous into a real project. ;) I want to decouple generic relations and factor out some reusable logic. – Brian M. Hunt Nov 22 '08 at 05:52
  • Django already *is* generic. Writing more generic stuff on top of Django is a bit too much genericity. My recommendation is to simply implement your application, avoiding over-generalization an already generic framework. – S.Lott Nov 22 '08 at 13:36
  • 13
    With respect, it's presumptuous to make recommendations without knowing anything about the design. To "simply implement" this application would beget astronomical (>200 apps ^21 foos) functions to meet the requirements. You're reading purpose and intent into the example; you shouldn't. :) – Brian M. Hunt Nov 22 '08 at 15:07
  • 2
    I meet a lot of people who feel that their problem would be trivial to solve if only things were (a) more generic and (b) worked the way they imagined. That way lies endless frustration because things aren't the way they imagined. I've seen too many failures stem from "fixing the framework". – S.Lott Nov 22 '08 at 15:36
  • @BMH: If I don't know anything about the design, then, well, um, perhaps the question is incomplete. If I'm not supposed to presume, how am I supposed to get enough information? If you don't like my presumption, perhaps you could provide facts to show me how wrong I am. – S.Lott Nov 22 '08 at 15:38
  • 2
    Things work as expected and desired per Daniel's response. My question was about syntax, not design. If I had time to write out the design, I'd have done that. I'm sure your input would be helpful, however it's just not a practical option. – Brian M. Hunt Nov 22 '08 at 16:29
  • @BMH: here's the point: Please don't call it presumptuous when the question could be incomplete. You can't complain about answers on SO to begin with, much less complain that they aren't spot on a question that appears unsound. – S.Lott Nov 22 '08 at 20:05
  • 9
    S.Lott, your answer doesn't even remotely answer this question. If you don't know an answer, please leave the question alone. Don't respond with unsolicited design advice when you have absolutely zero knowledge of the design! – slypete Aug 18 '09 at 06:28
  • 2
    @slypete: If a change to the design removes the problem, then the problem's solved. Continuing along the path based on a poor design is more expensive and complex than necessary. Solving root-cause problems is better than solving other problems that stem from bad design decisions. I'm sorry you don't like root-cause analysis. But when something's really hard, it usually means you're trying the wrong thing to begin with. – S.Lott Aug 18 '09 at 10:11