Dynamic filter in Django - python

In my app, I have a form. Depending of the form, one or many filters could be configured by the user.
In my view, I have for exemple :
query = Test.objects.filter(filter1 = request.post['filter1'], filter2 = request.post['filter2'], filter3 = request.post['filter3'])
So, sometimes filter1, filter2 or filter3 could not exist.
If any filters doesn't exist, I just want to ignore the filter.
I could do a script with many "IF" conditions but may be there is a smart solution ?
Thanks for your help !

You could do something along the lines of:
filters = {}
for key, value in request.post.items():
if key in ['filter1', 'filter2', 'filter3']:
filters[key] = value
Test.objects.filter(**filters)
Where the list is a list of keys that you are intending to filter by
Edit
As Thomas Junk suggested you can make it a lot cleaner with a comprehension:
filters = {
key: value
for key, value in request.post.items()
if key in ['filter1', 'filter2', 'filter3']
}
Test.objects.filter(**filters)

This code solved my problem:
if request.method == 'GET':
filters = {}
for key, value in request.GET.items():
if value != '':
filters[key] = value
filter_list=Pet.objects.filter(**filters)

You could use something like this:
import ast
def query_to_dict(query) -> dict:
filters = {}
for field, value in query.items(): # <- param and value like ?param1=True
filters[field] = ast.literal_eval(str(value)) # <- parse list/str/bool
return filters
def event_view(request):
events = Event.objects.filter(
Q(**query_to_dict(request.GET))
)
return Response(
EventsSerializer(events, many=True).data
)
In this example:
ast - module which will help to parse bool values and lists
request.GET - this is your query params ?param1=True&param2="[1, 2]". Make sure to pass params in way like ?title="hello" - using double quotes
query_to_dict - function which will convert query params to python dict. So you can pass this dict to your Event.objects.filter.
Benefit of using filtering this way, you can pass ?id__in="[1, 2]" something like this and it will work. I can't say for sure how it secure or not, but for some complex filtering or bool fields filtering it is perfect.

Related

Is there a better way to parse a python dictionary?

I have a json dictionary and I need to check the values of the data and see if there is a match. I am using multiple if statements and the in operator like so:
"SomeData":
{
"IsTrue": true,
"ExtraData":
{
"MyID": "1223"
}
}
json_data = MYAPI.get_json()
if 'SomeData' in json_data:
some_data = json_data['SomeData']
if 'IsTrue' in some_data:
if some_data['IsTrue'] is True:
if 'ExtraData' in some_data:
if 'MyID' in some_data['ExtraData']:
if some_data['ExtraData']['MyID'] == "1234":
is_a_match = True
break
I know that in python3 the in operator should be used, but I am thinking there must be a better way than using multiple if statements like I am using.
Is there a better way to parse json data like this?
Yes, you can assume that the keys are present, but catch a KeyError if they aren't.
try:
some_data = json_data['SomeData']
is_a_match = (
some_data['IsTrue'] is True and
some_data['ExtraData']['MyID'] == "1234"
)
except KeyError:
is_a_match = False
This style is called easier to ask for forgiveness than permission (EAFP) and it's used a lot in Python. The alternative is look before you leap (LBYL), which you use in your solution.
I would suggest writing a path function to access values in your nested dictionary. Something along the lines of this (pseudocode):
def get_path_value(json_dict, path):
"""
json_dict - dictionary with json keys and values
path - list of key sequences, e.g. ['SomeData', 'IsTrue']
you can make this a helper function and use an entry point that
splits paths, e.g. "SomeData/IsTrue"
"""
if len(path) == 1:
# last tag, base case
return json_dict[path[0]]
else:
return get_path_value(json_dict[path[0]], path[1:])
Add try/catch if you want something other than bad key, but this will let you navigat the dictionary a little more eloquently. Then you have things like:
if get_path_value(json_dict, ["SomeData", "IsTrue"]) == True and ...
You could even write a nice little class to wrap this all up, e.g. json["SomeData/IsTrue"] == True
Best of luck,
Marie

Django: how to use a variable key filter with __range

date_one = form.cleaned_data.get('date_one')
date_two = form.cleaned_data.get('date_two')
date_type = form.cleaned_data.get('date_type')
search = MyClass.objects.filter(date_type__range(date_one, date_two))
My model has two different date columns. (created and expires). The user can make a query filtering between two dates, but he can choose if he wants to filter by creation or expiration.
I could make two query lines using if, but I really want to know how to do it in the way I'm asking.
How can I do this? Since the key before __range is a variable. I tried with (**{ filter: search_string }) but it seems not to be compatible with __range.
try this
filter_dict = {"{}__range".format(date_type): [date_one, date_two]}
search = MyClass.objects.filter(**filter_dict)
The thing you attempted is almost correct!
Lookups are not functions (so it's not foo__range(start, end)), but they are keyword arguments: foo__range=(start, end)
So you would have:
date_one = form.cleaned_data.get('date_one')
date_two = form.cleaned_data.get('date_two')
date_type = form.cleaned_data.get('date_type')
query_kwargs = {
"{}__range".format(date_type): (date_one, date_two)
}
search = MyClass.objects.filter(**query_kwargs)

Django: Add a list to a QuerySet

I am new to django so apologies if this is not possible or easy.
I have a view that takes a subset of a model
data = Terms.objects.filter(language = language_id)
The subset is one language. The set has a number of concepts for a language. Some languages might use the same word for multiple concepts, and I want to colour these the same in an SVG image. So I do this next:
for d in data:
if d.term is None:
d.colour = "#D3D3D3"
else:
d.colour = termColours[d.term]
Where termColours is a dictionary with keys as the unique terms and values as the hexadecimal colour I want.
I thought this would add a new colour attribute to my queryset. However, when I convert the queryset to json (in order to pass it to JS) the colour object is not there.
terms_json = serializers.serialize('json', data)
How can I add a new colour element to my queryset?
Convert your Queryset to Dict and then modify values.
Ex:
data = Terms.objects.filter(language = language_id).values()
for d in data:
if d.term is None:
d.colour = "#D3D3D3"
else:
d.colour = termColours[d.term]
If I understand correctly - you need Django ORM annotation. And it might look like that:
from django.db.models import Case, When, Value
data = Terms.objects.filter(language = language_id)
.annotate(color = Case(
When(term__isnull = True, then = "#D3D3D3"),
When(term__isnull = False, then = termColours[Value(term)]),))
Only problem here - I don't exactly know this moment - termColours[Value(term)], you need to test different combinations of that expressions to get the value of field term.

Cleaner way to represent Rules (if-else) in Python

I am trying to find a design pattern (or maybe an algorithm) which will help me write these rules in a cleaner way. Any suggestions?
def get_rules(user, value):
if 500 <= value < 5000 and not user.address:
return [REQUEST_ADDRESS]
if value >= 5000:
if not user.address and not user.phone:
return [REQUEST_ADDRESS, REQUEST_PHONE]
if user.address and not user.phone:
return [REQUEST_PHONE]
if not user.address and user.phone:
return [REQUEST_ADDRESS]
# Potentially ~20 more conditions here based on various attributes of user
return [STATES.REQUEST_NONE]
Note: I am not looking for a rules engine since I don't want to complicate my code by adding "business friendly" DSL in python. Python itself is a simple language to write these rules.
Interesting read: http://martinfowler.com/bliki/RulesEngine.html (but I am still trying to stay away from a "framework" to do this for me).
You're checking lots of different combinations with your "if a and not b else check not a and b else check not a and not b" strategy to figure out what combination of requests you need to send.
Instead, only check what you're missing:
missing = []
if not user.phone:
missing.append(REQUEST_PHONE)
if not user.address:
missing.append(REQUEST_ADDRESS)
return missing or [REQUEST_NONE]
You can use a dict in this case:
resdict = {(False, False): [REQUEST_ADDRESS, REQUEST_PHONE],
(True, False): [REQUEST_PHONE],
(False, True): [REQUEST_ADDRESS]}
return resdict[(user.address, user.phone)]
You can also use a list comprehension:
return [req for req, haveit in zip([REQUEST_ADDRESS, REQUEST_PHONE], [user.address, user.phone]) if not haveit]
Or a simpler list append:
res = []
if not user.address:
res.append(REQUEST_ADDRESS)
if not user.phone:
res.append(REQUEST_PHONE)
If I understood the question right, you have a list of attributes for the user. If one is false a REQUEST value schould be added to the list. Then this could help:
# define all your combinations here:
mapping = {'address': REQUEST_ADDRESS, 'phone': REQUEST_PHONE, …)
return [value for key, value in mapping.items()
if not getattr(user, key, None)]
Looks like your "rules" boil down to this: Request values for fields that are not present as attributes in the object user. I will assume that the mapping of attributes to requests can be arbitrary; you can represent it as a dictionary mapping, e.g. like this:
rulemap = {
"address": REQUEST_ADDRESS,
"phone": REQUEST_PHONE,
# etc.
}
You can then get a list of the requests to issue by checking which of the keys in rulemap are not present as attributes in the object user:
return [ rulemap[fld] for fld in rulemap.keys() if fld not in user.__dict__ ]

How to filter a Django query set by passing in a list of options?

I've got an abstract ProductAttribute model that can be associated with a Product model. The attributes can be accessed using product.attribute_values.
Say I've got a form which contains two select boxes that allow the user to specify the values of two attributes - Size and Weight.
How do I, in a single query, pass in those values into a filter, so that - as you can with a list of integers with Object.objects.filter(pk__in=(1,2,3)) - I can select the Product that matches against all those attribute values?
I'd like to be able to do something like:
options = ['XL','50lbs']
p = Product.objects.filter(attribute_values__matches=options)
Is this possible in a one-liner in Django?
TIA
You can use __contains or __exact
and for case insensitive matches __icontains and __iexact
You can use Q model lookups to do an and filtering:
options = ['X1', 'X2', 'X3']
qs = [Q(attribute_name=option) for option in options] #or attribute_name__icontains - or whatever
query = qs.pop() #get the first element
for q in qs:
query |= q
qs = MyModel.objects.filter(query)
I ended up cracking this one with a little help from #karthikr above, and the the Q.add() method:
options = {'Size':'XL'}
qs = Q()
for key, val in options.iteritems():
qs.add(Q(attributes__name=key) & Q(attribute_values__value_option__option=val), qs.connector)
Hope this helps someone else in some small way in future.

Categories

Resources